diff --git a/app/email/api/internal/handler/email/forgotPasswordHandler.go b/app/email/api/internal/handler/email/forgotPasswordHandler.go new file mode 100644 index 0000000..ae22950 --- /dev/null +++ b/app/email/api/internal/handler/email/forgotPasswordHandler.go @@ -0,0 +1,32 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.2 + +package email + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "juwan-backend/app/email/api/internal/logic/email" + "juwan-backend/app/email/api/internal/svc" + "juwan-backend/app/email/api/internal/types" +) + +// 忘记密码-发送验证码 +func ForgotPasswordHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.ForgotPasswordReq + if err := httpx.Parse(r, &req); err != nil { + httpx.ErrorCtx(r.Context(), w, err) + return + } + + l := email.NewForgotPasswordLogic(r.Context(), svcCtx) + resp, err := l.ForgotPassword(&req) + if err != nil { + httpx.ErrorCtx(r.Context(), w, err) + } else { + httpx.OkJsonCtx(r.Context(), w, resp) + } + } +} diff --git a/app/email/api/internal/handler/routes.go b/app/email/api/internal/handler/routes.go index cc896c3..36a5f5a 100644 --- a/app/email/api/internal/handler/routes.go +++ b/app/email/api/internal/handler/routes.go @@ -25,6 +25,18 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { }, }..., ), - rest.WithPrefix("/api/email"), + rest.WithPrefix("/api/v1/email"), + ) + + server.AddRoutes( + []rest.Route{ + { + // 忘记密码-发送验证码 + Method: http.MethodPost, + Path: "/forgot-password/send", + Handler: email.ForgotPasswordHandler(serverCtx), + }, + }, + rest.WithPrefix("/api/v1/auth"), ) } diff --git a/app/email/api/internal/handler/user/forgotPasswordHandler.go b/app/email/api/internal/handler/user/forgotPasswordHandler.go new file mode 100644 index 0000000..9f7cd16 --- /dev/null +++ b/app/email/api/internal/handler/user/forgotPasswordHandler.go @@ -0,0 +1,32 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.2 + +package user + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "juwan-backend/app/email/api/internal/logic/user" + "juwan-backend/app/email/api/internal/svc" + "juwan-backend/app/email/api/internal/types" +) + +// 忘记密码-发送验证码 +func ForgotPasswordHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.ForgotPasswordReq + if err := httpx.Parse(r, &req); err != nil { + httpx.ErrorCtx(r.Context(), w, err) + return + } + + l := user.NewForgotPasswordLogic(r.Context(), svcCtx) + resp, err := l.ForgotPassword(&req) + if err != nil { + httpx.ErrorCtx(r.Context(), w, err) + } else { + httpx.OkJsonCtx(r.Context(), w, resp) + } + } +} diff --git a/app/email/api/internal/logic/email/forgotPasswordLogic.go b/app/email/api/internal/logic/email/forgotPasswordLogic.go new file mode 100644 index 0000000..2bd1c8b --- /dev/null +++ b/app/email/api/internal/logic/email/forgotPasswordLogic.go @@ -0,0 +1,34 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.2 + +package email + +import ( + "context" + + "juwan-backend/app/email/api/internal/svc" + "juwan-backend/app/email/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type ForgotPasswordLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// 忘记密码-发送验证码 +func NewForgotPasswordLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ForgotPasswordLogic { + return &ForgotPasswordLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *ForgotPasswordLogic) ForgotPassword(req *types.ForgotPasswordReq) (resp *types.EmptyResp, err error) { + // todo: add your logic here and delete this line + + return +} diff --git a/app/email/api/internal/logic/user/forgotPasswordLogic.go b/app/email/api/internal/logic/user/forgotPasswordLogic.go new file mode 100644 index 0000000..8edaee0 --- /dev/null +++ b/app/email/api/internal/logic/user/forgotPasswordLogic.go @@ -0,0 +1,34 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.2 + +package user + +import ( + "context" + + "juwan-backend/app/email/api/internal/svc" + "juwan-backend/app/email/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type ForgotPasswordLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// 忘记密码-发送验证码 +func NewForgotPasswordLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ForgotPasswordLogic { + return &ForgotPasswordLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *ForgotPasswordLogic) ForgotPassword(req *types.ForgotPasswordReq) (resp *types.EmptyResp, err error) { + // todo: add your logic here and delete this line + + return +} diff --git a/app/email/api/internal/types/types.go b/app/email/api/internal/types/types.go index 57b59f2..81929ab 100644 --- a/app/email/api/internal/types/types.go +++ b/app/email/api/internal/types/types.go @@ -3,6 +3,13 @@ package types +type EmptyResp struct { +} + +type ForgotPasswordReq struct { + Email string `json:"email"` +} + type SendVerificationCodeReq struct { Email string `json:"email" binding:"required,email"` Scene string `json:"scene" binding:"required,oneof=register login reset_password bind_email"` diff --git a/app/objectstory/api/etc/file-api.yaml b/app/objectstory/api/etc/file-api.yaml new file mode 100644 index 0000000..e65e032 --- /dev/null +++ b/app/objectstory/api/etc/file-api.yaml @@ -0,0 +1,3 @@ +Name: file-api +Host: 0.0.0.0 +Port: 8888 diff --git a/app/objectstory/api/file.go b/app/objectstory/api/file.go new file mode 100644 index 0000000..87302be --- /dev/null +++ b/app/objectstory/api/file.go @@ -0,0 +1,34 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.2 + +package main + +import ( + "flag" + "fmt" + + "juwan-backend/app/objectstory/api/internal/config" + "juwan-backend/app/objectstory/api/internal/handler" + "juwan-backend/app/objectstory/api/internal/svc" + + "github.com/zeromicro/go-zero/core/conf" + "github.com/zeromicro/go-zero/rest" +) + +var configFile = flag.String("f", "etc/file-api.yaml", "the config file") + +func main() { + flag.Parse() + + var c config.Config + conf.MustLoad(*configFile, &c) + + server := rest.MustNewServer(c.RestConf) + defer server.Stop() + + ctx := svc.NewServiceContext(c) + handler.RegisterHandlers(server, ctx) + + fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port) + server.Start() +} diff --git a/app/objectstory/api/internal/config/config.go b/app/objectstory/api/internal/config/config.go new file mode 100644 index 0000000..33aaf22 --- /dev/null +++ b/app/objectstory/api/internal/config/config.go @@ -0,0 +1,14 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.2 + +package config + +import "github.com/zeromicro/go-zero/rest" + +type Config struct { + rest.RestConf + Logger struct { + AccessSecret string + AccessExpire int64 + } +} diff --git a/app/objectstory/api/internal/handler/file/getFileHandler.go b/app/objectstory/api/internal/handler/file/getFileHandler.go new file mode 100644 index 0000000..5182473 --- /dev/null +++ b/app/objectstory/api/internal/handler/file/getFileHandler.go @@ -0,0 +1,32 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.2 + +package file + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "juwan-backend/app/objectstory/api/internal/logic/file" + "juwan-backend/app/objectstory/api/internal/svc" + "juwan-backend/app/objectstory/api/internal/types" +) + +// 文件获取接口 (如果是私有文件,通过此接口获取或重定向) +func GetFileHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.GetFileReq + if err := httpx.Parse(r, &req); err != nil { + httpx.ErrorCtx(r.Context(), w, err) + return + } + + l := file.NewGetFileLogic(r.Context(), svcCtx) + err := l.GetFile(&req) + if err != nil { + httpx.ErrorCtx(r.Context(), w, err) + } else { + httpx.Ok(w) + } + } +} diff --git a/app/objectstory/api/internal/handler/file/uploadHandler.go b/app/objectstory/api/internal/handler/file/uploadHandler.go new file mode 100644 index 0000000..ebe0f0d --- /dev/null +++ b/app/objectstory/api/internal/handler/file/uploadHandler.go @@ -0,0 +1,32 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.2 + +package file + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "juwan-backend/app/objectstory/api/internal/logic/file" + "juwan-backend/app/objectstory/api/internal/svc" + "juwan-backend/app/objectstory/api/internal/types" +) + +// 文件上传接口 +func UploadHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.UploadReq + if err := httpx.Parse(r, &req); err != nil { + httpx.ErrorCtx(r.Context(), w, err) + return + } + + l := file.NewUploadLogic(r.Context(), svcCtx) + resp, err := l.Upload(&req) + if err != nil { + httpx.ErrorCtx(r.Context(), w, err) + } else { + httpx.OkJsonCtx(r.Context(), w, resp) + } + } +} diff --git a/app/objectstory/api/internal/handler/routes.go b/app/objectstory/api/internal/handler/routes.go new file mode 100644 index 0000000..63c042e --- /dev/null +++ b/app/objectstory/api/internal/handler/routes.go @@ -0,0 +1,37 @@ +// Code generated by goctl. DO NOT EDIT. +// goctl 1.9.2 + +package handler + +import ( + "net/http" + + file "juwan-backend/app/objectstory/api/internal/handler/file" + "juwan-backend/app/objectstory/api/internal/svc" + + "github.com/zeromicro/go-zero/rest" +) + +func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { + server.AddRoutes( + rest.WithMiddlewares( + []rest.Middleware{serverCtx.FileSizeLimit}, + []rest.Route{ + { + // 文件获取接口 (如果是私有文件,通过此接口获取或重定向) + Method: http.MethodGet, + Path: "/files/:fileId", + Handler: file.GetFileHandler(serverCtx), + }, + { + // 文件上传接口 + Method: http.MethodPost, + Path: "/upload", + Handler: file.UploadHandler(serverCtx), + }, + }..., + ), + rest.WithJwt(serverCtx.Config.Logger.AccessSecret), + rest.WithPrefix("/api/v1"), + ) +} diff --git a/app/objectstory/api/internal/logic/file/getFileLogic.go b/app/objectstory/api/internal/logic/file/getFileLogic.go new file mode 100644 index 0000000..f482810 --- /dev/null +++ b/app/objectstory/api/internal/logic/file/getFileLogic.go @@ -0,0 +1,34 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.2 + +package file + +import ( + "context" + + "juwan-backend/app/objectstory/api/internal/svc" + "juwan-backend/app/objectstory/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type GetFileLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// 文件获取接口 (如果是私有文件,通过此接口获取或重定向) +func NewGetFileLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetFileLogic { + return &GetFileLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetFileLogic) GetFile(req *types.GetFileReq) error { + // todo: add your logic here and delete this line + + return nil +} diff --git a/app/objectstory/api/internal/logic/file/uploadLogic.go b/app/objectstory/api/internal/logic/file/uploadLogic.go new file mode 100644 index 0000000..8494b4b --- /dev/null +++ b/app/objectstory/api/internal/logic/file/uploadLogic.go @@ -0,0 +1,34 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.2 + +package file + +import ( + "context" + + "juwan-backend/app/objectstory/api/internal/svc" + "juwan-backend/app/objectstory/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type UploadLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// 文件上传接口 +func NewUploadLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UploadLogic { + return &UploadLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *UploadLogic) Upload(req *types.UploadReq) (resp *types.UploadResp, err error) { + // todo: add your logic here and delete this line + + return +} diff --git a/app/objectstory/api/internal/middleware/filesizelimitMiddleware.go b/app/objectstory/api/internal/middleware/filesizelimitMiddleware.go new file mode 100644 index 0000000..8aecbc7 --- /dev/null +++ b/app/objectstory/api/internal/middleware/filesizelimitMiddleware.go @@ -0,0 +1,22 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.2 + +package middleware + +import "net/http" + +type FileSizeLimitMiddleware struct { +} + +func NewFileSizeLimitMiddleware() *FileSizeLimitMiddleware { + return &FileSizeLimitMiddleware{} +} + +func (m *FileSizeLimitMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // TODO generate middleware implement function, delete after code implementation + + // Passthrough to next handler if need + next(w, r) + } +} diff --git a/app/objectstory/api/internal/svc/serviceContext.go b/app/objectstory/api/internal/svc/serviceContext.go new file mode 100644 index 0000000..2edfc00 --- /dev/null +++ b/app/objectstory/api/internal/svc/serviceContext.go @@ -0,0 +1,22 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.2 + +package svc + +import ( + "github.com/zeromicro/go-zero/rest" + "juwan-backend/app/objectstory/api/internal/config" + "juwan-backend/app/objectstory/api/internal/middleware" +) + +type ServiceContext struct { + Config config.Config + FileSizeLimit rest.Middleware +} + +func NewServiceContext(c config.Config) *ServiceContext { + return &ServiceContext{ + Config: c, + FileSizeLimit: middleware.NewFileSizeLimitMiddleware().Handle, + } +} diff --git a/app/objectstory/api/internal/types/types.go b/app/objectstory/api/internal/types/types.go new file mode 100644 index 0000000..b0bc84d --- /dev/null +++ b/app/objectstory/api/internal/types/types.go @@ -0,0 +1,16 @@ +// Code generated by goctl. DO NOT EDIT. +// goctl 1.9.2 + +package types + +type GetFileReq struct { + FileId string `path:"fileId"` +} + +type UploadReq struct { + Type string `form:"type,options=avatar|chat|post|verification|dispute"` // 文件类型限制 +} + +type UploadResp struct { + Url string `json:"url"` // 返回 CDN 地址或访问地址 +} diff --git a/app/objectstory/rpc/etc/file.yaml b/app/objectstory/rpc/etc/file.yaml new file mode 100644 index 0000000..dd71650 --- /dev/null +++ b/app/objectstory/rpc/etc/file.yaml @@ -0,0 +1,6 @@ +Name: file.rpc +ListenOn: 0.0.0.0:8080 +Etcd: + Hosts: + - 127.0.0.1:2379 + Key: file.rpc diff --git a/app/objectstory/rpc/file.go b/app/objectstory/rpc/file.go new file mode 100644 index 0000000..e45e019 --- /dev/null +++ b/app/objectstory/rpc/file.go @@ -0,0 +1,39 @@ +package main + +import ( + "flag" + "fmt" + + "juwan-backend/app/objectstory/rpc/internal/config" + "juwan-backend/app/objectstory/rpc/internal/server" + "juwan-backend/app/objectstory/rpc/internal/svc" + "juwan-backend/app/objectstory/rpc/pb" + + "github.com/zeromicro/go-zero/core/conf" + "github.com/zeromicro/go-zero/core/service" + "github.com/zeromicro/go-zero/zrpc" + "google.golang.org/grpc" + "google.golang.org/grpc/reflection" +) + +var configFile = flag.String("f", "etc/file.yaml", "the config file") + +func main() { + flag.Parse() + + var c config.Config + conf.MustLoad(*configFile, &c) + ctx := svc.NewServiceContext(c) + + s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) { + pb.RegisterFileServiceServer(grpcServer, server.NewFileServiceServer(ctx)) + + if c.Mode == service.DevMode || c.Mode == service.TestMode { + reflection.Register(grpcServer) + } + }) + defer s.Stop() + + fmt.Printf("Starting rpc server at %s...\n", c.ListenOn) + s.Start() +} diff --git a/app/objectstory/rpc/fileservice/fileService.go b/app/objectstory/rpc/fileservice/fileService.go new file mode 100644 index 0000000..b024ebe --- /dev/null +++ b/app/objectstory/rpc/fileservice/fileService.go @@ -0,0 +1,50 @@ +// Code generated by goctl. DO NOT EDIT. +// goctl 1.9.2 +// Source: objectstory.proto + +package fileservice + +import ( + "context" + + "juwan-backend/app/objectstory/rpc/pb" + + "github.com/zeromicro/go-zero/zrpc" + "google.golang.org/grpc" +) + +type ( + GetFileUrlReq = pb.GetFileUrlReq + GetFileUrlResp = pb.GetFileUrlResp + UploadFileMetadataReq = pb.UploadFileMetadataReq + UploadFileResp = pb.UploadFileResp + + FileService interface { + // 简单上传(适合小文件,或保存元数据) + Upload(ctx context.Context, in *UploadFileMetadataReq, opts ...grpc.CallOption) (*UploadFileResp, error) + // 获取文件访问链接(处理私有文件的鉴权) + GetFileUrl(ctx context.Context, in *GetFileUrlReq, opts ...grpc.CallOption) (*GetFileUrlResp, error) + } + + defaultFileService struct { + cli zrpc.Client + } +) + +func NewFileService(cli zrpc.Client) FileService { + return &defaultFileService{ + cli: cli, + } +} + +// 简单上传(适合小文件,或保存元数据) +func (m *defaultFileService) Upload(ctx context.Context, in *UploadFileMetadataReq, opts ...grpc.CallOption) (*UploadFileResp, error) { + client := pb.NewFileServiceClient(m.cli.Conn()) + return client.Upload(ctx, in, opts...) +} + +// 获取文件访问链接(处理私有文件的鉴权) +func (m *defaultFileService) GetFileUrl(ctx context.Context, in *GetFileUrlReq, opts ...grpc.CallOption) (*GetFileUrlResp, error) { + client := pb.NewFileServiceClient(m.cli.Conn()) + return client.GetFileUrl(ctx, in, opts...) +} diff --git a/app/objectstory/rpc/internal/config/config.go b/app/objectstory/rpc/internal/config/config.go new file mode 100644 index 0000000..c1f85b9 --- /dev/null +++ b/app/objectstory/rpc/internal/config/config.go @@ -0,0 +1,7 @@ +package config + +import "github.com/zeromicro/go-zero/zrpc" + +type Config struct { + zrpc.RpcServerConf +} diff --git a/app/objectstory/rpc/internal/logic/getFileUrlLogic.go b/app/objectstory/rpc/internal/logic/getFileUrlLogic.go new file mode 100644 index 0000000..3509d4f --- /dev/null +++ b/app/objectstory/rpc/internal/logic/getFileUrlLogic.go @@ -0,0 +1,31 @@ +package logic + +import ( + "context" + + "juwan-backend/app/objectstory/rpc/internal/svc" + "juwan-backend/app/objectstory/rpc/pb" + + "github.com/zeromicro/go-zero/core/logx" +) + +type GetFileUrlLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewGetFileUrlLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetFileUrlLogic { + return &GetFileUrlLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +// 获取文件访问链接(处理私有文件的鉴权) +func (l *GetFileUrlLogic) GetFileUrl(in *pb.GetFileUrlReq) (*pb.GetFileUrlResp, error) { + // todo: add your logic here and delete this line + + return &pb.GetFileUrlResp{}, nil +} diff --git a/app/objectstory/rpc/internal/logic/uploadLogic.go b/app/objectstory/rpc/internal/logic/uploadLogic.go new file mode 100644 index 0000000..685075c --- /dev/null +++ b/app/objectstory/rpc/internal/logic/uploadLogic.go @@ -0,0 +1,31 @@ +package logic + +import ( + "context" + + "juwan-backend/app/objectstory/rpc/internal/svc" + "juwan-backend/app/objectstory/rpc/pb" + + "github.com/zeromicro/go-zero/core/logx" +) + +type UploadLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewUploadLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UploadLogic { + return &UploadLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +// 简单上传(适合小文件,或保存元数据) +func (l *UploadLogic) Upload(in *pb.UploadFileMetadataReq) (*pb.UploadFileResp, error) { + // todo: add your logic here and delete this line + + return &pb.UploadFileResp{}, nil +} diff --git a/app/objectstory/rpc/internal/server/fileServiceServer.go b/app/objectstory/rpc/internal/server/fileServiceServer.go new file mode 100644 index 0000000..423acb0 --- /dev/null +++ b/app/objectstory/rpc/internal/server/fileServiceServer.go @@ -0,0 +1,36 @@ +// Code generated by goctl. DO NOT EDIT. +// goctl 1.9.2 +// Source: objectstory.proto + +package server + +import ( + "context" + + "juwan-backend/app/objectstory/rpc/internal/logic" + "juwan-backend/app/objectstory/rpc/internal/svc" + "juwan-backend/app/objectstory/rpc/pb" +) + +type FileServiceServer struct { + svcCtx *svc.ServiceContext + pb.UnimplementedFileServiceServer +} + +func NewFileServiceServer(svcCtx *svc.ServiceContext) *FileServiceServer { + return &FileServiceServer{ + svcCtx: svcCtx, + } +} + +// 简单上传(适合小文件,或保存元数据) +func (s *FileServiceServer) Upload(ctx context.Context, in *pb.UploadFileMetadataReq) (*pb.UploadFileResp, error) { + l := logic.NewUploadLogic(ctx, s.svcCtx) + return l.Upload(in) +} + +// 获取文件访问链接(处理私有文件的鉴权) +func (s *FileServiceServer) GetFileUrl(ctx context.Context, in *pb.GetFileUrlReq) (*pb.GetFileUrlResp, error) { + l := logic.NewGetFileUrlLogic(ctx, s.svcCtx) + return l.GetFileUrl(in) +} diff --git a/app/objectstory/rpc/internal/svc/serviceContext.go b/app/objectstory/rpc/internal/svc/serviceContext.go new file mode 100644 index 0000000..38212dd --- /dev/null +++ b/app/objectstory/rpc/internal/svc/serviceContext.go @@ -0,0 +1,13 @@ +package svc + +import "juwan-backend/app/objectstory/rpc/internal/config" + +type ServiceContext struct { + Config config.Config +} + +func NewServiceContext(c config.Config) *ServiceContext { + return &ServiceContext{ + Config: c, + } +} diff --git a/app/objectstory/rpc/pb/objectstory.pb.go b/app/objectstory/rpc/pb/objectstory.pb.go new file mode 100644 index 0000000..5e4fc52 --- /dev/null +++ b/app/objectstory/rpc/pb/objectstory.pb.go @@ -0,0 +1,326 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc v5.29.6 +// source: objectstory.proto + +package pb + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// 文件上传的元数据信息 +type UploadFileMetadataReq struct { + state protoimpl.MessageState `protogen:"open.v1"` + FileName string `protobuf:"bytes,1,opt,name=fileName,proto3" json:"fileName,omitempty"` + FileSize int64 `protobuf:"varint,2,opt,name=fileSize,proto3" json:"fileSize,omitempty"` + FileType string `protobuf:"bytes,3,opt,name=fileType,proto3" json:"fileType,omitempty"` // avatar, chat, etc. + UserId string `protobuf:"bytes,4,opt,name=userId,proto3" json:"userId,omitempty"` + FileData []byte `protobuf:"bytes,5,opt,name=fileData,proto3" json:"fileData,omitempty"` // 如果文件很小可以直接传,大文件建议API层直接传S3,RPC只传元数据 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UploadFileMetadataReq) Reset() { + *x = UploadFileMetadataReq{} + mi := &file_objectstory_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UploadFileMetadataReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UploadFileMetadataReq) ProtoMessage() {} + +func (x *UploadFileMetadataReq) ProtoReflect() protoreflect.Message { + mi := &file_objectstory_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UploadFileMetadataReq.ProtoReflect.Descriptor instead. +func (*UploadFileMetadataReq) Descriptor() ([]byte, []int) { + return file_objectstory_proto_rawDescGZIP(), []int{0} +} + +func (x *UploadFileMetadataReq) GetFileName() string { + if x != nil { + return x.FileName + } + return "" +} + +func (x *UploadFileMetadataReq) GetFileSize() int64 { + if x != nil { + return x.FileSize + } + return 0 +} + +func (x *UploadFileMetadataReq) GetFileType() string { + if x != nil { + return x.FileType + } + return "" +} + +func (x *UploadFileMetadataReq) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *UploadFileMetadataReq) GetFileData() []byte { + if x != nil { + return x.FileData + } + return nil +} + +type UploadFileResp struct { + state protoimpl.MessageState `protogen:"open.v1"` + Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` + FileId string `protobuf:"bytes,2,opt,name=fileId,proto3" json:"fileId,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UploadFileResp) Reset() { + *x = UploadFileResp{} + mi := &file_objectstory_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UploadFileResp) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UploadFileResp) ProtoMessage() {} + +func (x *UploadFileResp) ProtoReflect() protoreflect.Message { + mi := &file_objectstory_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UploadFileResp.ProtoReflect.Descriptor instead. +func (*UploadFileResp) Descriptor() ([]byte, []int) { + return file_objectstory_proto_rawDescGZIP(), []int{1} +} + +func (x *UploadFileResp) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +func (x *UploadFileResp) GetFileId() string { + if x != nil { + return x.FileId + } + return "" +} + +type GetFileUrlReq struct { + state protoimpl.MessageState `protogen:"open.v1"` + FileId string `protobuf:"bytes,1,opt,name=fileId,proto3" json:"fileId,omitempty"` + UserId string `protobuf:"bytes,2,opt,name=userId,proto3" json:"userId,omitempty"` // 用于鉴权 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetFileUrlReq) Reset() { + *x = GetFileUrlReq{} + mi := &file_objectstory_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetFileUrlReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetFileUrlReq) ProtoMessage() {} + +func (x *GetFileUrlReq) ProtoReflect() protoreflect.Message { + mi := &file_objectstory_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetFileUrlReq.ProtoReflect.Descriptor instead. +func (*GetFileUrlReq) Descriptor() ([]byte, []int) { + return file_objectstory_proto_rawDescGZIP(), []int{2} +} + +func (x *GetFileUrlReq) GetFileId() string { + if x != nil { + return x.FileId + } + return "" +} + +func (x *GetFileUrlReq) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +type GetFileUrlResp struct { + state protoimpl.MessageState `protogen:"open.v1"` + Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` // 可能是带签名的临时 URL + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetFileUrlResp) Reset() { + *x = GetFileUrlResp{} + mi := &file_objectstory_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetFileUrlResp) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetFileUrlResp) ProtoMessage() {} + +func (x *GetFileUrlResp) ProtoReflect() protoreflect.Message { + mi := &file_objectstory_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetFileUrlResp.ProtoReflect.Descriptor instead. +func (*GetFileUrlResp) Descriptor() ([]byte, []int) { + return file_objectstory_proto_rawDescGZIP(), []int{3} +} + +func (x *GetFileUrlResp) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +var File_objectstory_proto protoreflect.FileDescriptor + +const file_objectstory_proto_rawDesc = "" + + "\n" + + "\x11objectstory.proto\x12\x04file\"\x9f\x01\n" + + "\x15UploadFileMetadataReq\x12\x1a\n" + + "\bfileName\x18\x01 \x01(\tR\bfileName\x12\x1a\n" + + "\bfileSize\x18\x02 \x01(\x03R\bfileSize\x12\x1a\n" + + "\bfileType\x18\x03 \x01(\tR\bfileType\x12\x16\n" + + "\x06userId\x18\x04 \x01(\tR\x06userId\x12\x1a\n" + + "\bfileData\x18\x05 \x01(\fR\bfileData\":\n" + + "\x0eUploadFileResp\x12\x10\n" + + "\x03url\x18\x01 \x01(\tR\x03url\x12\x16\n" + + "\x06fileId\x18\x02 \x01(\tR\x06fileId\"?\n" + + "\rGetFileUrlReq\x12\x16\n" + + "\x06fileId\x18\x01 \x01(\tR\x06fileId\x12\x16\n" + + "\x06userId\x18\x02 \x01(\tR\x06userId\"\"\n" + + "\x0eGetFileUrlResp\x12\x10\n" + + "\x03url\x18\x01 \x01(\tR\x03url2\x83\x01\n" + + "\vFileService\x12;\n" + + "\x06Upload\x12\x1b.file.UploadFileMetadataReq\x1a\x14.file.UploadFileResp\x127\n" + + "\n" + + "GetFileUrl\x12\x13.file.GetFileUrlReq\x1a\x14.file.GetFileUrlRespB\x06Z\x04./pbb\x06proto3" + +var ( + file_objectstory_proto_rawDescOnce sync.Once + file_objectstory_proto_rawDescData []byte +) + +func file_objectstory_proto_rawDescGZIP() []byte { + file_objectstory_proto_rawDescOnce.Do(func() { + file_objectstory_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_objectstory_proto_rawDesc), len(file_objectstory_proto_rawDesc))) + }) + return file_objectstory_proto_rawDescData +} + +var file_objectstory_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_objectstory_proto_goTypes = []any{ + (*UploadFileMetadataReq)(nil), // 0: file.UploadFileMetadataReq + (*UploadFileResp)(nil), // 1: file.UploadFileResp + (*GetFileUrlReq)(nil), // 2: file.GetFileUrlReq + (*GetFileUrlResp)(nil), // 3: file.GetFileUrlResp +} +var file_objectstory_proto_depIdxs = []int32{ + 0, // 0: file.FileService.Upload:input_type -> file.UploadFileMetadataReq + 2, // 1: file.FileService.GetFileUrl:input_type -> file.GetFileUrlReq + 1, // 2: file.FileService.Upload:output_type -> file.UploadFileResp + 3, // 3: file.FileService.GetFileUrl:output_type -> file.GetFileUrlResp + 2, // [2:4] is the sub-list for method output_type + 0, // [0:2] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_objectstory_proto_init() } +func file_objectstory_proto_init() { + if File_objectstory_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_objectstory_proto_rawDesc), len(file_objectstory_proto_rawDesc)), + NumEnums: 0, + NumMessages: 4, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_objectstory_proto_goTypes, + DependencyIndexes: file_objectstory_proto_depIdxs, + MessageInfos: file_objectstory_proto_msgTypes, + }.Build() + File_objectstory_proto = out.File + file_objectstory_proto_goTypes = nil + file_objectstory_proto_depIdxs = nil +} diff --git a/app/objectstory/rpc/pb/objectstory_grpc.pb.go b/app/objectstory/rpc/pb/objectstory_grpc.pb.go new file mode 100644 index 0000000..c88400b --- /dev/null +++ b/app/objectstory/rpc/pb/objectstory_grpc.pb.go @@ -0,0 +1,163 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.6.1 +// - protoc v5.29.6 +// source: objectstory.proto + +package pb + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + FileService_Upload_FullMethodName = "/file.FileService/Upload" + FileService_GetFileUrl_FullMethodName = "/file.FileService/GetFileUrl" +) + +// FileServiceClient is the client API for FileService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type FileServiceClient interface { + // 简单上传(适合小文件,或保存元数据) + Upload(ctx context.Context, in *UploadFileMetadataReq, opts ...grpc.CallOption) (*UploadFileResp, error) + // 获取文件访问链接(处理私有文件的鉴权) + GetFileUrl(ctx context.Context, in *GetFileUrlReq, opts ...grpc.CallOption) (*GetFileUrlResp, error) +} + +type fileServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewFileServiceClient(cc grpc.ClientConnInterface) FileServiceClient { + return &fileServiceClient{cc} +} + +func (c *fileServiceClient) Upload(ctx context.Context, in *UploadFileMetadataReq, opts ...grpc.CallOption) (*UploadFileResp, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(UploadFileResp) + err := c.cc.Invoke(ctx, FileService_Upload_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *fileServiceClient) GetFileUrl(ctx context.Context, in *GetFileUrlReq, opts ...grpc.CallOption) (*GetFileUrlResp, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetFileUrlResp) + err := c.cc.Invoke(ctx, FileService_GetFileUrl_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// FileServiceServer is the server API for FileService service. +// All implementations must embed UnimplementedFileServiceServer +// for forward compatibility. +type FileServiceServer interface { + // 简单上传(适合小文件,或保存元数据) + Upload(context.Context, *UploadFileMetadataReq) (*UploadFileResp, error) + // 获取文件访问链接(处理私有文件的鉴权) + GetFileUrl(context.Context, *GetFileUrlReq) (*GetFileUrlResp, error) + mustEmbedUnimplementedFileServiceServer() +} + +// UnimplementedFileServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedFileServiceServer struct{} + +func (UnimplementedFileServiceServer) Upload(context.Context, *UploadFileMetadataReq) (*UploadFileResp, error) { + return nil, status.Error(codes.Unimplemented, "method Upload not implemented") +} +func (UnimplementedFileServiceServer) GetFileUrl(context.Context, *GetFileUrlReq) (*GetFileUrlResp, error) { + return nil, status.Error(codes.Unimplemented, "method GetFileUrl not implemented") +} +func (UnimplementedFileServiceServer) mustEmbedUnimplementedFileServiceServer() {} +func (UnimplementedFileServiceServer) testEmbeddedByValue() {} + +// UnsafeFileServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to FileServiceServer will +// result in compilation errors. +type UnsafeFileServiceServer interface { + mustEmbedUnimplementedFileServiceServer() +} + +func RegisterFileServiceServer(s grpc.ServiceRegistrar, srv FileServiceServer) { + // If the following call panics, it indicates UnimplementedFileServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&FileService_ServiceDesc, srv) +} + +func _FileService_Upload_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UploadFileMetadataReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(FileServiceServer).Upload(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: FileService_Upload_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(FileServiceServer).Upload(ctx, req.(*UploadFileMetadataReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _FileService_GetFileUrl_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetFileUrlReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(FileServiceServer).GetFileUrl(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: FileService_GetFileUrl_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(FileServiceServer).GetFileUrl(ctx, req.(*GetFileUrlReq)) + } + return interceptor(ctx, in, info, handler) +} + +// FileService_ServiceDesc is the grpc.ServiceDesc for FileService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var FileService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "file.FileService", + HandlerType: (*FileServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Upload", + Handler: _FileService_Upload_Handler, + }, + { + MethodName: "GetFileUrl", + Handler: _FileService_GetFileUrl_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "objectstory.proto", +} diff --git a/app/user_verifications/rpc/etc/pb.yaml b/app/user_verifications/rpc/etc/pb.yaml index 8faa7f7..097bc22 100644 --- a/app/user_verifications/rpc/etc/pb.yaml +++ b/app/user_verifications/rpc/etc/pb.yaml @@ -3,8 +3,12 @@ ListenOn: 0.0.0.0:8080 DataSource: "${DB_URI}?sslmode=disable" +UserVeriRpcConf : + Target: k8s://juwan/user_verifications-rpc-svc.juwan:8080 + SnowflakeRpcConf: - Target: k8s://juwan/snowflake-svc:8080 + Target: k8s://juwan/snowflake-svc.juwan:8080 + DB: Master: "postgresql://${PD_USERNAME}:${DB_PASSWORD}@user-db-rw.juwan:${DB_PORT}/${DB_NAME}?sslmode=disable" @@ -20,9 +24,5 @@ CacheConf: Pass: "${REDIS_PASSWORD}" User: "default" -Jwt: - SecretKey: "${JWT_SECRET_KEY}" - Issuer: "juwan-user-rpc" - Log: Level: info diff --git a/app/user_verifications/rpc/internal/config/config.go b/app/user_verifications/rpc/internal/config/config.go index c1f85b9..f951f06 100644 --- a/app/user_verifications/rpc/internal/config/config.go +++ b/app/user_verifications/rpc/internal/config/config.go @@ -1,7 +1,17 @@ package config -import "github.com/zeromicro/go-zero/zrpc" +import ( + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/zrpc" +) type Config struct { zrpc.RpcServerConf + DB struct { + Master string + Slave string + } + CacheConf cache.CacheConf + UserVeriRpcConf zrpc.RpcClientConf + SnowflakeRpcConf zrpc.RpcClientConf } diff --git a/app/user_verifications/rpc/internal/logic/addUserVerificationsLogic.go b/app/user_verifications/rpc/internal/logic/addUserVerificationsLogic.go index a0c859a..28b56b9 100644 --- a/app/user_verifications/rpc/internal/logic/addUserVerificationsLogic.go +++ b/app/user_verifications/rpc/internal/logic/addUserVerificationsLogic.go @@ -2,6 +2,10 @@ package logic import ( "context" + "encoding/json" + "errors" + "juwan-backend/app/snowflake/rpc/snowflake" + "juwan-backend/app/user_verifications/rpc/internal/models/schema" "juwan-backend/app/user_verifications/rpc/internal/svc" "juwan-backend/app/user_verifications/rpc/pb" @@ -25,7 +29,28 @@ func NewAddUserVerificationsLogic(ctx context.Context, svcCtx *svc.ServiceContex // -----------------------userVerifications----------------------- func (l *AddUserVerificationsLogic) AddUserVerifications(in *pb.AddUserVerificationsReq) (*pb.AddUserVerificationsResp, error) { - // todo: add your logic here and delete this line + nextIdResp, err := l.svcCtx.SnowflakeRpc.NextId(l.ctx, &snowflake.NextIdReq{}) + if err != nil { + return nil, err + } + + materials := schema.MaterialStruct{} + err = json.Unmarshal([]byte(in.Materials), &materials) + if err != nil { + logx.Errorf("Unmarshal %v materials failed: %s", in.Materials, err) + return nil, errors.New("bad input materials") + } + + err = l.svcCtx.UserVeriModelRW.Create(). + SetID(nextIdResp.Id). + SetUserID(in.UserId). + SetRole(in.Role). + SetStatus("padding"). + SetMaterials(materials). + Exec(l.ctx) + if err != nil { + return nil, err + } return &pb.AddUserVerificationsResp{}, nil } diff --git a/app/user_verifications/rpc/internal/logic/delUserVerificationsLogic.go b/app/user_verifications/rpc/internal/logic/delUserVerificationsLogic.go index 4209d18..2a0d334 100644 --- a/app/user_verifications/rpc/internal/logic/delUserVerificationsLogic.go +++ b/app/user_verifications/rpc/internal/logic/delUserVerificationsLogic.go @@ -25,6 +25,9 @@ func NewDelUserVerificationsLogic(ctx context.Context, svcCtx *svc.ServiceContex func (l *DelUserVerificationsLogic) DelUserVerifications(in *pb.DelUserVerificationsReq) (*pb.DelUserVerificationsResp, error) { // todo: add your logic here and delete this line - + err := l.svcCtx.UserVeriModelRW.DeleteOneID(in.Id).Exec(l.ctx) + if err != nil { + return nil, err + } return &pb.DelUserVerificationsResp{}, nil } diff --git a/app/user_verifications/rpc/internal/logic/getUserVerificationsByIdLogic.go b/app/user_verifications/rpc/internal/logic/getUserVerificationsByIdLogic.go index 4698785..9765584 100644 --- a/app/user_verifications/rpc/internal/logic/getUserVerificationsByIdLogic.go +++ b/app/user_verifications/rpc/internal/logic/getUserVerificationsByIdLogic.go @@ -2,10 +2,12 @@ package logic import ( "context" + "errors" "juwan-backend/app/user_verifications/rpc/internal/svc" "juwan-backend/app/user_verifications/rpc/pb" + "github.com/jinzhu/copier" "github.com/zeromicro/go-zero/core/logx" ) @@ -24,7 +26,23 @@ func NewGetUserVerificationsByIdLogic(ctx context.Context, svcCtx *svc.ServiceCo } func (l *GetUserVerificationsByIdLogic) GetUserVerificationsById(in *pb.GetUserVerificationsByIdReq) (*pb.GetUserVerificationsByIdResp, error) { - // todo: add your logic here and delete this line + userVerification, err := l.svcCtx.UserVeriModelRO.Get(l.ctx, in.Id) + if err != nil { + logx.Errorf("GetUserVerificationsById err: %v", err) + return nil, errors.New("get VerificationsById err") + } + pbVerification := pb.UserVerifications{} + err = copier.Copy(&pbVerification, userVerification) + if err != nil { + logx.Errorf("copier copy err: %v", err) + return nil, errors.New("copy Verification err") + } + createAt := userVerification.CreatedAt.Unix() + updateAt := userVerification.UpdatedAt.Unix() + pbVerification.CreatedAt = createAt + pbVerification.UpdatedAt = updateAt - return &pb.GetUserVerificationsByIdResp{}, nil + return &pb.GetUserVerificationsByIdResp{ + UserVerifications: &pbVerification, + }, nil } diff --git a/app/user_verifications/rpc/internal/logic/searchUserVerificationsLogic.go b/app/user_verifications/rpc/internal/logic/searchUserVerificationsLogic.go index 3ee5d05..a3507c0 100644 --- a/app/user_verifications/rpc/internal/logic/searchUserVerificationsLogic.go +++ b/app/user_verifications/rpc/internal/logic/searchUserVerificationsLogic.go @@ -2,10 +2,13 @@ package logic import ( "context" - + "errors" + "juwan-backend/app/user_verifications/rpc/internal/models" + "juwan-backend/app/user_verifications/rpc/internal/models/userverifications" "juwan-backend/app/user_verifications/rpc/internal/svc" "juwan-backend/app/user_verifications/rpc/pb" + "github.com/jinzhu/copier" "github.com/zeromicro/go-zero/core/logx" ) @@ -24,7 +27,50 @@ func NewSearchUserVerificationsLogic(ctx context.Context, svcCtx *svc.ServiceCon } func (l *SearchUserVerificationsLogic) SearchUserVerifications(in *pb.SearchUserVerificationsReq) (*pb.SearchUserVerificationsResp, error) { - // todo: add your logic here and delete this line - - return &pb.SearchUserVerificationsResp{}, nil + if in.Limit > 1000 { + logx.Errorf("Limit exceeds max limit: %d", in.Limit) + return nil, errors.New("limit exceeds max limit") + } + verifications, err := l.svcCtx.UserVeriModelRO.Query().Where(userverifications.Or( + userverifications.UserIDEQ(in.UserId), + userverifications.StatusEQ(in.Status), + userverifications.Role(in.Role), + )). + Offset(int(in.Page * in.Limit)). + Limit(int(in.Limit)). + All(l.ctx) + if err != nil { + logx.Errorf("Get all verifications err: %s", err.Error()) + return nil, errors.New("get all verifications err") + } + return &pb.SearchUserVerificationsResp{ + UserVerifications: convertModelUserVerificationsToProto(verifications), + }, nil +} + +func convertModelUserVerificationToProto(modelUserVerification *models.UserVerifications) *pb.UserVerifications { + + if modelUserVerification == nil { + return nil + } + out := &pb.UserVerifications{} + err := copier.Copy(out, modelUserVerification) + if err != nil { + logx.Errorf("copy modelUserVerification err: %s", err.Error()) + return out + } + + out.CreatedAt = modelUserVerification.CreatedAt.Unix() + out.UpdatedAt = modelUserVerification.UpdatedAt.Unix() + return out +} + +func convertModelUserVerificationsToProto(modelUserVerifications []*models.UserVerifications) []*pb.UserVerifications { + + out := make([]*pb.UserVerifications, 0, len(modelUserVerifications)) + for _, modelUserVerification := range modelUserVerifications { + out = append(out, convertModelUserVerificationToProto(modelUserVerification)) + } + + return out } diff --git a/app/user_verifications/rpc/internal/logic/updateUserVerificationsLogic.go b/app/user_verifications/rpc/internal/logic/updateUserVerificationsLogic.go index 15e7fb1..a4439dd 100644 --- a/app/user_verifications/rpc/internal/logic/updateUserVerificationsLogic.go +++ b/app/user_verifications/rpc/internal/logic/updateUserVerificationsLogic.go @@ -2,6 +2,9 @@ package logic import ( "context" + "encoding/json" + "errors" + "juwan-backend/app/user_verifications/rpc/internal/models/schema" "juwan-backend/app/user_verifications/rpc/internal/svc" "juwan-backend/app/user_verifications/rpc/pb" @@ -24,7 +27,31 @@ func NewUpdateUserVerificationsLogic(ctx context.Context, svcCtx *svc.ServiceCon } func (l *UpdateUserVerificationsLogic) UpdateUserVerifications(in *pb.UpdateUserVerificationsReq) (*pb.UpdateUserVerificationsResp, error) { - // todo: add your logic here and delete this line + var materials *schema.MaterialStruct + materials = nil + if in.Materials != nil { + err := json.Unmarshal([]byte(*in.Materials), &materials) + if err != nil { + logx.Errorf("Unmarshal materials failed, err:%v.", err) + return nil, errors.New("bad input materials") + } + if len(materials.GameScreenshots) > 20 { + logx.Errorf("User %v upload oo many game screenshots: %d", in.UserId, len(materials.GameScreenshots)) + return nil, errors.New("too many game screenshots") + } + } + + _, err := l.svcCtx.UserVeriModelRW.UpdateOneID(in.Id). + SetNillableRejectReason(in.RejectReason). + SetNillableMaterials(materials). + SetNillableRole(in.Role). + SetNillableStatus(in.Status). + SetReviewedBy(in.ReviewedBy). + Save(l.ctx) + if err != nil { + logx.Errorf("save user verifications failed, err:%v.", err) + return nil, errors.New("save user verifications failed") + } return &pb.UpdateUserVerificationsResp{}, nil } diff --git a/app/user_verifications/rpc/internal/models/client.go b/app/user_verifications/rpc/internal/models/client.go new file mode 100644 index 0000000..4ef0ebb --- /dev/null +++ b/app/user_verifications/rpc/internal/models/client.go @@ -0,0 +1,341 @@ +// Code generated by ent, DO NOT EDIT. + +package models + +import ( + "context" + "errors" + "fmt" + "log" + "reflect" + + "juwan-backend/app/user_verifications/rpc/internal/models/migrate" + + "juwan-backend/app/user_verifications/rpc/internal/models/userverifications" + + "entgo.io/ent" + "entgo.io/ent/dialect" + "entgo.io/ent/dialect/sql" +) + +// Client is the client that holds all ent builders. +type Client struct { + config + // Schema is the client for creating, migrating and dropping schema. + Schema *migrate.Schema + // UserVerifications is the client for interacting with the UserVerifications builders. + UserVerifications *UserVerificationsClient +} + +// NewClient creates a new client configured with the given options. +func NewClient(opts ...Option) *Client { + client := &Client{config: newConfig(opts...)} + client.init() + return client +} + +func (c *Client) init() { + c.Schema = migrate.NewSchema(c.driver) + c.UserVerifications = NewUserVerificationsClient(c.config) +} + +type ( + // config is the configuration for the client and its builder. + config struct { + // driver used for executing database requests. + driver dialect.Driver + // debug enable a debug logging. + debug bool + // log used for logging on debug mode. + log func(...any) + // hooks to execute on mutations. + hooks *hooks + // interceptors to execute on queries. + inters *inters + } + // Option function to configure the client. + Option func(*config) +) + +// newConfig creates a new config for the client. +func newConfig(opts ...Option) config { + cfg := config{log: log.Println, hooks: &hooks{}, inters: &inters{}} + cfg.options(opts...) + return cfg +} + +// options applies the options on the config object. +func (c *config) options(opts ...Option) { + for _, opt := range opts { + opt(c) + } + if c.debug { + c.driver = dialect.Debug(c.driver, c.log) + } +} + +// Debug enables debug logging on the ent.Driver. +func Debug() Option { + return func(c *config) { + c.debug = true + } +} + +// Log sets the logging function for debug mode. +func Log(fn func(...any)) Option { + return func(c *config) { + c.log = fn + } +} + +// Driver configures the client driver. +func Driver(driver dialect.Driver) Option { + return func(c *config) { + c.driver = driver + } +} + +// Open opens a database/sql.DB specified by the driver name and +// the data source name, and returns a new client attached to it. +// Optional parameters can be added for configuring the client. +func Open(driverName, dataSourceName string, options ...Option) (*Client, error) { + switch driverName { + case dialect.MySQL, dialect.Postgres, dialect.SQLite: + drv, err := sql.Open(driverName, dataSourceName) + if err != nil { + return nil, err + } + return NewClient(append(options, Driver(drv))...), nil + default: + return nil, fmt.Errorf("unsupported driver: %q", driverName) + } +} + +// ErrTxStarted is returned when trying to start a new transaction from a transactional client. +var ErrTxStarted = errors.New("models: cannot start a transaction within a transaction") + +// Tx returns a new transactional client. The provided context +// is used until the transaction is committed or rolled back. +func (c *Client) Tx(ctx context.Context) (*Tx, error) { + if _, ok := c.driver.(*txDriver); ok { + return nil, ErrTxStarted + } + tx, err := newTx(ctx, c.driver) + if err != nil { + return nil, fmt.Errorf("models: starting a transaction: %w", err) + } + cfg := c.config + cfg.driver = tx + return &Tx{ + ctx: ctx, + config: cfg, + UserVerifications: NewUserVerificationsClient(cfg), + }, nil +} + +// BeginTx returns a transactional client with specified options. +func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) { + if _, ok := c.driver.(*txDriver); ok { + return nil, errors.New("ent: cannot start a transaction within a transaction") + } + tx, err := c.driver.(interface { + BeginTx(context.Context, *sql.TxOptions) (dialect.Tx, error) + }).BeginTx(ctx, opts) + if err != nil { + return nil, fmt.Errorf("ent: starting a transaction: %w", err) + } + cfg := c.config + cfg.driver = &txDriver{tx: tx, drv: c.driver} + return &Tx{ + ctx: ctx, + config: cfg, + UserVerifications: NewUserVerificationsClient(cfg), + }, nil +} + +// Debug returns a new debug-client. It's used to get verbose logging on specific operations. +// +// client.Debug(). +// UserVerifications. +// Query(). +// Count(ctx) +func (c *Client) Debug() *Client { + if c.debug { + return c + } + cfg := c.config + cfg.driver = dialect.Debug(c.driver, c.log) + client := &Client{config: cfg} + client.init() + return client +} + +// Close closes the database connection and prevents new queries from starting. +func (c *Client) Close() error { + return c.driver.Close() +} + +// Use adds the mutation hooks to all the entity clients. +// In order to add hooks to a specific client, call: `client.Node.Use(...)`. +func (c *Client) Use(hooks ...Hook) { + c.UserVerifications.Use(hooks...) +} + +// Intercept adds the query interceptors to all the entity clients. +// In order to add interceptors to a specific client, call: `client.Node.Intercept(...)`. +func (c *Client) Intercept(interceptors ...Interceptor) { + c.UserVerifications.Intercept(interceptors...) +} + +// Mutate implements the ent.Mutator interface. +func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) { + switch m := m.(type) { + case *UserVerificationsMutation: + return c.UserVerifications.mutate(ctx, m) + default: + return nil, fmt.Errorf("models: unknown mutation type %T", m) + } +} + +// UserVerificationsClient is a client for the UserVerifications schema. +type UserVerificationsClient struct { + config +} + +// NewUserVerificationsClient returns a client for the UserVerifications from the given config. +func NewUserVerificationsClient(c config) *UserVerificationsClient { + return &UserVerificationsClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `userverifications.Hooks(f(g(h())))`. +func (c *UserVerificationsClient) Use(hooks ...Hook) { + c.hooks.UserVerifications = append(c.hooks.UserVerifications, hooks...) +} + +// Intercept adds a list of query interceptors to the interceptors stack. +// A call to `Intercept(f, g, h)` equals to `userverifications.Intercept(f(g(h())))`. +func (c *UserVerificationsClient) Intercept(interceptors ...Interceptor) { + c.inters.UserVerifications = append(c.inters.UserVerifications, interceptors...) +} + +// Create returns a builder for creating a UserVerifications entity. +func (c *UserVerificationsClient) Create() *UserVerificationsCreate { + mutation := newUserVerificationsMutation(c.config, OpCreate) + return &UserVerificationsCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of UserVerifications entities. +func (c *UserVerificationsClient) CreateBulk(builders ...*UserVerificationsCreate) *UserVerificationsCreateBulk { + return &UserVerificationsCreateBulk{config: c.config, builders: builders} +} + +// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates +// a builder and applies setFunc on it. +func (c *UserVerificationsClient) MapCreateBulk(slice any, setFunc func(*UserVerificationsCreate, int)) *UserVerificationsCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &UserVerificationsCreateBulk{err: fmt.Errorf("calling to UserVerificationsClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*UserVerificationsCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &UserVerificationsCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for UserVerifications. +func (c *UserVerificationsClient) Update() *UserVerificationsUpdate { + mutation := newUserVerificationsMutation(c.config, OpUpdate) + return &UserVerificationsUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *UserVerificationsClient) UpdateOne(_m *UserVerifications) *UserVerificationsUpdateOne { + mutation := newUserVerificationsMutation(c.config, OpUpdateOne, withUserVerifications(_m)) + return &UserVerificationsUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOneID returns an update builder for the given id. +func (c *UserVerificationsClient) UpdateOneID(id int64) *UserVerificationsUpdateOne { + mutation := newUserVerificationsMutation(c.config, OpUpdateOne, withUserVerificationsID(id)) + return &UserVerificationsUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for UserVerifications. +func (c *UserVerificationsClient) Delete() *UserVerificationsDelete { + mutation := newUserVerificationsMutation(c.config, OpDelete) + return &UserVerificationsDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// DeleteOne returns a builder for deleting the given entity. +func (c *UserVerificationsClient) DeleteOne(_m *UserVerifications) *UserVerificationsDeleteOne { + return c.DeleteOneID(_m.ID) +} + +// DeleteOneID returns a builder for deleting the given entity by its id. +func (c *UserVerificationsClient) DeleteOneID(id int64) *UserVerificationsDeleteOne { + builder := c.Delete().Where(userverifications.ID(id)) + builder.mutation.id = &id + builder.mutation.op = OpDeleteOne + return &UserVerificationsDeleteOne{builder} +} + +// Query returns a query builder for UserVerifications. +func (c *UserVerificationsClient) Query() *UserVerificationsQuery { + return &UserVerificationsQuery{ + config: c.config, + ctx: &QueryContext{Type: TypeUserVerifications}, + inters: c.Interceptors(), + } +} + +// Get returns a UserVerifications entity by its id. +func (c *UserVerificationsClient) Get(ctx context.Context, id int64) (*UserVerifications, error) { + return c.Query().Where(userverifications.ID(id)).Only(ctx) +} + +// GetX is like Get, but panics if an error occurs. +func (c *UserVerificationsClient) GetX(ctx context.Context, id int64) *UserVerifications { + obj, err := c.Get(ctx, id) + if err != nil { + panic(err) + } + return obj +} + +// Hooks returns the client hooks. +func (c *UserVerificationsClient) Hooks() []Hook { + return c.hooks.UserVerifications +} + +// Interceptors returns the client interceptors. +func (c *UserVerificationsClient) Interceptors() []Interceptor { + return c.inters.UserVerifications +} + +func (c *UserVerificationsClient) mutate(ctx context.Context, m *UserVerificationsMutation) (Value, error) { + switch m.Op() { + case OpCreate: + return (&UserVerificationsCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdate: + return (&UserVerificationsUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdateOne: + return (&UserVerificationsUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpDelete, OpDeleteOne: + return (&UserVerificationsDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) + default: + return nil, fmt.Errorf("models: unknown UserVerifications mutation op: %q", m.Op()) + } +} + +// hooks and interceptors per client, for fast access. +type ( + hooks struct { + UserVerifications []ent.Hook + } + inters struct { + UserVerifications []ent.Interceptor + } +) diff --git a/app/user_verifications/rpc/internal/models/ent.go b/app/user_verifications/rpc/internal/models/ent.go new file mode 100644 index 0000000..9d6ac26 --- /dev/null +++ b/app/user_verifications/rpc/internal/models/ent.go @@ -0,0 +1,608 @@ +// Code generated by ent, DO NOT EDIT. + +package models + +import ( + "context" + "errors" + "fmt" + "juwan-backend/app/user_verifications/rpc/internal/models/userverifications" + "reflect" + "sync" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" +) + +// ent aliases to avoid import conflicts in user's code. +type ( + Op = ent.Op + Hook = ent.Hook + Value = ent.Value + Query = ent.Query + QueryContext = ent.QueryContext + Querier = ent.Querier + QuerierFunc = ent.QuerierFunc + Interceptor = ent.Interceptor + InterceptFunc = ent.InterceptFunc + Traverser = ent.Traverser + TraverseFunc = ent.TraverseFunc + Policy = ent.Policy + Mutator = ent.Mutator + Mutation = ent.Mutation + MutateFunc = ent.MutateFunc +) + +type clientCtxKey struct{} + +// FromContext returns a Client stored inside a context, or nil if there isn't one. +func FromContext(ctx context.Context) *Client { + c, _ := ctx.Value(clientCtxKey{}).(*Client) + return c +} + +// NewContext returns a new context with the given Client attached. +func NewContext(parent context.Context, c *Client) context.Context { + return context.WithValue(parent, clientCtxKey{}, c) +} + +type txCtxKey struct{} + +// TxFromContext returns a Tx stored inside a context, or nil if there isn't one. +func TxFromContext(ctx context.Context) *Tx { + tx, _ := ctx.Value(txCtxKey{}).(*Tx) + return tx +} + +// NewTxContext returns a new context with the given Tx attached. +func NewTxContext(parent context.Context, tx *Tx) context.Context { + return context.WithValue(parent, txCtxKey{}, tx) +} + +// OrderFunc applies an ordering on the sql selector. +// Deprecated: Use Asc/Desc functions or the package builders instead. +type OrderFunc func(*sql.Selector) + +var ( + initCheck sync.Once + columnCheck sql.ColumnCheck +) + +// checkColumn checks if the column exists in the given table. +func checkColumn(t, c string) error { + initCheck.Do(func() { + columnCheck = sql.NewColumnCheck(map[string]func(string) bool{ + userverifications.Table: userverifications.ValidColumn, + }) + }) + return columnCheck(t, c) +} + +// Asc applies the given fields in ASC order. +func Asc(fields ...string) func(*sql.Selector) { + return func(s *sql.Selector) { + for _, f := range fields { + if err := checkColumn(s.TableName(), f); err != nil { + s.AddError(&ValidationError{Name: f, err: fmt.Errorf("models: %w", err)}) + } + s.OrderBy(sql.Asc(s.C(f))) + } + } +} + +// Desc applies the given fields in DESC order. +func Desc(fields ...string) func(*sql.Selector) { + return func(s *sql.Selector) { + for _, f := range fields { + if err := checkColumn(s.TableName(), f); err != nil { + s.AddError(&ValidationError{Name: f, err: fmt.Errorf("models: %w", err)}) + } + s.OrderBy(sql.Desc(s.C(f))) + } + } +} + +// AggregateFunc applies an aggregation step on the group-by traversal/selector. +type AggregateFunc func(*sql.Selector) string + +// As is a pseudo aggregation function for renaming another other functions with custom names. For example: +// +// GroupBy(field1, field2). +// Aggregate(models.As(models.Sum(field1), "sum_field1"), (models.As(models.Sum(field2), "sum_field2")). +// Scan(ctx, &v) +func As(fn AggregateFunc, end string) AggregateFunc { + return func(s *sql.Selector) string { + return sql.As(fn(s), end) + } +} + +// Count applies the "count" aggregation function on each group. +func Count() AggregateFunc { + return func(s *sql.Selector) string { + return sql.Count("*") + } +} + +// Max applies the "max" aggregation function on the given field of each group. +func Max(field string) AggregateFunc { + return func(s *sql.Selector) string { + if err := checkColumn(s.TableName(), field); err != nil { + s.AddError(&ValidationError{Name: field, err: fmt.Errorf("models: %w", err)}) + return "" + } + return sql.Max(s.C(field)) + } +} + +// Mean applies the "mean" aggregation function on the given field of each group. +func Mean(field string) AggregateFunc { + return func(s *sql.Selector) string { + if err := checkColumn(s.TableName(), field); err != nil { + s.AddError(&ValidationError{Name: field, err: fmt.Errorf("models: %w", err)}) + return "" + } + return sql.Avg(s.C(field)) + } +} + +// Min applies the "min" aggregation function on the given field of each group. +func Min(field string) AggregateFunc { + return func(s *sql.Selector) string { + if err := checkColumn(s.TableName(), field); err != nil { + s.AddError(&ValidationError{Name: field, err: fmt.Errorf("models: %w", err)}) + return "" + } + return sql.Min(s.C(field)) + } +} + +// Sum applies the "sum" aggregation function on the given field of each group. +func Sum(field string) AggregateFunc { + return func(s *sql.Selector) string { + if err := checkColumn(s.TableName(), field); err != nil { + s.AddError(&ValidationError{Name: field, err: fmt.Errorf("models: %w", err)}) + return "" + } + return sql.Sum(s.C(field)) + } +} + +// ValidationError returns when validating a field or edge fails. +type ValidationError struct { + Name string // Field or edge name. + err error +} + +// Error implements the error interface. +func (e *ValidationError) Error() string { + return e.err.Error() +} + +// Unwrap implements the errors.Wrapper interface. +func (e *ValidationError) Unwrap() error { + return e.err +} + +// IsValidationError returns a boolean indicating whether the error is a validation error. +func IsValidationError(err error) bool { + if err == nil { + return false + } + var e *ValidationError + return errors.As(err, &e) +} + +// NotFoundError returns when trying to fetch a specific entity and it was not found in the database. +type NotFoundError struct { + label string +} + +// Error implements the error interface. +func (e *NotFoundError) Error() string { + return "models: " + e.label + " not found" +} + +// IsNotFound returns a boolean indicating whether the error is a not found error. +func IsNotFound(err error) bool { + if err == nil { + return false + } + var e *NotFoundError + return errors.As(err, &e) +} + +// MaskNotFound masks not found error. +func MaskNotFound(err error) error { + if IsNotFound(err) { + return nil + } + return err +} + +// NotSingularError returns when trying to fetch a singular entity and more then one was found in the database. +type NotSingularError struct { + label string +} + +// Error implements the error interface. +func (e *NotSingularError) Error() string { + return "models: " + e.label + " not singular" +} + +// IsNotSingular returns a boolean indicating whether the error is a not singular error. +func IsNotSingular(err error) bool { + if err == nil { + return false + } + var e *NotSingularError + return errors.As(err, &e) +} + +// NotLoadedError returns when trying to get a node that was not loaded by the query. +type NotLoadedError struct { + edge string +} + +// Error implements the error interface. +func (e *NotLoadedError) Error() string { + return "models: " + e.edge + " edge was not loaded" +} + +// IsNotLoaded returns a boolean indicating whether the error is a not loaded error. +func IsNotLoaded(err error) bool { + if err == nil { + return false + } + var e *NotLoadedError + return errors.As(err, &e) +} + +// ConstraintError returns when trying to create/update one or more entities and +// one or more of their constraints failed. For example, violation of edge or +// field uniqueness. +type ConstraintError struct { + msg string + wrap error +} + +// Error implements the error interface. +func (e ConstraintError) Error() string { + return "models: constraint failed: " + e.msg +} + +// Unwrap implements the errors.Wrapper interface. +func (e *ConstraintError) Unwrap() error { + return e.wrap +} + +// IsConstraintError returns a boolean indicating whether the error is a constraint failure. +func IsConstraintError(err error) bool { + if err == nil { + return false + } + var e *ConstraintError + return errors.As(err, &e) +} + +// selector embedded by the different Select/GroupBy builders. +type selector struct { + label string + flds *[]string + fns []AggregateFunc + scan func(context.Context, any) error +} + +// ScanX is like Scan, but panics if an error occurs. +func (s *selector) ScanX(ctx context.Context, v any) { + if err := s.scan(ctx, v); err != nil { + panic(err) + } +} + +// Strings returns list of strings from a selector. It is only allowed when selecting one field. +func (s *selector) Strings(ctx context.Context) ([]string, error) { + if len(*s.flds) > 1 { + return nil, errors.New("models: Strings is not achievable when selecting more than 1 field") + } + var v []string + if err := s.scan(ctx, &v); err != nil { + return nil, err + } + return v, nil +} + +// StringsX is like Strings, but panics if an error occurs. +func (s *selector) StringsX(ctx context.Context) []string { + v, err := s.Strings(ctx) + if err != nil { + panic(err) + } + return v +} + +// String returns a single string from a selector. It is only allowed when selecting one field. +func (s *selector) String(ctx context.Context) (_ string, err error) { + var v []string + if v, err = s.Strings(ctx); err != nil { + return + } + switch len(v) { + case 1: + return v[0], nil + case 0: + err = &NotFoundError{s.label} + default: + err = fmt.Errorf("models: Strings returned %d results when one was expected", len(v)) + } + return +} + +// StringX is like String, but panics if an error occurs. +func (s *selector) StringX(ctx context.Context) string { + v, err := s.String(ctx) + if err != nil { + panic(err) + } + return v +} + +// Ints returns list of ints from a selector. It is only allowed when selecting one field. +func (s *selector) Ints(ctx context.Context) ([]int, error) { + if len(*s.flds) > 1 { + return nil, errors.New("models: Ints is not achievable when selecting more than 1 field") + } + var v []int + if err := s.scan(ctx, &v); err != nil { + return nil, err + } + return v, nil +} + +// IntsX is like Ints, but panics if an error occurs. +func (s *selector) IntsX(ctx context.Context) []int { + v, err := s.Ints(ctx) + if err != nil { + panic(err) + } + return v +} + +// Int returns a single int from a selector. It is only allowed when selecting one field. +func (s *selector) Int(ctx context.Context) (_ int, err error) { + var v []int + if v, err = s.Ints(ctx); err != nil { + return + } + switch len(v) { + case 1: + return v[0], nil + case 0: + err = &NotFoundError{s.label} + default: + err = fmt.Errorf("models: Ints returned %d results when one was expected", len(v)) + } + return +} + +// IntX is like Int, but panics if an error occurs. +func (s *selector) IntX(ctx context.Context) int { + v, err := s.Int(ctx) + if err != nil { + panic(err) + } + return v +} + +// Float64s returns list of float64s from a selector. It is only allowed when selecting one field. +func (s *selector) Float64s(ctx context.Context) ([]float64, error) { + if len(*s.flds) > 1 { + return nil, errors.New("models: Float64s is not achievable when selecting more than 1 field") + } + var v []float64 + if err := s.scan(ctx, &v); err != nil { + return nil, err + } + return v, nil +} + +// Float64sX is like Float64s, but panics if an error occurs. +func (s *selector) Float64sX(ctx context.Context) []float64 { + v, err := s.Float64s(ctx) + if err != nil { + panic(err) + } + return v +} + +// Float64 returns a single float64 from a selector. It is only allowed when selecting one field. +func (s *selector) Float64(ctx context.Context) (_ float64, err error) { + var v []float64 + if v, err = s.Float64s(ctx); err != nil { + return + } + switch len(v) { + case 1: + return v[0], nil + case 0: + err = &NotFoundError{s.label} + default: + err = fmt.Errorf("models: Float64s returned %d results when one was expected", len(v)) + } + return +} + +// Float64X is like Float64, but panics if an error occurs. +func (s *selector) Float64X(ctx context.Context) float64 { + v, err := s.Float64(ctx) + if err != nil { + panic(err) + } + return v +} + +// Bools returns list of bools from a selector. It is only allowed when selecting one field. +func (s *selector) Bools(ctx context.Context) ([]bool, error) { + if len(*s.flds) > 1 { + return nil, errors.New("models: Bools is not achievable when selecting more than 1 field") + } + var v []bool + if err := s.scan(ctx, &v); err != nil { + return nil, err + } + return v, nil +} + +// BoolsX is like Bools, but panics if an error occurs. +func (s *selector) BoolsX(ctx context.Context) []bool { + v, err := s.Bools(ctx) + if err != nil { + panic(err) + } + return v +} + +// Bool returns a single bool from a selector. It is only allowed when selecting one field. +func (s *selector) Bool(ctx context.Context) (_ bool, err error) { + var v []bool + if v, err = s.Bools(ctx); err != nil { + return + } + switch len(v) { + case 1: + return v[0], nil + case 0: + err = &NotFoundError{s.label} + default: + err = fmt.Errorf("models: Bools returned %d results when one was expected", len(v)) + } + return +} + +// BoolX is like Bool, but panics if an error occurs. +func (s *selector) BoolX(ctx context.Context) bool { + v, err := s.Bool(ctx) + if err != nil { + panic(err) + } + return v +} + +// withHooks invokes the builder operation with the given hooks, if any. +func withHooks[V Value, M any, PM interface { + *M + Mutation +}](ctx context.Context, exec func(context.Context) (V, error), mutation PM, hooks []Hook) (value V, err error) { + if len(hooks) == 0 { + return exec(ctx) + } + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutationT, ok := any(m).(PM) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + // Set the mutation to the builder. + *mutation = *mutationT + return exec(ctx) + }) + for i := len(hooks) - 1; i >= 0; i-- { + if hooks[i] == nil { + return value, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)") + } + mut = hooks[i](mut) + } + v, err := mut.Mutate(ctx, mutation) + if err != nil { + return value, err + } + nv, ok := v.(V) + if !ok { + return value, fmt.Errorf("unexpected node type %T returned from %T", v, mutation) + } + return nv, nil +} + +// setContextOp returns a new context with the given QueryContext attached (including its op) in case it does not exist. +func setContextOp(ctx context.Context, qc *QueryContext, op string) context.Context { + if ent.QueryFromContext(ctx) == nil { + qc.Op = op + ctx = ent.NewQueryContext(ctx, qc) + } + return ctx +} + +func querierAll[V Value, Q interface { + sqlAll(context.Context, ...queryHook) (V, error) +}]() Querier { + return QuerierFunc(func(ctx context.Context, q Query) (Value, error) { + query, ok := q.(Q) + if !ok { + return nil, fmt.Errorf("unexpected query type %T", q) + } + return query.sqlAll(ctx) + }) +} + +func querierCount[Q interface { + sqlCount(context.Context) (int, error) +}]() Querier { + return QuerierFunc(func(ctx context.Context, q Query) (Value, error) { + query, ok := q.(Q) + if !ok { + return nil, fmt.Errorf("unexpected query type %T", q) + } + return query.sqlCount(ctx) + }) +} + +func withInterceptors[V Value](ctx context.Context, q Query, qr Querier, inters []Interceptor) (v V, err error) { + for i := len(inters) - 1; i >= 0; i-- { + qr = inters[i].Intercept(qr) + } + rv, err := qr.Query(ctx, q) + if err != nil { + return v, err + } + vt, ok := rv.(V) + if !ok { + return v, fmt.Errorf("unexpected type %T returned from %T. expected type: %T", vt, q, v) + } + return vt, nil +} + +func scanWithInterceptors[Q1 ent.Query, Q2 interface { + sqlScan(context.Context, Q1, any) error +}](ctx context.Context, rootQuery Q1, selectOrGroup Q2, inters []Interceptor, v any) error { + rv := reflect.ValueOf(v) + var qr Querier = QuerierFunc(func(ctx context.Context, q Query) (Value, error) { + query, ok := q.(Q1) + if !ok { + return nil, fmt.Errorf("unexpected query type %T", q) + } + if err := selectOrGroup.sqlScan(ctx, query, v); err != nil { + return nil, err + } + if k := rv.Kind(); k == reflect.Pointer && rv.Elem().CanInterface() { + return rv.Elem().Interface(), nil + } + return v, nil + }) + for i := len(inters) - 1; i >= 0; i-- { + qr = inters[i].Intercept(qr) + } + vv, err := qr.Query(ctx, rootQuery) + if err != nil { + return err + } + switch rv2 := reflect.ValueOf(vv); { + case rv.IsNil(), rv2.IsNil(), rv.Kind() != reflect.Pointer: + case rv.Type() == rv2.Type(): + rv.Elem().Set(rv2.Elem()) + case rv.Elem().Type() == rv2.Type(): + rv.Elem().Set(rv2) + } + return nil +} + +// queryHook describes an internal hook for the different sqlAll methods. +type queryHook func(context.Context, *sqlgraph.QuerySpec) diff --git a/app/user_verifications/rpc/internal/models/enttest/enttest.go b/app/user_verifications/rpc/internal/models/enttest/enttest.go new file mode 100644 index 0000000..b594991 --- /dev/null +++ b/app/user_verifications/rpc/internal/models/enttest/enttest.go @@ -0,0 +1,85 @@ +// Code generated by ent, DO NOT EDIT. + +package enttest + +import ( + "context" + + "juwan-backend/app/user_verifications/rpc/internal/models" + // required by schema hooks. + _ "juwan-backend/app/user_verifications/rpc/internal/models/runtime" + + "juwan-backend/app/user_verifications/rpc/internal/models/migrate" + + "entgo.io/ent/dialect/sql/schema" +) + +type ( + // TestingT is the interface that is shared between + // testing.T and testing.B and used by enttest. + TestingT interface { + FailNow() + Error(...any) + } + + // Option configures client creation. + Option func(*options) + + options struct { + opts []models.Option + migrateOpts []schema.MigrateOption + } +) + +// WithOptions forwards options to client creation. +func WithOptions(opts ...models.Option) Option { + return func(o *options) { + o.opts = append(o.opts, opts...) + } +} + +// WithMigrateOptions forwards options to auto migration. +func WithMigrateOptions(opts ...schema.MigrateOption) Option { + return func(o *options) { + o.migrateOpts = append(o.migrateOpts, opts...) + } +} + +func newOptions(opts []Option) *options { + o := &options{} + for _, opt := range opts { + opt(o) + } + return o +} + +// Open calls models.Open and auto-run migration. +func Open(t TestingT, driverName, dataSourceName string, opts ...Option) *models.Client { + o := newOptions(opts) + c, err := models.Open(driverName, dataSourceName, o.opts...) + if err != nil { + t.Error(err) + t.FailNow() + } + migrateSchema(t, c, o) + return c +} + +// NewClient calls models.NewClient and auto-run migration. +func NewClient(t TestingT, opts ...Option) *models.Client { + o := newOptions(opts) + c := models.NewClient(o.opts...) + migrateSchema(t, c, o) + return c +} +func migrateSchema(t TestingT, c *models.Client, o *options) { + tables, err := schema.CopyTables(migrate.Tables) + if err != nil { + t.Error(err) + t.FailNow() + } + if err := migrate.Create(context.Background(), c.Schema, tables, o.migrateOpts...); err != nil { + t.Error(err) + t.FailNow() + } +} diff --git a/app/user_verifications/rpc/internal/models/hook/hook.go b/app/user_verifications/rpc/internal/models/hook/hook.go new file mode 100644 index 0000000..b61436d --- /dev/null +++ b/app/user_verifications/rpc/internal/models/hook/hook.go @@ -0,0 +1,198 @@ +// Code generated by ent, DO NOT EDIT. + +package hook + +import ( + "context" + "fmt" + "juwan-backend/app/user_verifications/rpc/internal/models" +) + +// The UserVerificationsFunc type is an adapter to allow the use of ordinary +// function as UserVerifications mutator. +type UserVerificationsFunc func(context.Context, *models.UserVerificationsMutation) (models.Value, error) + +// Mutate calls f(ctx, m). +func (f UserVerificationsFunc) Mutate(ctx context.Context, m models.Mutation) (models.Value, error) { + if mv, ok := m.(*models.UserVerificationsMutation); ok { + return f(ctx, mv) + } + return nil, fmt.Errorf("unexpected mutation type %T. expect *models.UserVerificationsMutation", m) +} + +// Condition is a hook condition function. +type Condition func(context.Context, models.Mutation) bool + +// And groups conditions with the AND operator. +func And(first, second Condition, rest ...Condition) Condition { + return func(ctx context.Context, m models.Mutation) bool { + if !first(ctx, m) || !second(ctx, m) { + return false + } + for _, cond := range rest { + if !cond(ctx, m) { + return false + } + } + return true + } +} + +// Or groups conditions with the OR operator. +func Or(first, second Condition, rest ...Condition) Condition { + return func(ctx context.Context, m models.Mutation) bool { + if first(ctx, m) || second(ctx, m) { + return true + } + for _, cond := range rest { + if cond(ctx, m) { + return true + } + } + return false + } +} + +// Not negates a given condition. +func Not(cond Condition) Condition { + return func(ctx context.Context, m models.Mutation) bool { + return !cond(ctx, m) + } +} + +// HasOp is a condition testing mutation operation. +func HasOp(op models.Op) Condition { + return func(_ context.Context, m models.Mutation) bool { + return m.Op().Is(op) + } +} + +// HasAddedFields is a condition validating `.AddedField` on fields. +func HasAddedFields(field string, fields ...string) Condition { + return func(_ context.Context, m models.Mutation) bool { + if _, exists := m.AddedField(field); !exists { + return false + } + for _, field := range fields { + if _, exists := m.AddedField(field); !exists { + return false + } + } + return true + } +} + +// HasClearedFields is a condition validating `.FieldCleared` on fields. +func HasClearedFields(field string, fields ...string) Condition { + return func(_ context.Context, m models.Mutation) bool { + if exists := m.FieldCleared(field); !exists { + return false + } + for _, field := range fields { + if exists := m.FieldCleared(field); !exists { + return false + } + } + return true + } +} + +// HasFields is a condition validating `.Field` on fields. +func HasFields(field string, fields ...string) Condition { + return func(_ context.Context, m models.Mutation) bool { + if _, exists := m.Field(field); !exists { + return false + } + for _, field := range fields { + if _, exists := m.Field(field); !exists { + return false + } + } + return true + } +} + +// If executes the given hook under condition. +// +// hook.If(ComputeAverage, And(HasFields(...), HasAddedFields(...))) +func If(hk models.Hook, cond Condition) models.Hook { + return func(next models.Mutator) models.Mutator { + return models.MutateFunc(func(ctx context.Context, m models.Mutation) (models.Value, error) { + if cond(ctx, m) { + return hk(next).Mutate(ctx, m) + } + return next.Mutate(ctx, m) + }) + } +} + +// On executes the given hook only for the given operation. +// +// hook.On(Log, models.Delete|models.Create) +func On(hk models.Hook, op models.Op) models.Hook { + return If(hk, HasOp(op)) +} + +// Unless skips the given hook only for the given operation. +// +// hook.Unless(Log, models.Update|models.UpdateOne) +func Unless(hk models.Hook, op models.Op) models.Hook { + return If(hk, Not(HasOp(op))) +} + +// FixedError is a hook returning a fixed error. +func FixedError(err error) models.Hook { + return func(models.Mutator) models.Mutator { + return models.MutateFunc(func(context.Context, models.Mutation) (models.Value, error) { + return nil, err + }) + } +} + +// Reject returns a hook that rejects all operations that match op. +// +// func (T) Hooks() []models.Hook { +// return []models.Hook{ +// Reject(models.Delete|models.Update), +// } +// } +func Reject(op models.Op) models.Hook { + hk := FixedError(fmt.Errorf("%s operation is not allowed", op)) + return On(hk, op) +} + +// Chain acts as a list of hooks and is effectively immutable. +// Once created, it will always hold the same set of hooks in the same order. +type Chain struct { + hooks []models.Hook +} + +// NewChain creates a new chain of hooks. +func NewChain(hooks ...models.Hook) Chain { + return Chain{append([]models.Hook(nil), hooks...)} +} + +// Hook chains the list of hooks and returns the final hook. +func (c Chain) Hook() models.Hook { + return func(mutator models.Mutator) models.Mutator { + for i := len(c.hooks) - 1; i >= 0; i-- { + mutator = c.hooks[i](mutator) + } + return mutator + } +} + +// Append extends a chain, adding the specified hook +// as the last ones in the mutation flow. +func (c Chain) Append(hooks ...models.Hook) Chain { + newHooks := make([]models.Hook, 0, len(c.hooks)+len(hooks)) + newHooks = append(newHooks, c.hooks...) + newHooks = append(newHooks, hooks...) + return Chain{newHooks} +} + +// Extend extends a chain, adding the specified chain +// as the last ones in the mutation flow. +func (c Chain) Extend(chain Chain) Chain { + return c.Append(chain.hooks...) +} diff --git a/app/user_verifications/rpc/internal/models/migrate/migrate.go b/app/user_verifications/rpc/internal/models/migrate/migrate.go new file mode 100644 index 0000000..1956a6b --- /dev/null +++ b/app/user_verifications/rpc/internal/models/migrate/migrate.go @@ -0,0 +1,64 @@ +// Code generated by ent, DO NOT EDIT. + +package migrate + +import ( + "context" + "fmt" + "io" + + "entgo.io/ent/dialect" + "entgo.io/ent/dialect/sql/schema" +) + +var ( + // WithGlobalUniqueID sets the universal ids options to the migration. + // If this option is enabled, ent migration will allocate a 1<<32 range + // for the ids of each entity (table). + // Note that this option cannot be applied on tables that already exist. + WithGlobalUniqueID = schema.WithGlobalUniqueID + // WithDropColumn sets the drop column option to the migration. + // If this option is enabled, ent migration will drop old columns + // that were used for both fields and edges. This defaults to false. + WithDropColumn = schema.WithDropColumn + // WithDropIndex sets the drop index option to the migration. + // If this option is enabled, ent migration will drop old indexes + // that were defined in the schema. This defaults to false. + // Note that unique constraints are defined using `UNIQUE INDEX`, + // and therefore, it's recommended to enable this option to get more + // flexibility in the schema changes. + WithDropIndex = schema.WithDropIndex + // WithForeignKeys enables creating foreign-key in schema DDL. This defaults to true. + WithForeignKeys = schema.WithForeignKeys +) + +// Schema is the API for creating, migrating and dropping a schema. +type Schema struct { + drv dialect.Driver +} + +// NewSchema creates a new schema client. +func NewSchema(drv dialect.Driver) *Schema { return &Schema{drv: drv} } + +// Create creates all schema resources. +func (s *Schema) Create(ctx context.Context, opts ...schema.MigrateOption) error { + return Create(ctx, s, Tables, opts...) +} + +// Create creates all table resources using the given schema driver. +func Create(ctx context.Context, s *Schema, tables []*schema.Table, opts ...schema.MigrateOption) error { + migrate, err := schema.NewMigrate(s.drv, opts...) + if err != nil { + return fmt.Errorf("ent/migrate: %w", err) + } + return migrate.Create(ctx, tables...) +} + +// WriteTo writes the schema changes to w instead of running them against the database. +// +// if err := client.Schema.WriteTo(context.Background(), os.Stdout); err != nil { +// log.Fatal(err) +// } +func (s *Schema) WriteTo(ctx context.Context, w io.Writer, opts ...schema.MigrateOption) error { + return Create(ctx, &Schema{drv: &schema.WriteDriver{Writer: w, Driver: s.drv}}, Tables, opts...) +} diff --git a/app/user_verifications/rpc/internal/models/migrate/schema.go b/app/user_verifications/rpc/internal/models/migrate/schema.go new file mode 100644 index 0000000..a460dbd --- /dev/null +++ b/app/user_verifications/rpc/internal/models/migrate/schema.go @@ -0,0 +1,37 @@ +// Code generated by ent, DO NOT EDIT. + +package migrate + +import ( + "entgo.io/ent/dialect/sql/schema" + "entgo.io/ent/schema/field" +) + +var ( + // UserVerificationsColumns holds the columns for the "user_verifications" table. + UserVerificationsColumns = []*schema.Column{ + {Name: "id", Type: field.TypeInt64, Increment: true}, + {Name: "user_id", Type: field.TypeInt64, Unique: true}, + {Name: "role", Type: field.TypeString, Unique: true}, + {Name: "status", Type: field.TypeString, Default: "pending"}, + {Name: "materials", Type: field.TypeJSON}, + {Name: "reject_reason", Type: field.TypeString, Default: ""}, + {Name: "reviewed_by", Type: field.TypeInt64}, + {Name: "reviewed_at", Type: field.TypeTime}, + {Name: "created_at", Type: field.TypeTime}, + {Name: "updated_at", Type: field.TypeTime}, + } + // UserVerificationsTable holds the schema information for the "user_verifications" table. + UserVerificationsTable = &schema.Table{ + Name: "user_verifications", + Columns: UserVerificationsColumns, + PrimaryKey: []*schema.Column{UserVerificationsColumns[0]}, + } + // Tables holds all the tables in the schema. + Tables = []*schema.Table{ + UserVerificationsTable, + } +) + +func init() { +} diff --git a/app/user_verifications/rpc/internal/models/mutation.go b/app/user_verifications/rpc/internal/models/mutation.go new file mode 100644 index 0000000..0a1d297 --- /dev/null +++ b/app/user_verifications/rpc/internal/models/mutation.go @@ -0,0 +1,862 @@ +// Code generated by ent, DO NOT EDIT. + +package models + +import ( + "context" + "errors" + "fmt" + "juwan-backend/app/user_verifications/rpc/internal/models/predicate" + "juwan-backend/app/user_verifications/rpc/internal/models/schema" + "juwan-backend/app/user_verifications/rpc/internal/models/userverifications" + "sync" + "time" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" +) + +const ( + // Operation types. + OpCreate = ent.OpCreate + OpDelete = ent.OpDelete + OpDeleteOne = ent.OpDeleteOne + OpUpdate = ent.OpUpdate + OpUpdateOne = ent.OpUpdateOne + + // Node types. + TypeUserVerifications = "UserVerifications" +) + +// UserVerificationsMutation represents an operation that mutates the UserVerifications nodes in the graph. +type UserVerificationsMutation struct { + config + op Op + typ string + id *int64 + user_id *int64 + adduser_id *int64 + role *string + status *string + materials *schema.MaterialStruct + reject_reason *string + reviewed_by *int64 + addreviewed_by *int64 + reviewed_at *time.Time + created_at *time.Time + updated_at *time.Time + clearedFields map[string]struct{} + done bool + oldValue func(context.Context) (*UserVerifications, error) + predicates []predicate.UserVerifications +} + +var _ ent.Mutation = (*UserVerificationsMutation)(nil) + +// userverificationsOption allows management of the mutation configuration using functional options. +type userverificationsOption func(*UserVerificationsMutation) + +// newUserVerificationsMutation creates new mutation for the UserVerifications entity. +func newUserVerificationsMutation(c config, op Op, opts ...userverificationsOption) *UserVerificationsMutation { + m := &UserVerificationsMutation{ + config: c, + op: op, + typ: TypeUserVerifications, + clearedFields: make(map[string]struct{}), + } + for _, opt := range opts { + opt(m) + } + return m +} + +// withUserVerificationsID sets the ID field of the mutation. +func withUserVerificationsID(id int64) userverificationsOption { + return func(m *UserVerificationsMutation) { + var ( + err error + once sync.Once + value *UserVerifications + ) + m.oldValue = func(ctx context.Context) (*UserVerifications, error) { + once.Do(func() { + if m.done { + err = errors.New("querying old values post mutation is not allowed") + } else { + value, err = m.Client().UserVerifications.Get(ctx, id) + } + }) + return value, err + } + m.id = &id + } +} + +// withUserVerifications sets the old UserVerifications of the mutation. +func withUserVerifications(node *UserVerifications) userverificationsOption { + return func(m *UserVerificationsMutation) { + m.oldValue = func(context.Context) (*UserVerifications, error) { + return node, nil + } + m.id = &node.ID + } +} + +// Client returns a new `ent.Client` from the mutation. If the mutation was +// executed in a transaction (ent.Tx), a transactional client is returned. +func (m UserVerificationsMutation) Client() *Client { + client := &Client{config: m.config} + client.init() + return client +} + +// Tx returns an `ent.Tx` for mutations that were executed in transactions; +// it returns an error otherwise. +func (m UserVerificationsMutation) Tx() (*Tx, error) { + if _, ok := m.driver.(*txDriver); !ok { + return nil, errors.New("models: mutation is not running in a transaction") + } + tx := &Tx{config: m.config} + tx.init() + return tx, nil +} + +// SetID sets the value of the id field. Note that this +// operation is only accepted on creation of UserVerifications entities. +func (m *UserVerificationsMutation) SetID(id int64) { + m.id = &id +} + +// ID returns the ID value in the mutation. Note that the ID is only available +// if it was provided to the builder or after it was returned from the database. +func (m *UserVerificationsMutation) ID() (id int64, exists bool) { + if m.id == nil { + return + } + return *m.id, true +} + +// IDs queries the database and returns the entity ids that match the mutation's predicate. +// That means, if the mutation is applied within a transaction with an isolation level such +// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated +// or updated by the mutation. +func (m *UserVerificationsMutation) IDs(ctx context.Context) ([]int64, error) { + switch { + case m.op.Is(OpUpdateOne | OpDeleteOne): + id, exists := m.ID() + if exists { + return []int64{id}, nil + } + fallthrough + case m.op.Is(OpUpdate | OpDelete): + return m.Client().UserVerifications.Query().Where(m.predicates...).IDs(ctx) + default: + return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) + } +} + +// SetUserID sets the "user_id" field. +func (m *UserVerificationsMutation) SetUserID(i int64) { + m.user_id = &i + m.adduser_id = nil +} + +// UserID returns the value of the "user_id" field in the mutation. +func (m *UserVerificationsMutation) UserID() (r int64, exists bool) { + v := m.user_id + if v == nil { + return + } + return *v, true +} + +// OldUserID returns the old "user_id" field's value of the UserVerifications entity. +// If the UserVerifications object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserVerificationsMutation) OldUserID(ctx context.Context) (v int64, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldUserID is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldUserID requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldUserID: %w", err) + } + return oldValue.UserID, nil +} + +// AddUserID adds i to the "user_id" field. +func (m *UserVerificationsMutation) AddUserID(i int64) { + if m.adduser_id != nil { + *m.adduser_id += i + } else { + m.adduser_id = &i + } +} + +// AddedUserID returns the value that was added to the "user_id" field in this mutation. +func (m *UserVerificationsMutation) AddedUserID() (r int64, exists bool) { + v := m.adduser_id + if v == nil { + return + } + return *v, true +} + +// ResetUserID resets all changes to the "user_id" field. +func (m *UserVerificationsMutation) ResetUserID() { + m.user_id = nil + m.adduser_id = nil +} + +// SetRole sets the "role" field. +func (m *UserVerificationsMutation) SetRole(s string) { + m.role = &s +} + +// Role returns the value of the "role" field in the mutation. +func (m *UserVerificationsMutation) Role() (r string, exists bool) { + v := m.role + if v == nil { + return + } + return *v, true +} + +// OldRole returns the old "role" field's value of the UserVerifications entity. +// If the UserVerifications object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserVerificationsMutation) OldRole(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldRole is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldRole requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldRole: %w", err) + } + return oldValue.Role, nil +} + +// ResetRole resets all changes to the "role" field. +func (m *UserVerificationsMutation) ResetRole() { + m.role = nil +} + +// SetStatus sets the "status" field. +func (m *UserVerificationsMutation) SetStatus(s string) { + m.status = &s +} + +// Status returns the value of the "status" field in the mutation. +func (m *UserVerificationsMutation) Status() (r string, exists bool) { + v := m.status + if v == nil { + return + } + return *v, true +} + +// OldStatus returns the old "status" field's value of the UserVerifications entity. +// If the UserVerifications object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserVerificationsMutation) OldStatus(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldStatus is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldStatus requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldStatus: %w", err) + } + return oldValue.Status, nil +} + +// ResetStatus resets all changes to the "status" field. +func (m *UserVerificationsMutation) ResetStatus() { + m.status = nil +} + +// SetMaterials sets the "materials" field. +func (m *UserVerificationsMutation) SetMaterials(ss schema.MaterialStruct) { + m.materials = &ss +} + +// Materials returns the value of the "materials" field in the mutation. +func (m *UserVerificationsMutation) Materials() (r schema.MaterialStruct, exists bool) { + v := m.materials + if v == nil { + return + } + return *v, true +} + +// OldMaterials returns the old "materials" field's value of the UserVerifications entity. +// If the UserVerifications object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserVerificationsMutation) OldMaterials(ctx context.Context) (v schema.MaterialStruct, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldMaterials is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldMaterials requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldMaterials: %w", err) + } + return oldValue.Materials, nil +} + +// ResetMaterials resets all changes to the "materials" field. +func (m *UserVerificationsMutation) ResetMaterials() { + m.materials = nil +} + +// SetRejectReason sets the "reject_reason" field. +func (m *UserVerificationsMutation) SetRejectReason(s string) { + m.reject_reason = &s +} + +// RejectReason returns the value of the "reject_reason" field in the mutation. +func (m *UserVerificationsMutation) RejectReason() (r string, exists bool) { + v := m.reject_reason + if v == nil { + return + } + return *v, true +} + +// OldRejectReason returns the old "reject_reason" field's value of the UserVerifications entity. +// If the UserVerifications object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserVerificationsMutation) OldRejectReason(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldRejectReason is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldRejectReason requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldRejectReason: %w", err) + } + return oldValue.RejectReason, nil +} + +// ResetRejectReason resets all changes to the "reject_reason" field. +func (m *UserVerificationsMutation) ResetRejectReason() { + m.reject_reason = nil +} + +// SetReviewedBy sets the "reviewed_by" field. +func (m *UserVerificationsMutation) SetReviewedBy(i int64) { + m.reviewed_by = &i + m.addreviewed_by = nil +} + +// ReviewedBy returns the value of the "reviewed_by" field in the mutation. +func (m *UserVerificationsMutation) ReviewedBy() (r int64, exists bool) { + v := m.reviewed_by + if v == nil { + return + } + return *v, true +} + +// OldReviewedBy returns the old "reviewed_by" field's value of the UserVerifications entity. +// If the UserVerifications object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserVerificationsMutation) OldReviewedBy(ctx context.Context) (v int64, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldReviewedBy is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldReviewedBy requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldReviewedBy: %w", err) + } + return oldValue.ReviewedBy, nil +} + +// AddReviewedBy adds i to the "reviewed_by" field. +func (m *UserVerificationsMutation) AddReviewedBy(i int64) { + if m.addreviewed_by != nil { + *m.addreviewed_by += i + } else { + m.addreviewed_by = &i + } +} + +// AddedReviewedBy returns the value that was added to the "reviewed_by" field in this mutation. +func (m *UserVerificationsMutation) AddedReviewedBy() (r int64, exists bool) { + v := m.addreviewed_by + if v == nil { + return + } + return *v, true +} + +// ResetReviewedBy resets all changes to the "reviewed_by" field. +func (m *UserVerificationsMutation) ResetReviewedBy() { + m.reviewed_by = nil + m.addreviewed_by = nil +} + +// SetReviewedAt sets the "reviewed_at" field. +func (m *UserVerificationsMutation) SetReviewedAt(t time.Time) { + m.reviewed_at = &t +} + +// ReviewedAt returns the value of the "reviewed_at" field in the mutation. +func (m *UserVerificationsMutation) ReviewedAt() (r time.Time, exists bool) { + v := m.reviewed_at + if v == nil { + return + } + return *v, true +} + +// OldReviewedAt returns the old "reviewed_at" field's value of the UserVerifications entity. +// If the UserVerifications object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserVerificationsMutation) OldReviewedAt(ctx context.Context) (v time.Time, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldReviewedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldReviewedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldReviewedAt: %w", err) + } + return oldValue.ReviewedAt, nil +} + +// ResetReviewedAt resets all changes to the "reviewed_at" field. +func (m *UserVerificationsMutation) ResetReviewedAt() { + m.reviewed_at = nil +} + +// SetCreatedAt sets the "created_at" field. +func (m *UserVerificationsMutation) SetCreatedAt(t time.Time) { + m.created_at = &t +} + +// CreatedAt returns the value of the "created_at" field in the mutation. +func (m *UserVerificationsMutation) CreatedAt() (r time.Time, exists bool) { + v := m.created_at + if v == nil { + return + } + return *v, true +} + +// OldCreatedAt returns the old "created_at" field's value of the UserVerifications entity. +// If the UserVerifications object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserVerificationsMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldCreatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err) + } + return oldValue.CreatedAt, nil +} + +// ResetCreatedAt resets all changes to the "created_at" field. +func (m *UserVerificationsMutation) ResetCreatedAt() { + m.created_at = nil +} + +// SetUpdatedAt sets the "updated_at" field. +func (m *UserVerificationsMutation) SetUpdatedAt(t time.Time) { + m.updated_at = &t +} + +// UpdatedAt returns the value of the "updated_at" field in the mutation. +func (m *UserVerificationsMutation) UpdatedAt() (r time.Time, exists bool) { + v := m.updated_at + if v == nil { + return + } + return *v, true +} + +// OldUpdatedAt returns the old "updated_at" field's value of the UserVerifications entity. +// If the UserVerifications object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserVerificationsMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldUpdatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err) + } + return oldValue.UpdatedAt, nil +} + +// ResetUpdatedAt resets all changes to the "updated_at" field. +func (m *UserVerificationsMutation) ResetUpdatedAt() { + m.updated_at = nil +} + +// Where appends a list predicates to the UserVerificationsMutation builder. +func (m *UserVerificationsMutation) Where(ps ...predicate.UserVerifications) { + m.predicates = append(m.predicates, ps...) +} + +// WhereP appends storage-level predicates to the UserVerificationsMutation builder. Using this method, +// users can use type-assertion to append predicates that do not depend on any generated package. +func (m *UserVerificationsMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.UserVerifications, len(ps)) + for i := range ps { + p[i] = ps[i] + } + m.Where(p...) +} + +// Op returns the operation name. +func (m *UserVerificationsMutation) Op() Op { + return m.op +} + +// SetOp allows setting the mutation operation. +func (m *UserVerificationsMutation) SetOp(op Op) { + m.op = op +} + +// Type returns the node type of this mutation (UserVerifications). +func (m *UserVerificationsMutation) Type() string { + return m.typ +} + +// Fields returns all fields that were changed during this mutation. Note that in +// order to get all numeric fields that were incremented/decremented, call +// AddedFields(). +func (m *UserVerificationsMutation) Fields() []string { + fields := make([]string, 0, 9) + if m.user_id != nil { + fields = append(fields, userverifications.FieldUserID) + } + if m.role != nil { + fields = append(fields, userverifications.FieldRole) + } + if m.status != nil { + fields = append(fields, userverifications.FieldStatus) + } + if m.materials != nil { + fields = append(fields, userverifications.FieldMaterials) + } + if m.reject_reason != nil { + fields = append(fields, userverifications.FieldRejectReason) + } + if m.reviewed_by != nil { + fields = append(fields, userverifications.FieldReviewedBy) + } + if m.reviewed_at != nil { + fields = append(fields, userverifications.FieldReviewedAt) + } + if m.created_at != nil { + fields = append(fields, userverifications.FieldCreatedAt) + } + if m.updated_at != nil { + fields = append(fields, userverifications.FieldUpdatedAt) + } + return fields +} + +// Field returns the value of a field with the given name. The second boolean +// return value indicates that this field was not set, or was not defined in the +// schema. +func (m *UserVerificationsMutation) Field(name string) (ent.Value, bool) { + switch name { + case userverifications.FieldUserID: + return m.UserID() + case userverifications.FieldRole: + return m.Role() + case userverifications.FieldStatus: + return m.Status() + case userverifications.FieldMaterials: + return m.Materials() + case userverifications.FieldRejectReason: + return m.RejectReason() + case userverifications.FieldReviewedBy: + return m.ReviewedBy() + case userverifications.FieldReviewedAt: + return m.ReviewedAt() + case userverifications.FieldCreatedAt: + return m.CreatedAt() + case userverifications.FieldUpdatedAt: + return m.UpdatedAt() + } + return nil, false +} + +// OldField returns the old value of the field from the database. An error is +// returned if the mutation operation is not UpdateOne, or the query to the +// database failed. +func (m *UserVerificationsMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + switch name { + case userverifications.FieldUserID: + return m.OldUserID(ctx) + case userverifications.FieldRole: + return m.OldRole(ctx) + case userverifications.FieldStatus: + return m.OldStatus(ctx) + case userverifications.FieldMaterials: + return m.OldMaterials(ctx) + case userverifications.FieldRejectReason: + return m.OldRejectReason(ctx) + case userverifications.FieldReviewedBy: + return m.OldReviewedBy(ctx) + case userverifications.FieldReviewedAt: + return m.OldReviewedAt(ctx) + case userverifications.FieldCreatedAt: + return m.OldCreatedAt(ctx) + case userverifications.FieldUpdatedAt: + return m.OldUpdatedAt(ctx) + } + return nil, fmt.Errorf("unknown UserVerifications field %s", name) +} + +// SetField sets the value of a field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *UserVerificationsMutation) SetField(name string, value ent.Value) error { + switch name { + case userverifications.FieldUserID: + v, ok := value.(int64) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetUserID(v) + return nil + case userverifications.FieldRole: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetRole(v) + return nil + case userverifications.FieldStatus: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetStatus(v) + return nil + case userverifications.FieldMaterials: + v, ok := value.(schema.MaterialStruct) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetMaterials(v) + return nil + case userverifications.FieldRejectReason: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetRejectReason(v) + return nil + case userverifications.FieldReviewedBy: + v, ok := value.(int64) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetReviewedBy(v) + return nil + case userverifications.FieldReviewedAt: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetReviewedAt(v) + return nil + case userverifications.FieldCreatedAt: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetCreatedAt(v) + return nil + case userverifications.FieldUpdatedAt: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetUpdatedAt(v) + return nil + } + return fmt.Errorf("unknown UserVerifications field %s", name) +} + +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *UserVerificationsMutation) AddedFields() []string { + var fields []string + if m.adduser_id != nil { + fields = append(fields, userverifications.FieldUserID) + } + if m.addreviewed_by != nil { + fields = append(fields, userverifications.FieldReviewedBy) + } + return fields +} + +// AddedField returns the numeric value that was incremented/decremented on a field +// with the given name. The second boolean return value indicates that this field +// was not set, or was not defined in the schema. +func (m *UserVerificationsMutation) AddedField(name string) (ent.Value, bool) { + switch name { + case userverifications.FieldUserID: + return m.AddedUserID() + case userverifications.FieldReviewedBy: + return m.AddedReviewedBy() + } + return nil, false +} + +// AddField adds the value to the field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *UserVerificationsMutation) AddField(name string, value ent.Value) error { + switch name { + case userverifications.FieldUserID: + v, ok := value.(int64) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddUserID(v) + return nil + case userverifications.FieldReviewedBy: + v, ok := value.(int64) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddReviewedBy(v) + return nil + } + return fmt.Errorf("unknown UserVerifications numeric field %s", name) +} + +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *UserVerificationsMutation) ClearedFields() []string { + return nil +} + +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *UserVerificationsMutation) FieldCleared(name string) bool { + _, ok := m.clearedFields[name] + return ok +} + +// ClearField clears the value of the field with the given name. It returns an +// error if the field is not defined in the schema. +func (m *UserVerificationsMutation) ClearField(name string) error { + return fmt.Errorf("unknown UserVerifications nullable field %s", name) +} + +// ResetField resets all changes in the mutation for the field with the given name. +// It returns an error if the field is not defined in the schema. +func (m *UserVerificationsMutation) ResetField(name string) error { + switch name { + case userverifications.FieldUserID: + m.ResetUserID() + return nil + case userverifications.FieldRole: + m.ResetRole() + return nil + case userverifications.FieldStatus: + m.ResetStatus() + return nil + case userverifications.FieldMaterials: + m.ResetMaterials() + return nil + case userverifications.FieldRejectReason: + m.ResetRejectReason() + return nil + case userverifications.FieldReviewedBy: + m.ResetReviewedBy() + return nil + case userverifications.FieldReviewedAt: + m.ResetReviewedAt() + return nil + case userverifications.FieldCreatedAt: + m.ResetCreatedAt() + return nil + case userverifications.FieldUpdatedAt: + m.ResetUpdatedAt() + return nil + } + return fmt.Errorf("unknown UserVerifications field %s", name) +} + +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *UserVerificationsMutation) AddedEdges() []string { + edges := make([]string, 0, 0) + return edges +} + +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *UserVerificationsMutation) AddedIDs(name string) []ent.Value { + return nil +} + +// RemovedEdges returns all edge names that were removed in this mutation. +func (m *UserVerificationsMutation) RemovedEdges() []string { + edges := make([]string, 0, 0) + return edges +} + +// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with +// the given name in this mutation. +func (m *UserVerificationsMutation) RemovedIDs(name string) []ent.Value { + return nil +} + +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *UserVerificationsMutation) ClearedEdges() []string { + edges := make([]string, 0, 0) + return edges +} + +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *UserVerificationsMutation) EdgeCleared(name string) bool { + return false +} + +// ClearEdge clears the value of the edge with the given name. It returns an error +// if that edge is not defined in the schema. +func (m *UserVerificationsMutation) ClearEdge(name string) error { + return fmt.Errorf("unknown UserVerifications unique edge %s", name) +} + +// ResetEdge resets all changes to the edge with the given name in this mutation. +// It returns an error if the edge is not defined in the schema. +func (m *UserVerificationsMutation) ResetEdge(name string) error { + return fmt.Errorf("unknown UserVerifications edge %s", name) +} diff --git a/app/user_verifications/rpc/internal/models/predicate/predicate.go b/app/user_verifications/rpc/internal/models/predicate/predicate.go new file mode 100644 index 0000000..ac6f6b8 --- /dev/null +++ b/app/user_verifications/rpc/internal/models/predicate/predicate.go @@ -0,0 +1,10 @@ +// Code generated by ent, DO NOT EDIT. + +package predicate + +import ( + "entgo.io/ent/dialect/sql" +) + +// UserVerifications is the predicate function for userverifications builders. +type UserVerifications func(*sql.Selector) diff --git a/app/user_verifications/rpc/internal/models/runtime.go b/app/user_verifications/rpc/internal/models/runtime.go new file mode 100644 index 0000000..cec2fc2 --- /dev/null +++ b/app/user_verifications/rpc/internal/models/runtime.go @@ -0,0 +1,24 @@ +// Code generated by ent, DO NOT EDIT. + +package models + +import ( + "juwan-backend/app/user_verifications/rpc/internal/models/schema" + "juwan-backend/app/user_verifications/rpc/internal/models/userverifications" +) + +// The init function reads all schema descriptors with runtime code +// (default values, validators, hooks and policies) and stitches it +// to their package variables. +func init() { + userverificationsFields := schema.UserVerifications{}.Fields() + _ = userverificationsFields + // userverificationsDescStatus is the schema descriptor for status field. + userverificationsDescStatus := userverificationsFields[3].Descriptor() + // userverifications.DefaultStatus holds the default value on creation for the status field. + userverifications.DefaultStatus = userverificationsDescStatus.Default.(string) + // userverificationsDescRejectReason is the schema descriptor for reject_reason field. + userverificationsDescRejectReason := userverificationsFields[5].Descriptor() + // userverifications.DefaultRejectReason holds the default value on creation for the reject_reason field. + userverifications.DefaultRejectReason = userverificationsDescRejectReason.Default.(string) +} diff --git a/app/user_verifications/rpc/internal/models/runtime/runtime.go b/app/user_verifications/rpc/internal/models/runtime/runtime.go new file mode 100644 index 0000000..e9dda7a --- /dev/null +++ b/app/user_verifications/rpc/internal/models/runtime/runtime.go @@ -0,0 +1,10 @@ +// Code generated by ent, DO NOT EDIT. + +package runtime + +// The schema-stitching logic is generated in juwan-backend/app/user_verifications/rpc/internal/models/runtime.go + +const ( + Version = "v0.14.5" // Version of ent codegen. + Sum = "h1:Rj2WOYJtCkWyFo6a+5wB3EfBRP0rnx1fMk6gGA0UUe4=" // Sum of ent codegen. +) diff --git a/app/user_verifications/rpc/internal/models/schema/userverifications.go b/app/user_verifications/rpc/internal/models/schema/userverifications.go new file mode 100644 index 0000000..a429696 --- /dev/null +++ b/app/user_verifications/rpc/internal/models/schema/userverifications.go @@ -0,0 +1,39 @@ +package schema + +import ( + "entgo.io/ent" + "entgo.io/ent/schema/field" +) + +// UserVerifications holds the schema definition for the UserVerifications entity. +type UserVerifications struct { + ent.Schema +} + +type MaterialStruct struct { + IdCardFront string `json:"idCardFront"` + IdCardBack string `json:"idCardBack"` + GameScreenshots []string `json:"gameScreenshots"` + VoiceDemo string `json:"voiceDemo"` +} + +// Fields of the UserVerifications. +func (UserVerifications) Fields() []ent.Field { + return []ent.Field{ + field.Int64("id").Immutable().Unique(), + field.Int64("user_id").Immutable().Unique(), + field.String("role").Unique(), + field.String("status").Default("pending"), + field.JSON("materials", MaterialStruct{}), + field.String("reject_reason").Default(""), + field.Int64("reviewed_by"), + field.Time("reviewed_at").Immutable(), + field.Time("created_at").Immutable(), + field.Time("updated_at").Immutable(), + } +} + +// Edges of the UserVerifications. +func (UserVerifications) Edges() []ent.Edge { + return nil +} diff --git a/app/user_verifications/rpc/internal/models/tx.go b/app/user_verifications/rpc/internal/models/tx.go new file mode 100644 index 0000000..8b33a63 --- /dev/null +++ b/app/user_verifications/rpc/internal/models/tx.go @@ -0,0 +1,210 @@ +// Code generated by ent, DO NOT EDIT. + +package models + +import ( + "context" + "sync" + + "entgo.io/ent/dialect" +) + +// Tx is a transactional client that is created by calling Client.Tx(). +type Tx struct { + config + // UserVerifications is the client for interacting with the UserVerifications builders. + UserVerifications *UserVerificationsClient + + // lazily loaded. + client *Client + clientOnce sync.Once + // ctx lives for the life of the transaction. It is + // the same context used by the underlying connection. + ctx context.Context +} + +type ( + // Committer is the interface that wraps the Commit method. + Committer interface { + Commit(context.Context, *Tx) error + } + + // The CommitFunc type is an adapter to allow the use of ordinary + // function as a Committer. If f is a function with the appropriate + // signature, CommitFunc(f) is a Committer that calls f. + CommitFunc func(context.Context, *Tx) error + + // CommitHook defines the "commit middleware". A function that gets a Committer + // and returns a Committer. For example: + // + // hook := func(next ent.Committer) ent.Committer { + // return ent.CommitFunc(func(ctx context.Context, tx *ent.Tx) error { + // // Do some stuff before. + // if err := next.Commit(ctx, tx); err != nil { + // return err + // } + // // Do some stuff after. + // return nil + // }) + // } + // + CommitHook func(Committer) Committer +) + +// Commit calls f(ctx, m). +func (f CommitFunc) Commit(ctx context.Context, tx *Tx) error { + return f(ctx, tx) +} + +// Commit commits the transaction. +func (tx *Tx) Commit() error { + txDriver := tx.config.driver.(*txDriver) + var fn Committer = CommitFunc(func(context.Context, *Tx) error { + return txDriver.tx.Commit() + }) + txDriver.mu.Lock() + hooks := append([]CommitHook(nil), txDriver.onCommit...) + txDriver.mu.Unlock() + for i := len(hooks) - 1; i >= 0; i-- { + fn = hooks[i](fn) + } + return fn.Commit(tx.ctx, tx) +} + +// OnCommit adds a hook to call on commit. +func (tx *Tx) OnCommit(f CommitHook) { + txDriver := tx.config.driver.(*txDriver) + txDriver.mu.Lock() + txDriver.onCommit = append(txDriver.onCommit, f) + txDriver.mu.Unlock() +} + +type ( + // Rollbacker is the interface that wraps the Rollback method. + Rollbacker interface { + Rollback(context.Context, *Tx) error + } + + // The RollbackFunc type is an adapter to allow the use of ordinary + // function as a Rollbacker. If f is a function with the appropriate + // signature, RollbackFunc(f) is a Rollbacker that calls f. + RollbackFunc func(context.Context, *Tx) error + + // RollbackHook defines the "rollback middleware". A function that gets a Rollbacker + // and returns a Rollbacker. For example: + // + // hook := func(next ent.Rollbacker) ent.Rollbacker { + // return ent.RollbackFunc(func(ctx context.Context, tx *ent.Tx) error { + // // Do some stuff before. + // if err := next.Rollback(ctx, tx); err != nil { + // return err + // } + // // Do some stuff after. + // return nil + // }) + // } + // + RollbackHook func(Rollbacker) Rollbacker +) + +// Rollback calls f(ctx, m). +func (f RollbackFunc) Rollback(ctx context.Context, tx *Tx) error { + return f(ctx, tx) +} + +// Rollback rollbacks the transaction. +func (tx *Tx) Rollback() error { + txDriver := tx.config.driver.(*txDriver) + var fn Rollbacker = RollbackFunc(func(context.Context, *Tx) error { + return txDriver.tx.Rollback() + }) + txDriver.mu.Lock() + hooks := append([]RollbackHook(nil), txDriver.onRollback...) + txDriver.mu.Unlock() + for i := len(hooks) - 1; i >= 0; i-- { + fn = hooks[i](fn) + } + return fn.Rollback(tx.ctx, tx) +} + +// OnRollback adds a hook to call on rollback. +func (tx *Tx) OnRollback(f RollbackHook) { + txDriver := tx.config.driver.(*txDriver) + txDriver.mu.Lock() + txDriver.onRollback = append(txDriver.onRollback, f) + txDriver.mu.Unlock() +} + +// Client returns a Client that binds to current transaction. +func (tx *Tx) Client() *Client { + tx.clientOnce.Do(func() { + tx.client = &Client{config: tx.config} + tx.client.init() + }) + return tx.client +} + +func (tx *Tx) init() { + tx.UserVerifications = NewUserVerificationsClient(tx.config) +} + +// txDriver wraps the given dialect.Tx with a nop dialect.Driver implementation. +// The idea is to support transactions without adding any extra code to the builders. +// When a builder calls to driver.Tx(), it gets the same dialect.Tx instance. +// Commit and Rollback are nop for the internal builders and the user must call one +// of them in order to commit or rollback the transaction. +// +// If a closed transaction is embedded in one of the generated entities, and the entity +// applies a query, for example: UserVerifications.QueryXXX(), the query will be executed +// through the driver which created this transaction. +// +// Note that txDriver is not goroutine safe. +type txDriver struct { + // the driver we started the transaction from. + drv dialect.Driver + // tx is the underlying transaction. + tx dialect.Tx + // completion hooks. + mu sync.Mutex + onCommit []CommitHook + onRollback []RollbackHook +} + +// newTx creates a new transactional driver. +func newTx(ctx context.Context, drv dialect.Driver) (*txDriver, error) { + tx, err := drv.Tx(ctx) + if err != nil { + return nil, err + } + return &txDriver{tx: tx, drv: drv}, nil +} + +// Tx returns the transaction wrapper (txDriver) to avoid Commit or Rollback calls +// from the internal builders. Should be called only by the internal builders. +func (tx *txDriver) Tx(context.Context) (dialect.Tx, error) { return tx, nil } + +// Dialect returns the dialect of the driver we started the transaction from. +func (tx *txDriver) Dialect() string { return tx.drv.Dialect() } + +// Close is a nop close. +func (*txDriver) Close() error { return nil } + +// Commit is a nop commit for the internal builders. +// User must call `Tx.Commit` in order to commit the transaction. +func (*txDriver) Commit() error { return nil } + +// Rollback is a nop rollback for the internal builders. +// User must call `Tx.Rollback` in order to rollback the transaction. +func (*txDriver) Rollback() error { return nil } + +// Exec calls tx.Exec. +func (tx *txDriver) Exec(ctx context.Context, query string, args, v any) error { + return tx.tx.Exec(ctx, query, args, v) +} + +// Query calls tx.Query. +func (tx *txDriver) Query(ctx context.Context, query string, args, v any) error { + return tx.tx.Query(ctx, query, args, v) +} + +var _ dialect.Driver = (*txDriver)(nil) diff --git a/app/user_verifications/rpc/internal/models/userverifications.go b/app/user_verifications/rpc/internal/models/userverifications.go new file mode 100644 index 0000000..4d36b2b --- /dev/null +++ b/app/user_verifications/rpc/internal/models/userverifications.go @@ -0,0 +1,200 @@ +// Code generated by ent, DO NOT EDIT. + +package models + +import ( + "encoding/json" + "fmt" + "juwan-backend/app/user_verifications/rpc/internal/models/schema" + "juwan-backend/app/user_verifications/rpc/internal/models/userverifications" + "strings" + "time" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" +) + +// UserVerifications is the models entity for the UserVerifications schema. +type UserVerifications struct { + config `json:"-"` + // ID of the ent. + ID int64 `json:"id,omitempty"` + // UserID holds the value of the "user_id" field. + UserID int64 `json:"user_id,omitempty"` + // Role holds the value of the "role" field. + Role string `json:"role,omitempty"` + // Status holds the value of the "status" field. + Status string `json:"status,omitempty"` + // Materials holds the value of the "materials" field. + Materials schema.MaterialStruct `json:"materials,omitempty"` + // RejectReason holds the value of the "reject_reason" field. + RejectReason string `json:"reject_reason,omitempty"` + // ReviewedBy holds the value of the "reviewed_by" field. + ReviewedBy int64 `json:"reviewed_by,omitempty"` + // ReviewedAt holds the value of the "reviewed_at" field. + ReviewedAt time.Time `json:"reviewed_at,omitempty"` + // CreatedAt holds the value of the "created_at" field. + CreatedAt time.Time `json:"created_at,omitempty"` + // UpdatedAt holds the value of the "updated_at" field. + UpdatedAt time.Time `json:"updated_at,omitempty"` + selectValues sql.SelectValues +} + +// scanValues returns the types for scanning values from sql.Rows. +func (*UserVerifications) scanValues(columns []string) ([]any, error) { + values := make([]any, len(columns)) + for i := range columns { + switch columns[i] { + case userverifications.FieldMaterials: + values[i] = new([]byte) + case userverifications.FieldID, userverifications.FieldUserID, userverifications.FieldReviewedBy: + values[i] = new(sql.NullInt64) + case userverifications.FieldRole, userverifications.FieldStatus, userverifications.FieldRejectReason: + values[i] = new(sql.NullString) + case userverifications.FieldReviewedAt, userverifications.FieldCreatedAt, userverifications.FieldUpdatedAt: + values[i] = new(sql.NullTime) + default: + values[i] = new(sql.UnknownType) + } + } + return values, nil +} + +// assignValues assigns the values that were returned from sql.Rows (after scanning) +// to the UserVerifications fields. +func (_m *UserVerifications) assignValues(columns []string, values []any) error { + if m, n := len(values), len(columns); m < n { + return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) + } + for i := range columns { + switch columns[i] { + case userverifications.FieldID: + value, ok := values[i].(*sql.NullInt64) + if !ok { + return fmt.Errorf("unexpected type %T for field id", value) + } + _m.ID = int64(value.Int64) + case userverifications.FieldUserID: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field user_id", values[i]) + } else if value.Valid { + _m.UserID = value.Int64 + } + case userverifications.FieldRole: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field role", values[i]) + } else if value.Valid { + _m.Role = value.String + } + case userverifications.FieldStatus: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field status", values[i]) + } else if value.Valid { + _m.Status = value.String + } + case userverifications.FieldMaterials: + if value, ok := values[i].(*[]byte); !ok { + return fmt.Errorf("unexpected type %T for field materials", values[i]) + } else if value != nil && len(*value) > 0 { + if err := json.Unmarshal(*value, &_m.Materials); err != nil { + return fmt.Errorf("unmarshal field materials: %w", err) + } + } + case userverifications.FieldRejectReason: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field reject_reason", values[i]) + } else if value.Valid { + _m.RejectReason = value.String + } + case userverifications.FieldReviewedBy: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field reviewed_by", values[i]) + } else if value.Valid { + _m.ReviewedBy = value.Int64 + } + case userverifications.FieldReviewedAt: + if value, ok := values[i].(*sql.NullTime); !ok { + return fmt.Errorf("unexpected type %T for field reviewed_at", values[i]) + } else if value.Valid { + _m.ReviewedAt = value.Time + } + case userverifications.FieldCreatedAt: + if value, ok := values[i].(*sql.NullTime); !ok { + return fmt.Errorf("unexpected type %T for field created_at", values[i]) + } else if value.Valid { + _m.CreatedAt = value.Time + } + case userverifications.FieldUpdatedAt: + if value, ok := values[i].(*sql.NullTime); !ok { + return fmt.Errorf("unexpected type %T for field updated_at", values[i]) + } else if value.Valid { + _m.UpdatedAt = value.Time + } + default: + _m.selectValues.Set(columns[i], values[i]) + } + } + return nil +} + +// Value returns the ent.Value that was dynamically selected and assigned to the UserVerifications. +// This includes values selected through modifiers, order, etc. +func (_m *UserVerifications) Value(name string) (ent.Value, error) { + return _m.selectValues.Get(name) +} + +// Update returns a builder for updating this UserVerifications. +// Note that you need to call UserVerifications.Unwrap() before calling this method if this UserVerifications +// was returned from a transaction, and the transaction was committed or rolled back. +func (_m *UserVerifications) Update() *UserVerificationsUpdateOne { + return NewUserVerificationsClient(_m.config).UpdateOne(_m) +} + +// Unwrap unwraps the UserVerifications entity that was returned from a transaction after it was closed, +// so that all future queries will be executed through the driver which created the transaction. +func (_m *UserVerifications) Unwrap() *UserVerifications { + _tx, ok := _m.config.driver.(*txDriver) + if !ok { + panic("models: UserVerifications is not a transactional entity") + } + _m.config.driver = _tx.drv + return _m +} + +// String implements the fmt.Stringer. +func (_m *UserVerifications) String() string { + var builder strings.Builder + builder.WriteString("UserVerifications(") + builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID)) + builder.WriteString("user_id=") + builder.WriteString(fmt.Sprintf("%v", _m.UserID)) + builder.WriteString(", ") + builder.WriteString("role=") + builder.WriteString(_m.Role) + builder.WriteString(", ") + builder.WriteString("status=") + builder.WriteString(_m.Status) + builder.WriteString(", ") + builder.WriteString("materials=") + builder.WriteString(fmt.Sprintf("%v", _m.Materials)) + builder.WriteString(", ") + builder.WriteString("reject_reason=") + builder.WriteString(_m.RejectReason) + builder.WriteString(", ") + builder.WriteString("reviewed_by=") + builder.WriteString(fmt.Sprintf("%v", _m.ReviewedBy)) + builder.WriteString(", ") + builder.WriteString("reviewed_at=") + builder.WriteString(_m.ReviewedAt.Format(time.ANSIC)) + builder.WriteString(", ") + builder.WriteString("created_at=") + builder.WriteString(_m.CreatedAt.Format(time.ANSIC)) + builder.WriteString(", ") + builder.WriteString("updated_at=") + builder.WriteString(_m.UpdatedAt.Format(time.ANSIC)) + builder.WriteByte(')') + return builder.String() +} + +// UserVerificationsSlice is a parsable slice of UserVerifications. +type UserVerificationsSlice []*UserVerifications diff --git a/app/user_verifications/rpc/internal/models/userverifications/userverifications.go b/app/user_verifications/rpc/internal/models/userverifications/userverifications.go new file mode 100644 index 0000000..2a1081b --- /dev/null +++ b/app/user_verifications/rpc/internal/models/userverifications/userverifications.go @@ -0,0 +1,113 @@ +// Code generated by ent, DO NOT EDIT. + +package userverifications + +import ( + "entgo.io/ent/dialect/sql" +) + +const ( + // Label holds the string label denoting the userverifications type in the database. + Label = "user_verifications" + // FieldID holds the string denoting the id field in the database. + FieldID = "id" + // FieldUserID holds the string denoting the user_id field in the database. + FieldUserID = "user_id" + // FieldRole holds the string denoting the role field in the database. + FieldRole = "role" + // FieldStatus holds the string denoting the status field in the database. + FieldStatus = "status" + // FieldMaterials holds the string denoting the materials field in the database. + FieldMaterials = "materials" + // FieldRejectReason holds the string denoting the reject_reason field in the database. + FieldRejectReason = "reject_reason" + // FieldReviewedBy holds the string denoting the reviewed_by field in the database. + FieldReviewedBy = "reviewed_by" + // FieldReviewedAt holds the string denoting the reviewed_at field in the database. + FieldReviewedAt = "reviewed_at" + // FieldCreatedAt holds the string denoting the created_at field in the database. + FieldCreatedAt = "created_at" + // FieldUpdatedAt holds the string denoting the updated_at field in the database. + FieldUpdatedAt = "updated_at" + // Table holds the table name of the userverifications in the database. + Table = "user_verifications" +) + +// Columns holds all SQL columns for userverifications fields. +var Columns = []string{ + FieldID, + FieldUserID, + FieldRole, + FieldStatus, + FieldMaterials, + FieldRejectReason, + FieldReviewedBy, + FieldReviewedAt, + FieldCreatedAt, + FieldUpdatedAt, +} + +// ValidColumn reports if the column name is valid (part of the table columns). +func ValidColumn(column string) bool { + for i := range Columns { + if column == Columns[i] { + return true + } + } + return false +} + +var ( + // DefaultStatus holds the default value on creation for the "status" field. + DefaultStatus string + // DefaultRejectReason holds the default value on creation for the "reject_reason" field. + DefaultRejectReason string +) + +// OrderOption defines the ordering options for the UserVerifications queries. +type OrderOption func(*sql.Selector) + +// ByID orders the results by the id field. +func ByID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldID, opts...).ToFunc() +} + +// ByUserID orders the results by the user_id field. +func ByUserID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldUserID, opts...).ToFunc() +} + +// ByRole orders the results by the role field. +func ByRole(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldRole, opts...).ToFunc() +} + +// ByStatus orders the results by the status field. +func ByStatus(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldStatus, opts...).ToFunc() +} + +// ByRejectReason orders the results by the reject_reason field. +func ByRejectReason(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldRejectReason, opts...).ToFunc() +} + +// ByReviewedBy orders the results by the reviewed_by field. +func ByReviewedBy(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldReviewedBy, opts...).ToFunc() +} + +// ByReviewedAt orders the results by the reviewed_at field. +func ByReviewedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldReviewedAt, opts...).ToFunc() +} + +// ByCreatedAt orders the results by the created_at field. +func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldCreatedAt, opts...).ToFunc() +} + +// ByUpdatedAt orders the results by the updated_at field. +func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc() +} diff --git a/app/user_verifications/rpc/internal/models/userverifications/where.go b/app/user_verifications/rpc/internal/models/userverifications/where.go new file mode 100644 index 0000000..d50c71b --- /dev/null +++ b/app/user_verifications/rpc/internal/models/userverifications/where.go @@ -0,0 +1,505 @@ +// Code generated by ent, DO NOT EDIT. + +package userverifications + +import ( + "juwan-backend/app/user_verifications/rpc/internal/models/predicate" + "time" + + "entgo.io/ent/dialect/sql" +) + +// ID filters vertices based on their ID field. +func ID(id int64) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldEQ(FieldID, id)) +} + +// IDEQ applies the EQ predicate on the ID field. +func IDEQ(id int64) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldEQ(FieldID, id)) +} + +// IDNEQ applies the NEQ predicate on the ID field. +func IDNEQ(id int64) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldNEQ(FieldID, id)) +} + +// IDIn applies the In predicate on the ID field. +func IDIn(ids ...int64) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldIn(FieldID, ids...)) +} + +// IDNotIn applies the NotIn predicate on the ID field. +func IDNotIn(ids ...int64) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldNotIn(FieldID, ids...)) +} + +// IDGT applies the GT predicate on the ID field. +func IDGT(id int64) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldGT(FieldID, id)) +} + +// IDGTE applies the GTE predicate on the ID field. +func IDGTE(id int64) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldGTE(FieldID, id)) +} + +// IDLT applies the LT predicate on the ID field. +func IDLT(id int64) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldLT(FieldID, id)) +} + +// IDLTE applies the LTE predicate on the ID field. +func IDLTE(id int64) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldLTE(FieldID, id)) +} + +// UserID applies equality check predicate on the "user_id" field. It's identical to UserIDEQ. +func UserID(v int64) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldEQ(FieldUserID, v)) +} + +// Role applies equality check predicate on the "role" field. It's identical to RoleEQ. +func Role(v string) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldEQ(FieldRole, v)) +} + +// Status applies equality check predicate on the "status" field. It's identical to StatusEQ. +func Status(v string) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldEQ(FieldStatus, v)) +} + +// RejectReason applies equality check predicate on the "reject_reason" field. It's identical to RejectReasonEQ. +func RejectReason(v string) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldEQ(FieldRejectReason, v)) +} + +// ReviewedBy applies equality check predicate on the "reviewed_by" field. It's identical to ReviewedByEQ. +func ReviewedBy(v int64) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldEQ(FieldReviewedBy, v)) +} + +// ReviewedAt applies equality check predicate on the "reviewed_at" field. It's identical to ReviewedAtEQ. +func ReviewedAt(v time.Time) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldEQ(FieldReviewedAt, v)) +} + +// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. +func CreatedAt(v time.Time) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldEQ(FieldCreatedAt, v)) +} + +// UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. +func UpdatedAt(v time.Time) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// UserIDEQ applies the EQ predicate on the "user_id" field. +func UserIDEQ(v int64) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldEQ(FieldUserID, v)) +} + +// UserIDNEQ applies the NEQ predicate on the "user_id" field. +func UserIDNEQ(v int64) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldNEQ(FieldUserID, v)) +} + +// UserIDIn applies the In predicate on the "user_id" field. +func UserIDIn(vs ...int64) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldIn(FieldUserID, vs...)) +} + +// UserIDNotIn applies the NotIn predicate on the "user_id" field. +func UserIDNotIn(vs ...int64) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldNotIn(FieldUserID, vs...)) +} + +// UserIDGT applies the GT predicate on the "user_id" field. +func UserIDGT(v int64) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldGT(FieldUserID, v)) +} + +// UserIDGTE applies the GTE predicate on the "user_id" field. +func UserIDGTE(v int64) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldGTE(FieldUserID, v)) +} + +// UserIDLT applies the LT predicate on the "user_id" field. +func UserIDLT(v int64) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldLT(FieldUserID, v)) +} + +// UserIDLTE applies the LTE predicate on the "user_id" field. +func UserIDLTE(v int64) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldLTE(FieldUserID, v)) +} + +// RoleEQ applies the EQ predicate on the "role" field. +func RoleEQ(v string) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldEQ(FieldRole, v)) +} + +// RoleNEQ applies the NEQ predicate on the "role" field. +func RoleNEQ(v string) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldNEQ(FieldRole, v)) +} + +// RoleIn applies the In predicate on the "role" field. +func RoleIn(vs ...string) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldIn(FieldRole, vs...)) +} + +// RoleNotIn applies the NotIn predicate on the "role" field. +func RoleNotIn(vs ...string) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldNotIn(FieldRole, vs...)) +} + +// RoleGT applies the GT predicate on the "role" field. +func RoleGT(v string) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldGT(FieldRole, v)) +} + +// RoleGTE applies the GTE predicate on the "role" field. +func RoleGTE(v string) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldGTE(FieldRole, v)) +} + +// RoleLT applies the LT predicate on the "role" field. +func RoleLT(v string) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldLT(FieldRole, v)) +} + +// RoleLTE applies the LTE predicate on the "role" field. +func RoleLTE(v string) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldLTE(FieldRole, v)) +} + +// RoleContains applies the Contains predicate on the "role" field. +func RoleContains(v string) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldContains(FieldRole, v)) +} + +// RoleHasPrefix applies the HasPrefix predicate on the "role" field. +func RoleHasPrefix(v string) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldHasPrefix(FieldRole, v)) +} + +// RoleHasSuffix applies the HasSuffix predicate on the "role" field. +func RoleHasSuffix(v string) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldHasSuffix(FieldRole, v)) +} + +// RoleEqualFold applies the EqualFold predicate on the "role" field. +func RoleEqualFold(v string) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldEqualFold(FieldRole, v)) +} + +// RoleContainsFold applies the ContainsFold predicate on the "role" field. +func RoleContainsFold(v string) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldContainsFold(FieldRole, v)) +} + +// StatusEQ applies the EQ predicate on the "status" field. +func StatusEQ(v string) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldEQ(FieldStatus, v)) +} + +// StatusNEQ applies the NEQ predicate on the "status" field. +func StatusNEQ(v string) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldNEQ(FieldStatus, v)) +} + +// StatusIn applies the In predicate on the "status" field. +func StatusIn(vs ...string) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldIn(FieldStatus, vs...)) +} + +// StatusNotIn applies the NotIn predicate on the "status" field. +func StatusNotIn(vs ...string) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldNotIn(FieldStatus, vs...)) +} + +// StatusGT applies the GT predicate on the "status" field. +func StatusGT(v string) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldGT(FieldStatus, v)) +} + +// StatusGTE applies the GTE predicate on the "status" field. +func StatusGTE(v string) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldGTE(FieldStatus, v)) +} + +// StatusLT applies the LT predicate on the "status" field. +func StatusLT(v string) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldLT(FieldStatus, v)) +} + +// StatusLTE applies the LTE predicate on the "status" field. +func StatusLTE(v string) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldLTE(FieldStatus, v)) +} + +// StatusContains applies the Contains predicate on the "status" field. +func StatusContains(v string) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldContains(FieldStatus, v)) +} + +// StatusHasPrefix applies the HasPrefix predicate on the "status" field. +func StatusHasPrefix(v string) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldHasPrefix(FieldStatus, v)) +} + +// StatusHasSuffix applies the HasSuffix predicate on the "status" field. +func StatusHasSuffix(v string) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldHasSuffix(FieldStatus, v)) +} + +// StatusEqualFold applies the EqualFold predicate on the "status" field. +func StatusEqualFold(v string) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldEqualFold(FieldStatus, v)) +} + +// StatusContainsFold applies the ContainsFold predicate on the "status" field. +func StatusContainsFold(v string) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldContainsFold(FieldStatus, v)) +} + +// RejectReasonEQ applies the EQ predicate on the "reject_reason" field. +func RejectReasonEQ(v string) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldEQ(FieldRejectReason, v)) +} + +// RejectReasonNEQ applies the NEQ predicate on the "reject_reason" field. +func RejectReasonNEQ(v string) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldNEQ(FieldRejectReason, v)) +} + +// RejectReasonIn applies the In predicate on the "reject_reason" field. +func RejectReasonIn(vs ...string) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldIn(FieldRejectReason, vs...)) +} + +// RejectReasonNotIn applies the NotIn predicate on the "reject_reason" field. +func RejectReasonNotIn(vs ...string) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldNotIn(FieldRejectReason, vs...)) +} + +// RejectReasonGT applies the GT predicate on the "reject_reason" field. +func RejectReasonGT(v string) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldGT(FieldRejectReason, v)) +} + +// RejectReasonGTE applies the GTE predicate on the "reject_reason" field. +func RejectReasonGTE(v string) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldGTE(FieldRejectReason, v)) +} + +// RejectReasonLT applies the LT predicate on the "reject_reason" field. +func RejectReasonLT(v string) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldLT(FieldRejectReason, v)) +} + +// RejectReasonLTE applies the LTE predicate on the "reject_reason" field. +func RejectReasonLTE(v string) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldLTE(FieldRejectReason, v)) +} + +// RejectReasonContains applies the Contains predicate on the "reject_reason" field. +func RejectReasonContains(v string) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldContains(FieldRejectReason, v)) +} + +// RejectReasonHasPrefix applies the HasPrefix predicate on the "reject_reason" field. +func RejectReasonHasPrefix(v string) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldHasPrefix(FieldRejectReason, v)) +} + +// RejectReasonHasSuffix applies the HasSuffix predicate on the "reject_reason" field. +func RejectReasonHasSuffix(v string) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldHasSuffix(FieldRejectReason, v)) +} + +// RejectReasonEqualFold applies the EqualFold predicate on the "reject_reason" field. +func RejectReasonEqualFold(v string) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldEqualFold(FieldRejectReason, v)) +} + +// RejectReasonContainsFold applies the ContainsFold predicate on the "reject_reason" field. +func RejectReasonContainsFold(v string) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldContainsFold(FieldRejectReason, v)) +} + +// ReviewedByEQ applies the EQ predicate on the "reviewed_by" field. +func ReviewedByEQ(v int64) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldEQ(FieldReviewedBy, v)) +} + +// ReviewedByNEQ applies the NEQ predicate on the "reviewed_by" field. +func ReviewedByNEQ(v int64) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldNEQ(FieldReviewedBy, v)) +} + +// ReviewedByIn applies the In predicate on the "reviewed_by" field. +func ReviewedByIn(vs ...int64) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldIn(FieldReviewedBy, vs...)) +} + +// ReviewedByNotIn applies the NotIn predicate on the "reviewed_by" field. +func ReviewedByNotIn(vs ...int64) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldNotIn(FieldReviewedBy, vs...)) +} + +// ReviewedByGT applies the GT predicate on the "reviewed_by" field. +func ReviewedByGT(v int64) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldGT(FieldReviewedBy, v)) +} + +// ReviewedByGTE applies the GTE predicate on the "reviewed_by" field. +func ReviewedByGTE(v int64) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldGTE(FieldReviewedBy, v)) +} + +// ReviewedByLT applies the LT predicate on the "reviewed_by" field. +func ReviewedByLT(v int64) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldLT(FieldReviewedBy, v)) +} + +// ReviewedByLTE applies the LTE predicate on the "reviewed_by" field. +func ReviewedByLTE(v int64) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldLTE(FieldReviewedBy, v)) +} + +// ReviewedAtEQ applies the EQ predicate on the "reviewed_at" field. +func ReviewedAtEQ(v time.Time) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldEQ(FieldReviewedAt, v)) +} + +// ReviewedAtNEQ applies the NEQ predicate on the "reviewed_at" field. +func ReviewedAtNEQ(v time.Time) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldNEQ(FieldReviewedAt, v)) +} + +// ReviewedAtIn applies the In predicate on the "reviewed_at" field. +func ReviewedAtIn(vs ...time.Time) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldIn(FieldReviewedAt, vs...)) +} + +// ReviewedAtNotIn applies the NotIn predicate on the "reviewed_at" field. +func ReviewedAtNotIn(vs ...time.Time) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldNotIn(FieldReviewedAt, vs...)) +} + +// ReviewedAtGT applies the GT predicate on the "reviewed_at" field. +func ReviewedAtGT(v time.Time) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldGT(FieldReviewedAt, v)) +} + +// ReviewedAtGTE applies the GTE predicate on the "reviewed_at" field. +func ReviewedAtGTE(v time.Time) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldGTE(FieldReviewedAt, v)) +} + +// ReviewedAtLT applies the LT predicate on the "reviewed_at" field. +func ReviewedAtLT(v time.Time) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldLT(FieldReviewedAt, v)) +} + +// ReviewedAtLTE applies the LTE predicate on the "reviewed_at" field. +func ReviewedAtLTE(v time.Time) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldLTE(FieldReviewedAt, v)) +} + +// CreatedAtEQ applies the EQ predicate on the "created_at" field. +func CreatedAtEQ(v time.Time) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldEQ(FieldCreatedAt, v)) +} + +// CreatedAtNEQ applies the NEQ predicate on the "created_at" field. +func CreatedAtNEQ(v time.Time) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldNEQ(FieldCreatedAt, v)) +} + +// CreatedAtIn applies the In predicate on the "created_at" field. +func CreatedAtIn(vs ...time.Time) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldIn(FieldCreatedAt, vs...)) +} + +// CreatedAtNotIn applies the NotIn predicate on the "created_at" field. +func CreatedAtNotIn(vs ...time.Time) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldNotIn(FieldCreatedAt, vs...)) +} + +// CreatedAtGT applies the GT predicate on the "created_at" field. +func CreatedAtGT(v time.Time) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldGT(FieldCreatedAt, v)) +} + +// CreatedAtGTE applies the GTE predicate on the "created_at" field. +func CreatedAtGTE(v time.Time) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldGTE(FieldCreatedAt, v)) +} + +// CreatedAtLT applies the LT predicate on the "created_at" field. +func CreatedAtLT(v time.Time) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldLT(FieldCreatedAt, v)) +} + +// CreatedAtLTE applies the LTE predicate on the "created_at" field. +func CreatedAtLTE(v time.Time) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldLTE(FieldCreatedAt, v)) +} + +// UpdatedAtEQ applies the EQ predicate on the "updated_at" field. +func UpdatedAtEQ(v time.Time) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. +func UpdatedAtNEQ(v time.Time) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldNEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtIn applies the In predicate on the "updated_at" field. +func UpdatedAtIn(vs ...time.Time) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. +func UpdatedAtNotIn(vs ...time.Time) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldNotIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtGT applies the GT predicate on the "updated_at" field. +func UpdatedAtGT(v time.Time) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldGT(FieldUpdatedAt, v)) +} + +// UpdatedAtGTE applies the GTE predicate on the "updated_at" field. +func UpdatedAtGTE(v time.Time) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldGTE(FieldUpdatedAt, v)) +} + +// UpdatedAtLT applies the LT predicate on the "updated_at" field. +func UpdatedAtLT(v time.Time) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldLT(FieldUpdatedAt, v)) +} + +// UpdatedAtLTE applies the LTE predicate on the "updated_at" field. +func UpdatedAtLTE(v time.Time) predicate.UserVerifications { + return predicate.UserVerifications(sql.FieldLTE(FieldUpdatedAt, v)) +} + +// And groups predicates with the AND operator between them. +func And(predicates ...predicate.UserVerifications) predicate.UserVerifications { + return predicate.UserVerifications(sql.AndPredicates(predicates...)) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.UserVerifications) predicate.UserVerifications { + return predicate.UserVerifications(sql.OrPredicates(predicates...)) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.UserVerifications) predicate.UserVerifications { + return predicate.UserVerifications(sql.NotPredicates(p)) +} diff --git a/app/user_verifications/rpc/internal/models/userverifications_create.go b/app/user_verifications/rpc/internal/models/userverifications_create.go new file mode 100644 index 0000000..752207d --- /dev/null +++ b/app/user_verifications/rpc/internal/models/userverifications_create.go @@ -0,0 +1,331 @@ +// Code generated by ent, DO NOT EDIT. + +package models + +import ( + "context" + "errors" + "fmt" + "juwan-backend/app/user_verifications/rpc/internal/models/schema" + "juwan-backend/app/user_verifications/rpc/internal/models/userverifications" + "time" + + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" +) + +// UserVerificationsCreate is the builder for creating a UserVerifications entity. +type UserVerificationsCreate struct { + config + mutation *UserVerificationsMutation + hooks []Hook +} + +// SetUserID sets the "user_id" field. +func (_c *UserVerificationsCreate) SetUserID(v int64) *UserVerificationsCreate { + _c.mutation.SetUserID(v) + return _c +} + +// SetRole sets the "role" field. +func (_c *UserVerificationsCreate) SetRole(v string) *UserVerificationsCreate { + _c.mutation.SetRole(v) + return _c +} + +// SetStatus sets the "status" field. +func (_c *UserVerificationsCreate) SetStatus(v string) *UserVerificationsCreate { + _c.mutation.SetStatus(v) + return _c +} + +// SetNillableStatus sets the "status" field if the given value is not nil. +func (_c *UserVerificationsCreate) SetNillableStatus(v *string) *UserVerificationsCreate { + if v != nil { + _c.SetStatus(*v) + } + return _c +} + +// SetMaterials sets the "materials" field. +func (_c *UserVerificationsCreate) SetMaterials(v schema.MaterialStruct) *UserVerificationsCreate { + _c.mutation.SetMaterials(v) + return _c +} + +// SetRejectReason sets the "reject_reason" field. +func (_c *UserVerificationsCreate) SetRejectReason(v string) *UserVerificationsCreate { + _c.mutation.SetRejectReason(v) + return _c +} + +// SetNillableRejectReason sets the "reject_reason" field if the given value is not nil. +func (_c *UserVerificationsCreate) SetNillableRejectReason(v *string) *UserVerificationsCreate { + if v != nil { + _c.SetRejectReason(*v) + } + return _c +} + +// SetReviewedBy sets the "reviewed_by" field. +func (_c *UserVerificationsCreate) SetReviewedBy(v int64) *UserVerificationsCreate { + _c.mutation.SetReviewedBy(v) + return _c +} + +// SetReviewedAt sets the "reviewed_at" field. +func (_c *UserVerificationsCreate) SetReviewedAt(v time.Time) *UserVerificationsCreate { + _c.mutation.SetReviewedAt(v) + return _c +} + +// SetCreatedAt sets the "created_at" field. +func (_c *UserVerificationsCreate) SetCreatedAt(v time.Time) *UserVerificationsCreate { + _c.mutation.SetCreatedAt(v) + return _c +} + +// SetUpdatedAt sets the "updated_at" field. +func (_c *UserVerificationsCreate) SetUpdatedAt(v time.Time) *UserVerificationsCreate { + _c.mutation.SetUpdatedAt(v) + return _c +} + +// SetID sets the "id" field. +func (_c *UserVerificationsCreate) SetID(v int64) *UserVerificationsCreate { + _c.mutation.SetID(v) + return _c +} + +// Mutation returns the UserVerificationsMutation object of the builder. +func (_c *UserVerificationsCreate) Mutation() *UserVerificationsMutation { + return _c.mutation +} + +// Save creates the UserVerifications in the database. +func (_c *UserVerificationsCreate) Save(ctx context.Context) (*UserVerifications, error) { + _c.defaults() + return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks) +} + +// SaveX calls Save and panics if Save returns an error. +func (_c *UserVerificationsCreate) SaveX(ctx context.Context) *UserVerifications { + v, err := _c.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (_c *UserVerificationsCreate) Exec(ctx context.Context) error { + _, err := _c.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_c *UserVerificationsCreate) ExecX(ctx context.Context) { + if err := _c.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (_c *UserVerificationsCreate) defaults() { + if _, ok := _c.mutation.Status(); !ok { + v := userverifications.DefaultStatus + _c.mutation.SetStatus(v) + } + if _, ok := _c.mutation.RejectReason(); !ok { + v := userverifications.DefaultRejectReason + _c.mutation.SetRejectReason(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_c *UserVerificationsCreate) check() error { + if _, ok := _c.mutation.UserID(); !ok { + return &ValidationError{Name: "user_id", err: errors.New(`models: missing required field "UserVerifications.user_id"`)} + } + if _, ok := _c.mutation.Role(); !ok { + return &ValidationError{Name: "role", err: errors.New(`models: missing required field "UserVerifications.role"`)} + } + if _, ok := _c.mutation.Status(); !ok { + return &ValidationError{Name: "status", err: errors.New(`models: missing required field "UserVerifications.status"`)} + } + if _, ok := _c.mutation.Materials(); !ok { + return &ValidationError{Name: "materials", err: errors.New(`models: missing required field "UserVerifications.materials"`)} + } + if _, ok := _c.mutation.RejectReason(); !ok { + return &ValidationError{Name: "reject_reason", err: errors.New(`models: missing required field "UserVerifications.reject_reason"`)} + } + if _, ok := _c.mutation.ReviewedBy(); !ok { + return &ValidationError{Name: "reviewed_by", err: errors.New(`models: missing required field "UserVerifications.reviewed_by"`)} + } + if _, ok := _c.mutation.ReviewedAt(); !ok { + return &ValidationError{Name: "reviewed_at", err: errors.New(`models: missing required field "UserVerifications.reviewed_at"`)} + } + if _, ok := _c.mutation.CreatedAt(); !ok { + return &ValidationError{Name: "created_at", err: errors.New(`models: missing required field "UserVerifications.created_at"`)} + } + if _, ok := _c.mutation.UpdatedAt(); !ok { + return &ValidationError{Name: "updated_at", err: errors.New(`models: missing required field "UserVerifications.updated_at"`)} + } + return nil +} + +func (_c *UserVerificationsCreate) sqlSave(ctx context.Context) (*UserVerifications, error) { + if err := _c.check(); err != nil { + return nil, err + } + _node, _spec := _c.createSpec() + if err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + if _spec.ID.Value != _node.ID { + id := _spec.ID.Value.(int64) + _node.ID = int64(id) + } + _c.mutation.id = &_node.ID + _c.mutation.done = true + return _node, nil +} + +func (_c *UserVerificationsCreate) createSpec() (*UserVerifications, *sqlgraph.CreateSpec) { + var ( + _node = &UserVerifications{config: _c.config} + _spec = sqlgraph.NewCreateSpec(userverifications.Table, sqlgraph.NewFieldSpec(userverifications.FieldID, field.TypeInt64)) + ) + if id, ok := _c.mutation.ID(); ok { + _node.ID = id + _spec.ID.Value = id + } + if value, ok := _c.mutation.UserID(); ok { + _spec.SetField(userverifications.FieldUserID, field.TypeInt64, value) + _node.UserID = value + } + if value, ok := _c.mutation.Role(); ok { + _spec.SetField(userverifications.FieldRole, field.TypeString, value) + _node.Role = value + } + if value, ok := _c.mutation.Status(); ok { + _spec.SetField(userverifications.FieldStatus, field.TypeString, value) + _node.Status = value + } + if value, ok := _c.mutation.Materials(); ok { + _spec.SetField(userverifications.FieldMaterials, field.TypeJSON, value) + _node.Materials = value + } + if value, ok := _c.mutation.RejectReason(); ok { + _spec.SetField(userverifications.FieldRejectReason, field.TypeString, value) + _node.RejectReason = value + } + if value, ok := _c.mutation.ReviewedBy(); ok { + _spec.SetField(userverifications.FieldReviewedBy, field.TypeInt64, value) + _node.ReviewedBy = value + } + if value, ok := _c.mutation.ReviewedAt(); ok { + _spec.SetField(userverifications.FieldReviewedAt, field.TypeTime, value) + _node.ReviewedAt = value + } + if value, ok := _c.mutation.CreatedAt(); ok { + _spec.SetField(userverifications.FieldCreatedAt, field.TypeTime, value) + _node.CreatedAt = value + } + if value, ok := _c.mutation.UpdatedAt(); ok { + _spec.SetField(userverifications.FieldUpdatedAt, field.TypeTime, value) + _node.UpdatedAt = value + } + return _node, _spec +} + +// UserVerificationsCreateBulk is the builder for creating many UserVerifications entities in bulk. +type UserVerificationsCreateBulk struct { + config + err error + builders []*UserVerificationsCreate +} + +// Save creates the UserVerifications entities in the database. +func (_c *UserVerificationsCreateBulk) Save(ctx context.Context) ([]*UserVerifications, error) { + if _c.err != nil { + return nil, _c.err + } + specs := make([]*sqlgraph.CreateSpec, len(_c.builders)) + nodes := make([]*UserVerifications, len(_c.builders)) + mutators := make([]Mutator, len(_c.builders)) + for i := range _c.builders { + func(i int, root context.Context) { + builder := _c.builders[i] + builder.defaults() + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*UserVerificationsMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + if err := builder.check(); err != nil { + return nil, err + } + builder.mutation = mutation + var err error + nodes[i], specs[i] = builder.createSpec() + if i < len(mutators)-1 { + _, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation) + } else { + spec := &sqlgraph.BatchCreateSpec{Nodes: specs} + // Invoke the actual operation on the latest mutation in the chain. + if err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + } + } + if err != nil { + return nil, err + } + mutation.id = &nodes[i].ID + if specs[i].ID.Value != nil && nodes[i].ID == 0 { + id := specs[i].ID.Value.(int64) + nodes[i].ID = int64(id) + } + mutation.done = true + return nodes[i], nil + }) + for i := len(builder.hooks) - 1; i >= 0; i-- { + mut = builder.hooks[i](mut) + } + mutators[i] = mut + }(i, ctx) + } + if len(mutators) > 0 { + if _, err := mutators[0].Mutate(ctx, _c.builders[0].mutation); err != nil { + return nil, err + } + } + return nodes, nil +} + +// SaveX is like Save, but panics if an error occurs. +func (_c *UserVerificationsCreateBulk) SaveX(ctx context.Context) []*UserVerifications { + v, err := _c.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (_c *UserVerificationsCreateBulk) Exec(ctx context.Context) error { + _, err := _c.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_c *UserVerificationsCreateBulk) ExecX(ctx context.Context) { + if err := _c.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/app/user_verifications/rpc/internal/models/userverifications_delete.go b/app/user_verifications/rpc/internal/models/userverifications_delete.go new file mode 100644 index 0000000..6075465 --- /dev/null +++ b/app/user_verifications/rpc/internal/models/userverifications_delete.go @@ -0,0 +1,88 @@ +// Code generated by ent, DO NOT EDIT. + +package models + +import ( + "context" + "juwan-backend/app/user_verifications/rpc/internal/models/predicate" + "juwan-backend/app/user_verifications/rpc/internal/models/userverifications" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" +) + +// UserVerificationsDelete is the builder for deleting a UserVerifications entity. +type UserVerificationsDelete struct { + config + hooks []Hook + mutation *UserVerificationsMutation +} + +// Where appends a list predicates to the UserVerificationsDelete builder. +func (_d *UserVerificationsDelete) Where(ps ...predicate.UserVerifications) *UserVerificationsDelete { + _d.mutation.Where(ps...) + return _d +} + +// Exec executes the deletion query and returns how many vertices were deleted. +func (_d *UserVerificationsDelete) Exec(ctx context.Context) (int, error) { + return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks) +} + +// ExecX is like Exec, but panics if an error occurs. +func (_d *UserVerificationsDelete) ExecX(ctx context.Context) int { + n, err := _d.Exec(ctx) + if err != nil { + panic(err) + } + return n +} + +func (_d *UserVerificationsDelete) sqlExec(ctx context.Context) (int, error) { + _spec := sqlgraph.NewDeleteSpec(userverifications.Table, sqlgraph.NewFieldSpec(userverifications.FieldID, field.TypeInt64)) + if ps := _d.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec) + if err != nil && sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + _d.mutation.done = true + return affected, err +} + +// UserVerificationsDeleteOne is the builder for deleting a single UserVerifications entity. +type UserVerificationsDeleteOne struct { + _d *UserVerificationsDelete +} + +// Where appends a list predicates to the UserVerificationsDelete builder. +func (_d *UserVerificationsDeleteOne) Where(ps ...predicate.UserVerifications) *UserVerificationsDeleteOne { + _d._d.mutation.Where(ps...) + return _d +} + +// Exec executes the deletion query. +func (_d *UserVerificationsDeleteOne) Exec(ctx context.Context) error { + n, err := _d._d.Exec(ctx) + switch { + case err != nil: + return err + case n == 0: + return &NotFoundError{userverifications.Label} + default: + return nil + } +} + +// ExecX is like Exec, but panics if an error occurs. +func (_d *UserVerificationsDeleteOne) ExecX(ctx context.Context) { + if err := _d.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/app/user_verifications/rpc/internal/models/userverifications_query.go b/app/user_verifications/rpc/internal/models/userverifications_query.go new file mode 100644 index 0000000..0a02789 --- /dev/null +++ b/app/user_verifications/rpc/internal/models/userverifications_query.go @@ -0,0 +1,527 @@ +// Code generated by ent, DO NOT EDIT. + +package models + +import ( + "context" + "fmt" + "juwan-backend/app/user_verifications/rpc/internal/models/predicate" + "juwan-backend/app/user_verifications/rpc/internal/models/userverifications" + "math" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" +) + +// UserVerificationsQuery is the builder for querying UserVerifications entities. +type UserVerificationsQuery struct { + config + ctx *QueryContext + order []userverifications.OrderOption + inters []Interceptor + predicates []predicate.UserVerifications + // intermediate query (i.e. traversal path). + sql *sql.Selector + path func(context.Context) (*sql.Selector, error) +} + +// Where adds a new predicate for the UserVerificationsQuery builder. +func (_q *UserVerificationsQuery) Where(ps ...predicate.UserVerifications) *UserVerificationsQuery { + _q.predicates = append(_q.predicates, ps...) + return _q +} + +// Limit the number of records to be returned by this query. +func (_q *UserVerificationsQuery) Limit(limit int) *UserVerificationsQuery { + _q.ctx.Limit = &limit + return _q +} + +// Offset to start from. +func (_q *UserVerificationsQuery) Offset(offset int) *UserVerificationsQuery { + _q.ctx.Offset = &offset + return _q +} + +// Unique configures the query builder to filter duplicate records on query. +// By default, unique is set to true, and can be disabled using this method. +func (_q *UserVerificationsQuery) Unique(unique bool) *UserVerificationsQuery { + _q.ctx.Unique = &unique + return _q +} + +// Order specifies how the records should be ordered. +func (_q *UserVerificationsQuery) Order(o ...userverifications.OrderOption) *UserVerificationsQuery { + _q.order = append(_q.order, o...) + return _q +} + +// First returns the first UserVerifications entity from the query. +// Returns a *NotFoundError when no UserVerifications was found. +func (_q *UserVerificationsQuery) First(ctx context.Context) (*UserVerifications, error) { + nodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst)) + if err != nil { + return nil, err + } + if len(nodes) == 0 { + return nil, &NotFoundError{userverifications.Label} + } + return nodes[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (_q *UserVerificationsQuery) FirstX(ctx context.Context) *UserVerifications { + node, err := _q.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return node +} + +// FirstID returns the first UserVerifications ID from the query. +// Returns a *NotFoundError when no UserVerifications ID was found. +func (_q *UserVerificationsQuery) FirstID(ctx context.Context) (id int64, err error) { + var ids []int64 + if ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil { + return + } + if len(ids) == 0 { + err = &NotFoundError{userverifications.Label} + return + } + return ids[0], nil +} + +// FirstIDX is like FirstID, but panics if an error occurs. +func (_q *UserVerificationsQuery) FirstIDX(ctx context.Context) int64 { + id, err := _q.FirstID(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return id +} + +// Only returns a single UserVerifications entity found by the query, ensuring it only returns one. +// Returns a *NotSingularError when more than one UserVerifications entity is found. +// Returns a *NotFoundError when no UserVerifications entities are found. +func (_q *UserVerificationsQuery) Only(ctx context.Context) (*UserVerifications, error) { + nodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly)) + if err != nil { + return nil, err + } + switch len(nodes) { + case 1: + return nodes[0], nil + case 0: + return nil, &NotFoundError{userverifications.Label} + default: + return nil, &NotSingularError{userverifications.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (_q *UserVerificationsQuery) OnlyX(ctx context.Context) *UserVerifications { + node, err := _q.Only(ctx) + if err != nil { + panic(err) + } + return node +} + +// OnlyID is like Only, but returns the only UserVerifications ID in the query. +// Returns a *NotSingularError when more than one UserVerifications ID is found. +// Returns a *NotFoundError when no entities are found. +func (_q *UserVerificationsQuery) OnlyID(ctx context.Context) (id int64, err error) { + var ids []int64 + if ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil { + return + } + switch len(ids) { + case 1: + id = ids[0] + case 0: + err = &NotFoundError{userverifications.Label} + default: + err = &NotSingularError{userverifications.Label} + } + return +} + +// OnlyIDX is like OnlyID, but panics if an error occurs. +func (_q *UserVerificationsQuery) OnlyIDX(ctx context.Context) int64 { + id, err := _q.OnlyID(ctx) + if err != nil { + panic(err) + } + return id +} + +// All executes the query and returns a list of UserVerificationsSlice. +func (_q *UserVerificationsQuery) All(ctx context.Context) ([]*UserVerifications, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll) + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + qr := querierAll[[]*UserVerifications, *UserVerificationsQuery]() + return withInterceptors[[]*UserVerifications](ctx, _q, qr, _q.inters) +} + +// AllX is like All, but panics if an error occurs. +func (_q *UserVerificationsQuery) AllX(ctx context.Context) []*UserVerifications { + nodes, err := _q.All(ctx) + if err != nil { + panic(err) + } + return nodes +} + +// IDs executes the query and returns a list of UserVerifications IDs. +func (_q *UserVerificationsQuery) IDs(ctx context.Context) (ids []int64, err error) { + if _q.ctx.Unique == nil && _q.path != nil { + _q.Unique(true) + } + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs) + if err = _q.Select(userverifications.FieldID).Scan(ctx, &ids); err != nil { + return nil, err + } + return ids, nil +} + +// IDsX is like IDs, but panics if an error occurs. +func (_q *UserVerificationsQuery) IDsX(ctx context.Context) []int64 { + ids, err := _q.IDs(ctx) + if err != nil { + panic(err) + } + return ids +} + +// Count returns the count of the given query. +func (_q *UserVerificationsQuery) Count(ctx context.Context) (int, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount) + if err := _q.prepareQuery(ctx); err != nil { + return 0, err + } + return withInterceptors[int](ctx, _q, querierCount[*UserVerificationsQuery](), _q.inters) +} + +// CountX is like Count, but panics if an error occurs. +func (_q *UserVerificationsQuery) CountX(ctx context.Context) int { + count, err := _q.Count(ctx) + if err != nil { + panic(err) + } + return count +} + +// Exist returns true if the query has elements in the graph. +func (_q *UserVerificationsQuery) Exist(ctx context.Context) (bool, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist) + switch _, err := _q.FirstID(ctx); { + case IsNotFound(err): + return false, nil + case err != nil: + return false, fmt.Errorf("models: check existence: %w", err) + default: + return true, nil + } +} + +// ExistX is like Exist, but panics if an error occurs. +func (_q *UserVerificationsQuery) ExistX(ctx context.Context) bool { + exist, err := _q.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the UserVerificationsQuery builder, including all associated steps. It can be +// used to prepare common query builders and use them differently after the clone is made. +func (_q *UserVerificationsQuery) Clone() *UserVerificationsQuery { + if _q == nil { + return nil + } + return &UserVerificationsQuery{ + config: _q.config, + ctx: _q.ctx.Clone(), + order: append([]userverifications.OrderOption{}, _q.order...), + inters: append([]Interceptor{}, _q.inters...), + predicates: append([]predicate.UserVerifications{}, _q.predicates...), + // clone intermediate query. + sql: _q.sql.Clone(), + path: _q.path, + } +} + +// GroupBy is used to group vertices by one or more fields/columns. +// It is often used with aggregate functions, like: count, max, mean, min, sum. +// +// Example: +// +// var v []struct { +// UserID int64 `json:"user_id,omitempty"` +// Count int `json:"count,omitempty"` +// } +// +// client.UserVerifications.Query(). +// GroupBy(userverifications.FieldUserID). +// Aggregate(models.Count()). +// Scan(ctx, &v) +func (_q *UserVerificationsQuery) GroupBy(field string, fields ...string) *UserVerificationsGroupBy { + _q.ctx.Fields = append([]string{field}, fields...) + grbuild := &UserVerificationsGroupBy{build: _q} + grbuild.flds = &_q.ctx.Fields + grbuild.label = userverifications.Label + grbuild.scan = grbuild.Scan + return grbuild +} + +// Select allows the selection one or more fields/columns for the given query, +// instead of selecting all fields in the entity. +// +// Example: +// +// var v []struct { +// UserID int64 `json:"user_id,omitempty"` +// } +// +// client.UserVerifications.Query(). +// Select(userverifications.FieldUserID). +// Scan(ctx, &v) +func (_q *UserVerificationsQuery) Select(fields ...string) *UserVerificationsSelect { + _q.ctx.Fields = append(_q.ctx.Fields, fields...) + sbuild := &UserVerificationsSelect{UserVerificationsQuery: _q} + sbuild.label = userverifications.Label + sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan + return sbuild +} + +// Aggregate returns a UserVerificationsSelect configured with the given aggregations. +func (_q *UserVerificationsQuery) Aggregate(fns ...AggregateFunc) *UserVerificationsSelect { + return _q.Select().Aggregate(fns...) +} + +func (_q *UserVerificationsQuery) prepareQuery(ctx context.Context) error { + for _, inter := range _q.inters { + if inter == nil { + return fmt.Errorf("models: uninitialized interceptor (forgotten import models/runtime?)") + } + if trv, ok := inter.(Traverser); ok { + if err := trv.Traverse(ctx, _q); err != nil { + return err + } + } + } + for _, f := range _q.ctx.Fields { + if !userverifications.ValidColumn(f) { + return &ValidationError{Name: f, err: fmt.Errorf("models: invalid field %q for query", f)} + } + } + if _q.path != nil { + prev, err := _q.path(ctx) + if err != nil { + return err + } + _q.sql = prev + } + return nil +} + +func (_q *UserVerificationsQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*UserVerifications, error) { + var ( + nodes = []*UserVerifications{} + _spec = _q.querySpec() + ) + _spec.ScanValues = func(columns []string) ([]any, error) { + return (*UserVerifications).scanValues(nil, columns) + } + _spec.Assign = func(columns []string, values []any) error { + node := &UserVerifications{config: _q.config} + nodes = append(nodes, node) + return node.assignValues(columns, values) + } + for i := range hooks { + hooks[i](ctx, _spec) + } + if err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil { + return nil, err + } + if len(nodes) == 0 { + return nodes, nil + } + return nodes, nil +} + +func (_q *UserVerificationsQuery) sqlCount(ctx context.Context) (int, error) { + _spec := _q.querySpec() + _spec.Node.Columns = _q.ctx.Fields + if len(_q.ctx.Fields) > 0 { + _spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique + } + return sqlgraph.CountNodes(ctx, _q.driver, _spec) +} + +func (_q *UserVerificationsQuery) querySpec() *sqlgraph.QuerySpec { + _spec := sqlgraph.NewQuerySpec(userverifications.Table, userverifications.Columns, sqlgraph.NewFieldSpec(userverifications.FieldID, field.TypeInt64)) + _spec.From = _q.sql + if unique := _q.ctx.Unique; unique != nil { + _spec.Unique = *unique + } else if _q.path != nil { + _spec.Unique = true + } + if fields := _q.ctx.Fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, userverifications.FieldID) + for i := range fields { + if fields[i] != userverifications.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) + } + } + } + if ps := _q.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if limit := _q.ctx.Limit; limit != nil { + _spec.Limit = *limit + } + if offset := _q.ctx.Offset; offset != nil { + _spec.Offset = *offset + } + if ps := _q.order; len(ps) > 0 { + _spec.Order = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + return _spec +} + +func (_q *UserVerificationsQuery) sqlQuery(ctx context.Context) *sql.Selector { + builder := sql.Dialect(_q.driver.Dialect()) + t1 := builder.Table(userverifications.Table) + columns := _q.ctx.Fields + if len(columns) == 0 { + columns = userverifications.Columns + } + selector := builder.Select(t1.Columns(columns...)...).From(t1) + if _q.sql != nil { + selector = _q.sql + selector.Select(selector.Columns(columns...)...) + } + if _q.ctx.Unique != nil && *_q.ctx.Unique { + selector.Distinct() + } + for _, p := range _q.predicates { + p(selector) + } + for _, p := range _q.order { + p(selector) + } + if offset := _q.ctx.Offset; offset != nil { + // limit is mandatory for offset clause. We start + // with default value, and override it below if needed. + selector.Offset(*offset).Limit(math.MaxInt32) + } + if limit := _q.ctx.Limit; limit != nil { + selector.Limit(*limit) + } + return selector +} + +// UserVerificationsGroupBy is the group-by builder for UserVerifications entities. +type UserVerificationsGroupBy struct { + selector + build *UserVerificationsQuery +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (_g *UserVerificationsGroupBy) Aggregate(fns ...AggregateFunc) *UserVerificationsGroupBy { + _g.fns = append(_g.fns, fns...) + return _g +} + +// Scan applies the selector query and scans the result into the given value. +func (_g *UserVerificationsGroupBy) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy) + if err := _g.build.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*UserVerificationsQuery, *UserVerificationsGroupBy](ctx, _g.build, _g, _g.build.inters, v) +} + +func (_g *UserVerificationsGroupBy) sqlScan(ctx context.Context, root *UserVerificationsQuery, v any) error { + selector := root.sqlQuery(ctx).Select() + aggregation := make([]string, 0, len(_g.fns)) + for _, fn := range _g.fns { + aggregation = append(aggregation, fn(selector)) + } + if len(selector.SelectedColumns()) == 0 { + columns := make([]string, 0, len(*_g.flds)+len(_g.fns)) + for _, f := range *_g.flds { + columns = append(columns, selector.C(f)) + } + columns = append(columns, aggregation...) + selector.Select(columns...) + } + selector.GroupBy(selector.Columns(*_g.flds...)...) + if err := selector.Err(); err != nil { + return err + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := _g.build.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} + +// UserVerificationsSelect is the builder for selecting fields of UserVerifications entities. +type UserVerificationsSelect struct { + *UserVerificationsQuery + selector +} + +// Aggregate adds the given aggregation functions to the selector query. +func (_s *UserVerificationsSelect) Aggregate(fns ...AggregateFunc) *UserVerificationsSelect { + _s.fns = append(_s.fns, fns...) + return _s +} + +// Scan applies the selector query and scans the result into the given value. +func (_s *UserVerificationsSelect) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect) + if err := _s.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*UserVerificationsQuery, *UserVerificationsSelect](ctx, _s.UserVerificationsQuery, _s, _s.inters, v) +} + +func (_s *UserVerificationsSelect) sqlScan(ctx context.Context, root *UserVerificationsQuery, v any) error { + selector := root.sqlQuery(ctx) + aggregation := make([]string, 0, len(_s.fns)) + for _, fn := range _s.fns { + aggregation = append(aggregation, fn(selector)) + } + switch n := len(*_s.selector.flds); { + case n == 0 && len(aggregation) > 0: + selector.Select(aggregation...) + case n != 0 && len(aggregation) > 0: + selector.AppendSelect(aggregation...) + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := _s.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} diff --git a/app/user_verifications/rpc/internal/models/userverifications_update.go b/app/user_verifications/rpc/internal/models/userverifications_update.go new file mode 100644 index 0000000..ceb0ad9 --- /dev/null +++ b/app/user_verifications/rpc/internal/models/userverifications_update.go @@ -0,0 +1,366 @@ +// Code generated by ent, DO NOT EDIT. + +package models + +import ( + "context" + "errors" + "fmt" + "juwan-backend/app/user_verifications/rpc/internal/models/predicate" + "juwan-backend/app/user_verifications/rpc/internal/models/schema" + "juwan-backend/app/user_verifications/rpc/internal/models/userverifications" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" +) + +// UserVerificationsUpdate is the builder for updating UserVerifications entities. +type UserVerificationsUpdate struct { + config + hooks []Hook + mutation *UserVerificationsMutation +} + +// Where appends a list predicates to the UserVerificationsUpdate builder. +func (_u *UserVerificationsUpdate) Where(ps ...predicate.UserVerifications) *UserVerificationsUpdate { + _u.mutation.Where(ps...) + return _u +} + +// SetRole sets the "role" field. +func (_u *UserVerificationsUpdate) SetRole(v string) *UserVerificationsUpdate { + _u.mutation.SetRole(v) + return _u +} + +// SetNillableRole sets the "role" field if the given value is not nil. +func (_u *UserVerificationsUpdate) SetNillableRole(v *string) *UserVerificationsUpdate { + if v != nil { + _u.SetRole(*v) + } + return _u +} + +// SetStatus sets the "status" field. +func (_u *UserVerificationsUpdate) SetStatus(v string) *UserVerificationsUpdate { + _u.mutation.SetStatus(v) + return _u +} + +// SetNillableStatus sets the "status" field if the given value is not nil. +func (_u *UserVerificationsUpdate) SetNillableStatus(v *string) *UserVerificationsUpdate { + if v != nil { + _u.SetStatus(*v) + } + return _u +} + +// SetMaterials sets the "materials" field. +func (_u *UserVerificationsUpdate) SetMaterials(v schema.MaterialStruct) *UserVerificationsUpdate { + _u.mutation.SetMaterials(v) + return _u +} + +// SetNillableMaterials sets the "materials" field if the given value is not nil. +func (_u *UserVerificationsUpdate) SetNillableMaterials(v *schema.MaterialStruct) *UserVerificationsUpdate { + if v != nil { + _u.SetMaterials(*v) + } + return _u +} + +// SetRejectReason sets the "reject_reason" field. +func (_u *UserVerificationsUpdate) SetRejectReason(v string) *UserVerificationsUpdate { + _u.mutation.SetRejectReason(v) + return _u +} + +// SetNillableRejectReason sets the "reject_reason" field if the given value is not nil. +func (_u *UserVerificationsUpdate) SetNillableRejectReason(v *string) *UserVerificationsUpdate { + if v != nil { + _u.SetRejectReason(*v) + } + return _u +} + +// SetReviewedBy sets the "reviewed_by" field. +func (_u *UserVerificationsUpdate) SetReviewedBy(v int64) *UserVerificationsUpdate { + _u.mutation.ResetReviewedBy() + _u.mutation.SetReviewedBy(v) + return _u +} + +// SetNillableReviewedBy sets the "reviewed_by" field if the given value is not nil. +func (_u *UserVerificationsUpdate) SetNillableReviewedBy(v *int64) *UserVerificationsUpdate { + if v != nil { + _u.SetReviewedBy(*v) + } + return _u +} + +// AddReviewedBy adds value to the "reviewed_by" field. +func (_u *UserVerificationsUpdate) AddReviewedBy(v int64) *UserVerificationsUpdate { + _u.mutation.AddReviewedBy(v) + return _u +} + +// Mutation returns the UserVerificationsMutation object of the builder. +func (_u *UserVerificationsUpdate) Mutation() *UserVerificationsMutation { + return _u.mutation +} + +// Save executes the query and returns the number of nodes affected by the update operation. +func (_u *UserVerificationsUpdate) Save(ctx context.Context) (int, error) { + return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (_u *UserVerificationsUpdate) SaveX(ctx context.Context) int { + affected, err := _u.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (_u *UserVerificationsUpdate) Exec(ctx context.Context) error { + _, err := _u.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_u *UserVerificationsUpdate) ExecX(ctx context.Context) { + if err := _u.Exec(ctx); err != nil { + panic(err) + } +} + +func (_u *UserVerificationsUpdate) sqlSave(ctx context.Context) (_node int, err error) { + _spec := sqlgraph.NewUpdateSpec(userverifications.Table, userverifications.Columns, sqlgraph.NewFieldSpec(userverifications.FieldID, field.TypeInt64)) + if ps := _u.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := _u.mutation.Role(); ok { + _spec.SetField(userverifications.FieldRole, field.TypeString, value) + } + if value, ok := _u.mutation.Status(); ok { + _spec.SetField(userverifications.FieldStatus, field.TypeString, value) + } + if value, ok := _u.mutation.Materials(); ok { + _spec.SetField(userverifications.FieldMaterials, field.TypeJSON, value) + } + if value, ok := _u.mutation.RejectReason(); ok { + _spec.SetField(userverifications.FieldRejectReason, field.TypeString, value) + } + if value, ok := _u.mutation.ReviewedBy(); ok { + _spec.SetField(userverifications.FieldReviewedBy, field.TypeInt64, value) + } + if value, ok := _u.mutation.AddedReviewedBy(); ok { + _spec.AddField(userverifications.FieldReviewedBy, field.TypeInt64, value) + } + if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{userverifications.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return 0, err + } + _u.mutation.done = true + return _node, nil +} + +// UserVerificationsUpdateOne is the builder for updating a single UserVerifications entity. +type UserVerificationsUpdateOne struct { + config + fields []string + hooks []Hook + mutation *UserVerificationsMutation +} + +// SetRole sets the "role" field. +func (_u *UserVerificationsUpdateOne) SetRole(v string) *UserVerificationsUpdateOne { + _u.mutation.SetRole(v) + return _u +} + +// SetNillableRole sets the "role" field if the given value is not nil. +func (_u *UserVerificationsUpdateOne) SetNillableRole(v *string) *UserVerificationsUpdateOne { + if v != nil { + _u.SetRole(*v) + } + return _u +} + +// SetStatus sets the "status" field. +func (_u *UserVerificationsUpdateOne) SetStatus(v string) *UserVerificationsUpdateOne { + _u.mutation.SetStatus(v) + return _u +} + +// SetNillableStatus sets the "status" field if the given value is not nil. +func (_u *UserVerificationsUpdateOne) SetNillableStatus(v *string) *UserVerificationsUpdateOne { + if v != nil { + _u.SetStatus(*v) + } + return _u +} + +// SetMaterials sets the "materials" field. +func (_u *UserVerificationsUpdateOne) SetMaterials(v schema.MaterialStruct) *UserVerificationsUpdateOne { + _u.mutation.SetMaterials(v) + return _u +} + +// SetNillableMaterials sets the "materials" field if the given value is not nil. +func (_u *UserVerificationsUpdateOne) SetNillableMaterials(v *schema.MaterialStruct) *UserVerificationsUpdateOne { + if v != nil { + _u.SetMaterials(*v) + } + return _u +} + +// SetRejectReason sets the "reject_reason" field. +func (_u *UserVerificationsUpdateOne) SetRejectReason(v string) *UserVerificationsUpdateOne { + _u.mutation.SetRejectReason(v) + return _u +} + +// SetNillableRejectReason sets the "reject_reason" field if the given value is not nil. +func (_u *UserVerificationsUpdateOne) SetNillableRejectReason(v *string) *UserVerificationsUpdateOne { + if v != nil { + _u.SetRejectReason(*v) + } + return _u +} + +// SetReviewedBy sets the "reviewed_by" field. +func (_u *UserVerificationsUpdateOne) SetReviewedBy(v int64) *UserVerificationsUpdateOne { + _u.mutation.ResetReviewedBy() + _u.mutation.SetReviewedBy(v) + return _u +} + +// SetNillableReviewedBy sets the "reviewed_by" field if the given value is not nil. +func (_u *UserVerificationsUpdateOne) SetNillableReviewedBy(v *int64) *UserVerificationsUpdateOne { + if v != nil { + _u.SetReviewedBy(*v) + } + return _u +} + +// AddReviewedBy adds value to the "reviewed_by" field. +func (_u *UserVerificationsUpdateOne) AddReviewedBy(v int64) *UserVerificationsUpdateOne { + _u.mutation.AddReviewedBy(v) + return _u +} + +// Mutation returns the UserVerificationsMutation object of the builder. +func (_u *UserVerificationsUpdateOne) Mutation() *UserVerificationsMutation { + return _u.mutation +} + +// Where appends a list predicates to the UserVerificationsUpdate builder. +func (_u *UserVerificationsUpdateOne) Where(ps ...predicate.UserVerifications) *UserVerificationsUpdateOne { + _u.mutation.Where(ps...) + return _u +} + +// Select allows selecting one or more fields (columns) of the returned entity. +// The default is selecting all fields defined in the entity schema. +func (_u *UserVerificationsUpdateOne) Select(field string, fields ...string) *UserVerificationsUpdateOne { + _u.fields = append([]string{field}, fields...) + return _u +} + +// Save executes the query and returns the updated UserVerifications entity. +func (_u *UserVerificationsUpdateOne) Save(ctx context.Context) (*UserVerifications, error) { + return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (_u *UserVerificationsUpdateOne) SaveX(ctx context.Context) *UserVerifications { + node, err := _u.Save(ctx) + if err != nil { + panic(err) + } + return node +} + +// Exec executes the query on the entity. +func (_u *UserVerificationsUpdateOne) Exec(ctx context.Context) error { + _, err := _u.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_u *UserVerificationsUpdateOne) ExecX(ctx context.Context) { + if err := _u.Exec(ctx); err != nil { + panic(err) + } +} + +func (_u *UserVerificationsUpdateOne) sqlSave(ctx context.Context) (_node *UserVerifications, err error) { + _spec := sqlgraph.NewUpdateSpec(userverifications.Table, userverifications.Columns, sqlgraph.NewFieldSpec(userverifications.FieldID, field.TypeInt64)) + id, ok := _u.mutation.ID() + if !ok { + return nil, &ValidationError{Name: "id", err: errors.New(`models: missing "UserVerifications.id" for update`)} + } + _spec.Node.ID.Value = id + if fields := _u.fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, userverifications.FieldID) + for _, f := range fields { + if !userverifications.ValidColumn(f) { + return nil, &ValidationError{Name: f, err: fmt.Errorf("models: invalid field %q for query", f)} + } + if f != userverifications.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, f) + } + } + } + if ps := _u.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := _u.mutation.Role(); ok { + _spec.SetField(userverifications.FieldRole, field.TypeString, value) + } + if value, ok := _u.mutation.Status(); ok { + _spec.SetField(userverifications.FieldStatus, field.TypeString, value) + } + if value, ok := _u.mutation.Materials(); ok { + _spec.SetField(userverifications.FieldMaterials, field.TypeJSON, value) + } + if value, ok := _u.mutation.RejectReason(); ok { + _spec.SetField(userverifications.FieldRejectReason, field.TypeString, value) + } + if value, ok := _u.mutation.ReviewedBy(); ok { + _spec.SetField(userverifications.FieldReviewedBy, field.TypeInt64, value) + } + if value, ok := _u.mutation.AddedReviewedBy(); ok { + _spec.AddField(userverifications.FieldReviewedBy, field.TypeInt64, value) + } + _node = &UserVerifications{config: _u.config} + _spec.Assign = _node.assignValues + _spec.ScanValues = _node.scanValues + if err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{userverifications.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + _u.mutation.done = true + return _node, nil +} diff --git a/app/user_verifications/rpc/internal/svc/serviceContext.go b/app/user_verifications/rpc/internal/svc/serviceContext.go index c49f41b..abfe8bd 100644 --- a/app/user_verifications/rpc/internal/svc/serviceContext.go +++ b/app/user_verifications/rpc/internal/svc/serviceContext.go @@ -1,13 +1,55 @@ package svc -import "juwan-backend/app/user_verifications/rpc/internal/config" +import ( + "juwan-backend/app/snowflake/rpc/snowflake" + "juwan-backend/app/user_verifications/rpc/internal/config" + "juwan-backend/app/user_verifications/rpc/internal/models" + "juwan-backend/app/user_verifications/rpc/userverifications" + "juwan-backend/common/redisx" + "juwan-backend/common/snowflakex" + "juwan-backend/pkg/adapter" + "time" + + "ariga.io/entcache" + "entgo.io/ent/dialect/sql" + "github.com/redis/go-redis/v9" + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/zrpc" +) type ServiceContext struct { - Config config.Config + Config config.Config + UserVeriModelRW *models.UserVerificationsClient + UserVeriModelRO *models.UserVerificationsClient + RedisClient *redis.ClusterClient + UserVeriRpc userverifications.UserVerificationsZrpcClient + SnowflakeRpc snowflake.SnowflakeServiceClient } func NewServiceContext(c config.Config) *ServiceContext { + RWConn, err := sql.Open("pgx", c.DB.Master) + if err != nil { + panic(err) + } + ROConn, err := sql.Open("pgx", c.DB.Slave) + if err != nil { + panic(err) + } + + redisConn, err := redisx.ConnectMasterSlaveCluster(c.CacheConf, 5*time.Second) + if err != nil || redisConn == nil { + logx.Errorf("redis connect master error: %s", err) + panic(err) + } + + RWDrv := entcache.NewDriver(RWConn, entcache.TTL(time.Second*30), entcache.Levels(adapter.NewRedisCache(redisConn.Client))) + RODrv := entcache.NewDriver(ROConn, entcache.TTL(time.Second*30), entcache.Levels(adapter.NewRedisCache(redisConn.Client))) return &ServiceContext{ - Config: c, + Config: c, + UserVeriModelRW: models.NewClient(models.Driver(RWDrv)).UserVerifications, + UserVeriModelRO: models.NewClient(models.Driver(RODrv)).UserVerifications, + RedisClient: redisConn.Client, + UserVeriRpc: userverifications.NewUserVerificationsZrpcClient(zrpc.MustNewClient(c.UserVeriRpcConf)), + SnowflakeRpc: snowflakex.NewClient(c.SnowflakeRpcConf), } } diff --git a/app/user_verifications/rpc/internal/svc/userVerificationsModel.go b/app/user_verifications/rpc/internal/svc/userVerificationsModel.go deleted file mode 100644 index 59c0c67..0000000 --- a/app/user_verifications/rpc/internal/svc/userVerificationsModel.go +++ /dev/null @@ -1,27 +0,0 @@ -package svc - -import ( - "github.com/zeromicro/go-zero/core/stores/cache" - "github.com/zeromicro/go-zero/core/stores/sqlx" -) - -var _ UserVerificationsModel = (*customUserVerificationsModel)(nil) - -type ( - // UserVerificationsModel is an interface to be customized, add more methods here, - // and implement the added methods in customUserVerificationsModel. - UserVerificationsModel interface { - userVerificationsModel - } - - customUserVerificationsModel struct { - *defaultUserVerificationsModel - } -) - -// NewUserVerificationsModel returns a model for the database table. -func NewUserVerificationsModel(conn sqlx.SqlConn, c cache.CacheConf, opts ...cache.Option) UserVerificationsModel { - return &customUserVerificationsModel{ - defaultUserVerificationsModel: newUserVerificationsModel(conn, c, opts...), - } -} diff --git a/app/user_verifications/rpc/internal/svc/userVerificationsModel_gen.go b/app/user_verifications/rpc/internal/svc/userVerificationsModel_gen.go deleted file mode 100644 index 9385283..0000000 --- a/app/user_verifications/rpc/internal/svc/userVerificationsModel_gen.go +++ /dev/null @@ -1,154 +0,0 @@ -// Code generated by goctl. DO NOT EDIT. -// versions: -// goctl version: 1.9.2 - -package svc - -import ( - "context" - "database/sql" - "fmt" - "strings" - "time" - - "github.com/zeromicro/go-zero/core/stores/builder" - "github.com/zeromicro/go-zero/core/stores/cache" - "github.com/zeromicro/go-zero/core/stores/sqlc" - "github.com/zeromicro/go-zero/core/stores/sqlx" - "github.com/zeromicro/go-zero/core/stringx" -) - -var ( - userVerificationsFieldNames = builder.RawFieldNames(&UserVerifications{}, true) - userVerificationsRows = strings.Join(userVerificationsFieldNames, ",") - userVerificationsRowsExpectAutoSet = strings.Join(stringx.Remove(userVerificationsFieldNames, "create_at", "create_time", "created_at", "update_at", "update_time", "updated_at"), ",") - userVerificationsRowsWithPlaceHolder = builder.PostgreSqlJoin(stringx.Remove(userVerificationsFieldNames, "id", "create_at", "create_time", "created_at", "update_at", "update_time", "updated_at")) - - cachePublicUserVerificationsIdPrefix = "cache:public:userVerifications:id:" - cachePublicUserVerificationsUserIdRolePrefix = "cache:public:userVerifications:userId:role:" -) - -type ( - userVerificationsModel interface { - Insert(ctx context.Context, data *UserVerifications) (sql.Result, error) - FindOne(ctx context.Context, id int64) (*UserVerifications, error) - FindOneByUserIdRole(ctx context.Context, userId int64, role string) (*UserVerifications, error) - Update(ctx context.Context, data *UserVerifications) error - Delete(ctx context.Context, id int64) error - } - - defaultUserVerificationsModel struct { - sqlc.CachedConn - table string - } - - UserVerifications struct { - Id int64 `db:"id"` - UserId int64 `db:"user_id"` - Role string `db:"role"` - Status string `db:"status"` - Materials string `db:"materials"` - RejectReason sql.NullString `db:"reject_reason"` - ReviewedBy sql.NullInt64 `db:"reviewed_by"` - ReviewedAt sql.NullTime `db:"reviewed_at"` - CreatedAt time.Time `db:"created_at"` - UpdatedAt time.Time `db:"updated_at"` - } -) - -func newUserVerificationsModel(conn sqlx.SqlConn, c cache.CacheConf, opts ...cache.Option) *defaultUserVerificationsModel { - return &defaultUserVerificationsModel{ - CachedConn: sqlc.NewConn(conn, c, opts...), - table: `"public"."user_verifications"`, - } -} - -func (m *defaultUserVerificationsModel) Delete(ctx context.Context, id int64) error { - data, err := m.FindOne(ctx, id) - if err != nil { - return err - } - - publicUserVerificationsIdKey := fmt.Sprintf("%s%v", cachePublicUserVerificationsIdPrefix, id) - publicUserVerificationsUserIdRoleKey := fmt.Sprintf("%s%v:%v", cachePublicUserVerificationsUserIdRolePrefix, data.UserId, data.Role) - _, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { - query := fmt.Sprintf("delete from %s where id = $1", m.table) - return conn.ExecCtx(ctx, query, id) - }, publicUserVerificationsIdKey, publicUserVerificationsUserIdRoleKey) - return err -} - -func (m *defaultUserVerificationsModel) FindOne(ctx context.Context, id int64) (*UserVerifications, error) { - publicUserVerificationsIdKey := fmt.Sprintf("%s%v", cachePublicUserVerificationsIdPrefix, id) - var resp UserVerifications - err := m.QueryRowCtx(ctx, &resp, publicUserVerificationsIdKey, func(ctx context.Context, conn sqlx.SqlConn, v any) error { - query := fmt.Sprintf("select %s from %s where id = $1 limit 1", userVerificationsRows, m.table) - return conn.QueryRowCtx(ctx, v, query, id) - }) - switch err { - case nil: - return &resp, nil - case sqlc.ErrNotFound: - return nil, ErrNotFound - default: - return nil, err - } -} - -func (m *defaultUserVerificationsModel) FindOneByUserIdRole(ctx context.Context, userId int64, role string) (*UserVerifications, error) { - publicUserVerificationsUserIdRoleKey := fmt.Sprintf("%s%v:%v", cachePublicUserVerificationsUserIdRolePrefix, userId, role) - var resp UserVerifications - err := m.QueryRowIndexCtx(ctx, &resp, publicUserVerificationsUserIdRoleKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v any) (i any, e error) { - query := fmt.Sprintf("select %s from %s where user_id = $1 and role = $2 limit 1", userVerificationsRows, m.table) - if err := conn.QueryRowCtx(ctx, &resp, query, userId, role); err != nil { - return nil, err - } - return resp.Id, nil - }, m.queryPrimary) - switch err { - case nil: - return &resp, nil - case sqlc.ErrNotFound: - return nil, ErrNotFound - default: - return nil, err - } -} - -func (m *defaultUserVerificationsModel) Insert(ctx context.Context, data *UserVerifications) (sql.Result, error) { - publicUserVerificationsIdKey := fmt.Sprintf("%s%v", cachePublicUserVerificationsIdPrefix, data.Id) - publicUserVerificationsUserIdRoleKey := fmt.Sprintf("%s%v:%v", cachePublicUserVerificationsUserIdRolePrefix, data.UserId, data.Role) - ret, err := m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { - query := fmt.Sprintf("insert into %s (%s) values ($1, $2, $3, $4, $5, $6, $7, $8)", m.table, userVerificationsRowsExpectAutoSet) - return conn.ExecCtx(ctx, query, data.Id, data.UserId, data.Role, data.Status, data.Materials, data.RejectReason, data.ReviewedBy, data.ReviewedAt) - }, publicUserVerificationsIdKey, publicUserVerificationsUserIdRoleKey) - return ret, err -} - -func (m *defaultUserVerificationsModel) Update(ctx context.Context, newData *UserVerifications) error { - data, err := m.FindOne(ctx, newData.Id) - if err != nil { - return err - } - - publicUserVerificationsIdKey := fmt.Sprintf("%s%v", cachePublicUserVerificationsIdPrefix, data.Id) - publicUserVerificationsUserIdRoleKey := fmt.Sprintf("%s%v:%v", cachePublicUserVerificationsUserIdRolePrefix, data.UserId, data.Role) - _, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { - query := fmt.Sprintf("update %s set %s where id = $1", m.table, userVerificationsRowsWithPlaceHolder) - return conn.ExecCtx(ctx, query, newData.Id, newData.UserId, newData.Role, newData.Status, newData.Materials, newData.RejectReason, newData.ReviewedBy, newData.ReviewedAt) - }, publicUserVerificationsIdKey, publicUserVerificationsUserIdRoleKey) - return err -} - -func (m *defaultUserVerificationsModel) formatPrimary(primary any) string { - return fmt.Sprintf("%s%v", cachePublicUserVerificationsIdPrefix, primary) -} - -func (m *defaultUserVerificationsModel) queryPrimary(ctx context.Context, conn sqlx.SqlConn, v, primary any) error { - query := fmt.Sprintf("select %s from %s where id = $1 limit 1", userVerificationsRows, m.table) - return conn.QueryRowCtx(ctx, v, query, primary) -} - -func (m *defaultUserVerificationsModel) tableName() string { - return m.table -} diff --git a/app/user_verifications/rpc/internal/svc/vars.go b/app/user_verifications/rpc/internal/svc/vars.go deleted file mode 100644 index e3c6697..0000000 --- a/app/user_verifications/rpc/internal/svc/vars.go +++ /dev/null @@ -1,5 +0,0 @@ -package svc - -import "github.com/zeromicro/go-zero/core/stores/sqlx" - -var ErrNotFound = sqlx.ErrNotFound diff --git a/app/user_verifications/rpc/pb.go b/app/user_verifications/rpc/pb.go index bef17af..119e7c2 100644 --- a/app/user_verifications/rpc/pb.go +++ b/app/user_verifications/rpc/pb.go @@ -22,7 +22,7 @@ func main() { flag.Parse() var c config.Config - conf.MustLoad(*configFile, &c) + conf.MustLoad(*configFile, &c, conf.UseEnv()) ctx := svc.NewServiceContext(c) s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) { diff --git a/app/user_verifications/rpc/pb/user_verifications.pb.go b/app/user_verifications/rpc/pb/user_verifications.pb.go index 6ad5107..fcdd59c 100644 --- a/app/user_verifications/rpc/pb/user_verifications.pb.go +++ b/app/user_verifications/rpc/pb/user_verifications.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.11 -// protoc v5.29.6 +// protoc v3.19.4 // source: user_verifications.proto package pb @@ -284,16 +284,16 @@ func (*AddUserVerificationsResp) Descriptor() ([]byte, []int) { type UpdateUserVerificationsReq struct { state protoimpl.MessageState `protogen:"open.v1"` - Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` //id - UserId int64 `protobuf:"varint,2,opt,name=userId,proto3" json:"userId,omitempty"` //userId - Role string `protobuf:"bytes,3,opt,name=role,proto3" json:"role,omitempty"` //role - Status string `protobuf:"bytes,4,opt,name=status,proto3" json:"status,omitempty"` //status - Materials string `protobuf:"bytes,5,opt,name=materials,proto3" json:"materials,omitempty"` //materials - RejectReason string `protobuf:"bytes,6,opt,name=rejectReason,proto3" json:"rejectReason,omitempty"` //rejectReason - ReviewedBy int64 `protobuf:"varint,7,opt,name=reviewedBy,proto3" json:"reviewedBy,omitempty"` //reviewedBy - ReviewedAt int64 `protobuf:"varint,8,opt,name=reviewedAt,proto3" json:"reviewedAt,omitempty"` //reviewedAt - CreatedAt int64 `protobuf:"varint,9,opt,name=createdAt,proto3" json:"createdAt,omitempty"` //createdAt - UpdatedAt int64 `protobuf:"varint,10,opt,name=updatedAt,proto3" json:"updatedAt,omitempty"` //updatedAt + Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` //id + UserId *int64 `protobuf:"varint,2,opt,name=userId,proto3,oneof" json:"userId,omitempty"` //userId + Role *string `protobuf:"bytes,3,opt,name=role,proto3,oneof" json:"role,omitempty"` //role + Status *string `protobuf:"bytes,4,opt,name=status,proto3,oneof" json:"status,omitempty"` //status + Materials *string `protobuf:"bytes,5,opt,name=materials,proto3,oneof" json:"materials,omitempty"` //materials + RejectReason *string `protobuf:"bytes,6,opt,name=rejectReason,proto3,oneof" json:"rejectReason,omitempty"` //rejectReason + ReviewedBy int64 `protobuf:"varint,7,opt,name=reviewedBy,proto3" json:"reviewedBy,omitempty"` //reviewedBy + ReviewedAt int64 `protobuf:"varint,8,opt,name=reviewedAt,proto3" json:"reviewedAt,omitempty"` //reviewedAt + CreatedAt int64 `protobuf:"varint,9,opt,name=createdAt,proto3" json:"createdAt,omitempty"` //createdAt + UpdatedAt int64 `protobuf:"varint,10,opt,name=updatedAt,proto3" json:"updatedAt,omitempty"` //updatedAt unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -336,36 +336,36 @@ func (x *UpdateUserVerificationsReq) GetId() int64 { } func (x *UpdateUserVerificationsReq) GetUserId() int64 { - if x != nil { - return x.UserId + if x != nil && x.UserId != nil { + return *x.UserId } return 0 } func (x *UpdateUserVerificationsReq) GetRole() string { - if x != nil { - return x.Role + if x != nil && x.Role != nil { + return *x.Role } return "" } func (x *UpdateUserVerificationsReq) GetStatus() string { - if x != nil { - return x.Status + if x != nil && x.Status != nil { + return *x.Status } return "" } func (x *UpdateUserVerificationsReq) GetMaterials() string { - if x != nil { - return x.Materials + if x != nil && x.Materials != nil { + return *x.Materials } return "" } func (x *UpdateUserVerificationsReq) GetRejectReason() string { - if x != nil { - return x.RejectReason + if x != nil && x.RejectReason != nil { + return *x.RejectReason } return "" } @@ -813,14 +813,14 @@ const file_user_verifications_proto_rawDesc = "" + "reviewedAt\x12\x1c\n" + "\tcreatedAt\x18\b \x01(\x03R\tcreatedAt\x12\x1c\n" + "\tupdatedAt\x18\t \x01(\x03R\tupdatedAt\"\x1a\n" + - "\x18AddUserVerificationsResp\"\xae\x02\n" + + "\x18AddUserVerificationsResp\"\x85\x03\n" + "\x1aUpdateUserVerificationsReq\x12\x0e\n" + - "\x02id\x18\x01 \x01(\x03R\x02id\x12\x16\n" + - "\x06userId\x18\x02 \x01(\x03R\x06userId\x12\x12\n" + - "\x04role\x18\x03 \x01(\tR\x04role\x12\x16\n" + - "\x06status\x18\x04 \x01(\tR\x06status\x12\x1c\n" + - "\tmaterials\x18\x05 \x01(\tR\tmaterials\x12\"\n" + - "\frejectReason\x18\x06 \x01(\tR\frejectReason\x12\x1e\n" + + "\x02id\x18\x01 \x01(\x03R\x02id\x12\x1b\n" + + "\x06userId\x18\x02 \x01(\x03H\x00R\x06userId\x88\x01\x01\x12\x17\n" + + "\x04role\x18\x03 \x01(\tH\x01R\x04role\x88\x01\x01\x12\x1b\n" + + "\x06status\x18\x04 \x01(\tH\x02R\x06status\x88\x01\x01\x12!\n" + + "\tmaterials\x18\x05 \x01(\tH\x03R\tmaterials\x88\x01\x01\x12'\n" + + "\frejectReason\x18\x06 \x01(\tH\x04R\frejectReason\x88\x01\x01\x12\x1e\n" + "\n" + "reviewedBy\x18\a \x01(\x03R\n" + "reviewedBy\x12\x1e\n" + @@ -829,7 +829,13 @@ const file_user_verifications_proto_rawDesc = "" + "reviewedAt\x12\x1c\n" + "\tcreatedAt\x18\t \x01(\x03R\tcreatedAt\x12\x1c\n" + "\tupdatedAt\x18\n" + - " \x01(\x03R\tupdatedAt\"\x1d\n" + + " \x01(\x03R\tupdatedAtB\t\n" + + "\a_userIdB\a\n" + + "\x05_roleB\t\n" + + "\a_statusB\f\n" + + "\n" + + "_materialsB\x0f\n" + + "\r_rejectReason\"\x1d\n" + "\x1bUpdateUserVerificationsResp\")\n" + "\x17DelUserVerificationsReq\x12\x0e\n" + "\x02id\x18\x01 \x01(\x03R\x02id\"\x1a\n" + @@ -916,6 +922,7 @@ func file_user_verifications_proto_init() { if File_user_verifications_proto != nil { return } + file_user_verifications_proto_msgTypes[3].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ diff --git a/app/user_verifications/rpc/pb/user_verifications_grpc.pb.go b/app/user_verifications/rpc/pb/user_verifications_grpc.pb.go index 020be6a..6d3b409 100644 --- a/app/user_verifications/rpc/pb/user_verifications_grpc.pb.go +++ b/app/user_verifications/rpc/pb/user_verifications_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.6.1 -// - protoc v5.29.6 +// - protoc v3.19.4 // source: user_verifications.proto package pb diff --git a/app/users/api/etc/user-api.yaml b/app/users/api/etc/user-api.yaml index 93d74f3..9ec0dfa 100644 --- a/app/users/api/etc/user-api.yaml +++ b/app/users/api/etc/user-api.yaml @@ -7,8 +7,9 @@ Prometheus: Port: 4001 Path: /metrics -UsercenterRpcConf: - Target: k8s://juwan/user-rpc-svc:9001 SnowflakeRpcConf: Target: k8s://juwan/snowflake-svc:8080 + +UserVerificationRpc: + Target: k8s://juwan/user_verifications-svc:8080 diff --git a/app/users/api/internal/config/config.go b/app/users/api/internal/config/config.go index d864b93..da8c048 100644 --- a/app/users/api/internal/config/config.go +++ b/app/users/api/internal/config/config.go @@ -10,6 +10,6 @@ import ( type Config struct { rest.RestConf - UsercenterRpcConf zrpc.RpcClientConf - SnowflakeRpcConf zrpc.RpcClientConf + UsercenterRpcConf zrpc.RpcClientConf + UserVerificationRpc zrpc.RpcClientConf } diff --git a/app/users/api/internal/handler/auth/forgotPasswordHandler.go b/app/users/api/internal/handler/auth/forgotPasswordHandler.go new file mode 100644 index 0000000..802b7e0 --- /dev/null +++ b/app/users/api/internal/handler/auth/forgotPasswordHandler.go @@ -0,0 +1,32 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.2 + +package auth + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "juwan-backend/app/users/api/internal/logic/auth" + "juwan-backend/app/users/api/internal/svc" + "juwan-backend/app/users/api/internal/types" +) + +// 忘记密码-发送验证码 +func ForgotPasswordHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.ForgotPasswordReq + if err := httpx.Parse(r, &req); err != nil { + httpx.ErrorCtx(r.Context(), w, err) + return + } + + l := auth.NewForgotPasswordLogic(r.Context(), svcCtx) + resp, err := l.ForgotPassword(&req) + if err != nil { + httpx.ErrorCtx(r.Context(), w, err) + } else { + httpx.OkJsonCtx(r.Context(), w, resp) + } + } +} diff --git a/app/users/api/internal/handler/user/loginHandler.go b/app/users/api/internal/handler/auth/loginHandler.go similarity index 76% rename from app/users/api/internal/handler/user/loginHandler.go rename to app/users/api/internal/handler/auth/loginHandler.go index ef7032b..fbb671c 100644 --- a/app/users/api/internal/handler/user/loginHandler.go +++ b/app/users/api/internal/handler/auth/loginHandler.go @@ -1,19 +1,19 @@ // Code scaffolded by goctl. Safe to edit. // goctl 1.9.2 -package user +package auth import ( - "juwan-backend/app/users/api/internal/logic/user" + "net/http" + + "juwan-backend/app/users/api/internal/logic/auth" "juwan-backend/app/users/api/internal/svc" "juwan-backend/app/users/api/internal/types" - "juwan-backend/common/utils" - "net/http" "github.com/zeromicro/go-zero/rest/httpx" ) -// 用户登录接口 +// 用户登录 func LoginHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var req types.LoginReq @@ -22,14 +22,13 @@ func LoginHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { return } - l := user.NewLoginLogic(r.Context(), svcCtx) + l := auth.NewLoginLogic(r.Context(), svcCtx) resp, err := l.Login(&req) - if err != nil { - httpx.ErrorCtx(r.Context(), w, utils.NewErrorResp(400, err)) + httpx.ErrorCtx(r.Context(), w, err) } else { - token := resp.Token - resp.Token = "" + token := resp.RefreshToken + resp.RefreshToken = "" http.SetCookie(w, &http.Cookie{ Name: "JToken", Value: token, diff --git a/app/users/api/internal/handler/user/logoutHandler.go b/app/users/api/internal/handler/auth/logoutHandler.go similarity index 77% rename from app/users/api/internal/handler/user/logoutHandler.go rename to app/users/api/internal/handler/auth/logoutHandler.go index 1dd45f1..4420138 100644 --- a/app/users/api/internal/handler/user/logoutHandler.go +++ b/app/users/api/internal/handler/auth/logoutHandler.go @@ -1,18 +1,19 @@ // Code scaffolded by goctl. Safe to edit. // goctl 1.9.2 -package user +package auth import ( "net/http" - "github.com/zeromicro/go-zero/rest/httpx" - "juwan-backend/app/users/api/internal/logic/user" + "juwan-backend/app/users/api/internal/logic/auth" "juwan-backend/app/users/api/internal/svc" "juwan-backend/app/users/api/internal/types" + + "github.com/zeromicro/go-zero/rest/httpx" ) -// 用户登出 +// 退出登录 func LogoutHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var req types.LogoutReq @@ -21,7 +22,8 @@ func LogoutHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { return } - l := user.NewLogoutLogic(r.Context(), svcCtx) + // TODO: add userId from http header x-auth-user-id + l := auth.NewLogoutLogic(r.Context(), svcCtx) resp, err := l.Logout(&req) if err != nil { httpx.ErrorCtx(r.Context(), w, err) diff --git a/app/users/api/internal/handler/auth/registerHandler.go b/app/users/api/internal/handler/auth/registerHandler.go new file mode 100644 index 0000000..13d96e1 --- /dev/null +++ b/app/users/api/internal/handler/auth/registerHandler.go @@ -0,0 +1,48 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.2 + +package auth + +import ( + "net/http" + + "juwan-backend/app/users/api/internal/logic/auth" + "juwan-backend/app/users/api/internal/svc" + "juwan-backend/app/users/api/internal/types" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +// 用户注册 +func RegisterHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.RegisterReq + if err := httpx.Parse(r, &req); err != nil { + httpx.ErrorCtx(r.Context(), w, err) + return + } + + l := auth.NewRegisterLogic(r.Context(), svcCtx) + resp, err := l.Register(&req) + if err != nil { + httpx.ErrorCtx(r.Context(), w, err) + } else { + token := resp.RefreshToken + resp.RefreshToken = "" + http.SetCookie(w, &http.Cookie{ + Name: "JToken", + Value: token, + Quoted: false, + Path: "/", + Domain: "", + RawExpires: "", + MaxAge: 691200, + Secure: false, + HttpOnly: true, + SameSite: http.SameSiteStrictMode, + Partitioned: false, + }) + httpx.OkJsonCtx(r.Context(), w, resp) + } + } +} diff --git a/app/users/api/internal/handler/auth/resetPasswordHandler.go b/app/users/api/internal/handler/auth/resetPasswordHandler.go new file mode 100644 index 0000000..8733e23 --- /dev/null +++ b/app/users/api/internal/handler/auth/resetPasswordHandler.go @@ -0,0 +1,32 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.2 + +package auth + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "juwan-backend/app/users/api/internal/logic/auth" + "juwan-backend/app/users/api/internal/svc" + "juwan-backend/app/users/api/internal/types" +) + +// 重置密码 +func ResetPasswordHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.ResetPasswordReq + if err := httpx.Parse(r, &req); err != nil { + httpx.ErrorCtx(r.Context(), w, err) + return + } + + l := auth.NewResetPasswordLogic(r.Context(), svcCtx) + resp, err := l.ResetPassword(&req) + if err != nil { + httpx.ErrorCtx(r.Context(), w, err) + } else { + httpx.OkJsonCtx(r.Context(), w, resp) + } + } +} diff --git a/app/users/api/internal/handler/routes.go b/app/users/api/internal/handler/routes.go index ea2b386..578bf8d 100644 --- a/app/users/api/internal/handler/routes.go +++ b/app/users/api/internal/handler/routes.go @@ -6,7 +6,10 @@ package handler import ( "net/http" + auth "juwan-backend/app/users/api/internal/handler/auth" user "juwan-backend/app/users/api/internal/handler/user" + verification_admin "juwan-backend/app/users/api/internal/handler/verification_admin" + verification_user "juwan-backend/app/users/api/internal/handler/verification_user" "juwan-backend/app/users/api/internal/svc" "github.com/zeromicro/go-zero/rest" @@ -14,41 +17,44 @@ import ( func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { server.AddRoutes( - rest.WithMiddlewares( - []rest.Middleware{serverCtx.Logger}, - []rest.Route{ - { - // 用户登出 - Method: http.MethodPost, - Path: "/:userId/logout", - Handler: user.LogoutHandler(serverCtx), - }, - { - // 修改用户密码 - Method: http.MethodPut, - Path: "/:userId/password", - Handler: user.UpdatePasswordHandler(serverCtx), - }, - { - // 修改密码-使用验证码 - Method: http.MethodPut, - Path: "/forgot-password/reset", - Handler: user.UpdatePasswordByVcodeHandler(serverCtx), - }, - { - // 用户登录接口 - Method: http.MethodPost, - Path: "/login", - Handler: user.LoginHandler(serverCtx), - }, - { - // 用户注册接口 - Method: http.MethodPost, - Path: "/register", - Handler: user.RegisterHandler(serverCtx), - }, - }..., - ), + []rest.Route{ + { + // 忘记密码-发送验证码 + Method: http.MethodPost, + Path: "/forgot-password", + Handler: auth.ForgotPasswordHandler(serverCtx), + }, + { + // 用户登录 + Method: http.MethodPost, + Path: "/login", + Handler: auth.LoginHandler(serverCtx), + }, + { + // 用户注册 + Method: http.MethodPost, + Path: "/register", + Handler: auth.RegisterHandler(serverCtx), + }, + { + // 重置密码 + Method: http.MethodPost, + Path: "/reset-password", + Handler: auth.ResetPasswordHandler(serverCtx), + }, + }, + rest.WithPrefix("/api/v1/auth"), + ) + + server.AddRoutes( + []rest.Route{ + { + // 退出登录 + Method: http.MethodPost, + Path: "/logout", + Handler: auth.LogoutHandler(serverCtx), + }, + }, rest.WithPrefix("/api/v1/auth"), ) @@ -57,16 +63,16 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { []rest.Middleware{serverCtx.Logger}, []rest.Route{ { - // 获取用户信息 - Method: http.MethodGet, - Path: "/:userId", - Handler: user.GetUserInfoHandler(serverCtx), + // 关注用户 + Method: http.MethodPost, + Path: "/:id/follow", + Handler: user.FollowUserHandler(serverCtx), }, { - // 修改用户信息 - Method: http.MethodPut, - Path: "/:userId", - Handler: user.UpdateUserInfoHandler(serverCtx), + // 取消关注用户 + Method: http.MethodDelete, + Path: "/:id/follow", + Handler: user.UnfollowUserHandler(serverCtx), }, { // 获取当前登录用户信息 @@ -75,13 +81,88 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { Handler: user.GetMeHandler(serverCtx), }, { - // 更改当前登录用户信息 + // 更新个人资料 Method: http.MethodPut, Path: "/me", Handler: user.UpdateMeHandler(serverCtx), }, + { + // 更新通知偏好 + Method: http.MethodPut, + Path: "/me/preferences/notifications", + Handler: user.UpdateNotificationSettingsHandler(serverCtx), + }, + { + // 更新主题偏好 + Method: http.MethodPut, + Path: "/me/preferences/theme", + Handler: user.UpdateThemeSettingsHandler(serverCtx), + }, + { + // 切换当前激活角色 + Method: http.MethodPost, + Path: "/me/switch-role", + Handler: user.SwitchRoleHandler(serverCtx), + }, }..., ), - rest.WithPrefix("/api/v1/user"), + rest.WithPrefix("/api/v1/users"), + ) + + server.AddRoutes( + []rest.Route{ + { + // 获取指定用户信息 + Method: http.MethodGet, + Path: "/:id", + Handler: user.GetUserInfoHandler(serverCtx), + }, + }, + rest.WithPrefix("/api/v1/users"), + ) + + server.AddRoutes( + []rest.Route{ + { + // 管理员获取认证申请列表 (分页) + Method: http.MethodGet, + Path: "/verifications", + Handler: verification_admin.GetVerificationsHandler(serverCtx), + }, + { + // 管理员通过申请 + Method: http.MethodPost, + Path: "/verifications/:id/approve", + Handler: verification_admin.ApproveVerificationHandler(serverCtx), + }, + { + // 管理员驳回申请 + Method: http.MethodPost, + Path: "/verifications/:id/reject", + Handler: verification_admin.RejectVerificationHandler(serverCtx), + }, + }, + rest.WithPrefix("/api/v1/admin"), + ) + + server.AddRoutes( + rest.WithMiddlewares( + []rest.Middleware{serverCtx.Logger}, + []rest.Route{ + { + // 提交或修改角色认证申请 (支持幂等更新) + Method: http.MethodPost, + Path: "/me/verification", + Handler: verification_user.ApplyVerificationHandler(serverCtx), + }, + { + // 获取我的所有认证状态 + Method: http.MethodGet, + Path: "/me/verification", + Handler: verification_user.GetMyVerificationsHandler(serverCtx), + }, + }..., + ), + rest.WithPrefix("/api/v1/users"), ) } diff --git a/app/users/api/internal/handler/user/updatePasswordHandler.go b/app/users/api/internal/handler/user/followUserHandler.go similarity index 71% rename from app/users/api/internal/handler/user/updatePasswordHandler.go rename to app/users/api/internal/handler/user/followUserHandler.go index db1b50c..aebb470 100644 --- a/app/users/api/internal/handler/user/updatePasswordHandler.go +++ b/app/users/api/internal/handler/user/followUserHandler.go @@ -12,17 +12,17 @@ import ( "juwan-backend/app/users/api/internal/types" ) -// 修改用户密码 -func UpdatePasswordHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { +// 关注用户 +func FollowUserHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - var req types.UpdatePasswordReq + var req types.FollowUserReq if err := httpx.Parse(r, &req); err != nil { httpx.ErrorCtx(r.Context(), w, err) return } - l := user.NewUpdatePasswordLogic(r.Context(), svcCtx) - resp, err := l.UpdatePassword(&req) + l := user.NewFollowUserLogic(r.Context(), svcCtx) + resp, err := l.FollowUser(&req) if err != nil { httpx.ErrorCtx(r.Context(), w, err) } else { diff --git a/app/users/api/internal/handler/user/registerHandler.go b/app/users/api/internal/handler/user/registerHandler.go deleted file mode 100644 index affba01..0000000 --- a/app/users/api/internal/handler/user/registerHandler.go +++ /dev/null @@ -1,96 +0,0 @@ -// Code scaffolded by goctl. Safe to edit. -// goctl 1.9.2 - -package user - -import ( - "bytes" - "encoding/json" - "errors" - "fmt" - "io" - "juwan-backend/app/users/api/internal/contextx" - "juwan-backend/common/utils" - "net/http" - "strconv" - - "juwan-backend/app/users/api/internal/logic/user" - "juwan-backend/app/users/api/internal/svc" - "juwan-backend/app/users/api/internal/types" - - "github.com/zeromicro/go-zero/rest/httpx" -) - -// 用户注册接口 -func RegisterHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - if err := normalizeRegisterBody(r); err != nil { - httpx.ErrorCtx(r.Context(), w, err) - return - } - - var req types.RegisterReq - if err := httpx.Parse(r, &req); err != nil { - httpx.ErrorCtx(r.Context(), w, err) - return - } - - requestId := r.Header.Get("X-Request-ID") - //regCtx := context.WithValue(r.Context(), "request_id", requestId) - regCtx := contextx.WithRequestId(r.Context(), requestId) - if requestId == "" { - httpx.ErrorCtx(r.Context(), w, errors.New("bad request")) - } - - l := user.NewRegisterLogic(regCtx, svcCtx) - resp, err := l.Register(&req) - - if err != nil { - httpx.ErrorCtx(r.Context(), w, utils.NewErrorResp(400, err)) - } else { - httpx.OkJsonCtx(r.Context(), w, resp) - } - } -} - -func normalizeRegisterBody(r *http.Request) error { - body, err := io.ReadAll(r.Body) - if err != nil { - return err - } - defer r.Body.Close() - - if len(body) == 0 { - r.Body = io.NopCloser(bytes.NewReader(body)) - return nil - } - - var payload map[string]any - if err := json.Unmarshal(body, &payload); err != nil { - r.Body = io.NopCloser(bytes.NewReader(body)) - return nil - } - - vcode, exists := payload["vcode"] - if exists { - switch value := vcode.(type) { - case string: - parsed, convErr := strconv.Atoi(value) - if convErr != nil { - return fmt.Errorf("invalid vcode format") - } - payload["vcode"] = parsed - case float64: - payload["vcode"] = int(value) - } - } - - normalized, err := json.Marshal(payload) - if err != nil { - return err - } - - r.Body = io.NopCloser(bytes.NewReader(normalized)) - r.ContentLength = int64(len(normalized)) - return nil -} diff --git a/app/users/api/internal/handler/user/updatePasswordByVcodeHandler.go b/app/users/api/internal/handler/user/switchRoleHandler.go similarity index 68% rename from app/users/api/internal/handler/user/updatePasswordByVcodeHandler.go rename to app/users/api/internal/handler/user/switchRoleHandler.go index 189ab43..2e31ee1 100644 --- a/app/users/api/internal/handler/user/updatePasswordByVcodeHandler.go +++ b/app/users/api/internal/handler/user/switchRoleHandler.go @@ -12,17 +12,17 @@ import ( "juwan-backend/app/users/api/internal/types" ) -// 修改密码-使用验证码 -func UpdatePasswordByVcodeHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { +// 切换当前激活角色 +func SwitchRoleHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - var req types.ResetPasswordByVcode + var req types.SwitchRoleReq if err := httpx.Parse(r, &req); err != nil { httpx.ErrorCtx(r.Context(), w, err) return } - l := user.NewUpdatePasswordByVcodeLogic(r.Context(), svcCtx) - resp, err := l.UpdatePasswordByVcode(&req) + l := user.NewSwitchRoleLogic(r.Context(), svcCtx) + resp, err := l.SwitchRole(&req) if err != nil { httpx.ErrorCtx(r.Context(), w, err) } else { diff --git a/app/users/api/internal/handler/user/unfollowUserHandler.go b/app/users/api/internal/handler/user/unfollowUserHandler.go new file mode 100644 index 0000000..7d7161b --- /dev/null +++ b/app/users/api/internal/handler/user/unfollowUserHandler.go @@ -0,0 +1,32 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.2 + +package user + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "juwan-backend/app/users/api/internal/logic/user" + "juwan-backend/app/users/api/internal/svc" + "juwan-backend/app/users/api/internal/types" +) + +// 取消关注用户 +func UnfollowUserHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.UnfollowUserReq + if err := httpx.Parse(r, &req); err != nil { + httpx.ErrorCtx(r.Context(), w, err) + return + } + + l := user.NewUnfollowUserLogic(r.Context(), svcCtx) + resp, err := l.UnfollowUser(&req) + if err != nil { + httpx.ErrorCtx(r.Context(), w, err) + } else { + httpx.OkJsonCtx(r.Context(), w, resp) + } + } +} diff --git a/app/users/api/internal/handler/user/updateNotificationSettingsHandler.go b/app/users/api/internal/handler/user/updateNotificationSettingsHandler.go new file mode 100644 index 0000000..5b8c3dd --- /dev/null +++ b/app/users/api/internal/handler/user/updateNotificationSettingsHandler.go @@ -0,0 +1,33 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.2 + +package user + +import ( + "net/http" + + "juwan-backend/app/users/api/internal/logic/user" + "juwan-backend/app/users/api/internal/svc" + "juwan-backend/app/users/api/internal/types" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +// 更新通知偏好 +func UpdateNotificationSettingsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.UpdateNotifySettingsReq + if err := httpx.Parse(r, &req); err != nil { + httpx.ErrorCtx(r.Context(), w, err) + return + } + + l := user.NewUpdateNotificationSettingsLogic(r.Context(), svcCtx) + resp, err := l.UpdateNotificationSettings(&req) + if err != nil { + httpx.ErrorCtx(r.Context(), w, err) + } else { + httpx.OkJsonCtx(r.Context(), w, resp) + } + } +} diff --git a/app/users/api/internal/handler/user/updateThemeSettingsHandler.go b/app/users/api/internal/handler/user/updateThemeSettingsHandler.go new file mode 100644 index 0000000..3c4d0fb --- /dev/null +++ b/app/users/api/internal/handler/user/updateThemeSettingsHandler.go @@ -0,0 +1,32 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.2 + +package user + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "juwan-backend/app/users/api/internal/logic/user" + "juwan-backend/app/users/api/internal/svc" + "juwan-backend/app/users/api/internal/types" +) + +// 更新主题偏好 +func UpdateThemeSettingsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.UpdateThemeSettingsReq + if err := httpx.Parse(r, &req); err != nil { + httpx.ErrorCtx(r.Context(), w, err) + return + } + + l := user.NewUpdateThemeSettingsLogic(r.Context(), svcCtx) + resp, err := l.UpdateThemeSettings(&req) + if err != nil { + httpx.ErrorCtx(r.Context(), w, err) + } else { + httpx.OkJsonCtx(r.Context(), w, resp) + } + } +} diff --git a/app/users/api/internal/handler/verification_admin/approveVerificationHandler.go b/app/users/api/internal/handler/verification_admin/approveVerificationHandler.go new file mode 100644 index 0000000..f9e0c1e --- /dev/null +++ b/app/users/api/internal/handler/verification_admin/approveVerificationHandler.go @@ -0,0 +1,32 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.2 + +package verification_admin + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "juwan-backend/app/users/api/internal/logic/verification_admin" + "juwan-backend/app/users/api/internal/svc" + "juwan-backend/app/users/api/internal/types" +) + +// 管理员通过申请 +func ApproveVerificationHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.VerificationIdReq + if err := httpx.Parse(r, &req); err != nil { + httpx.ErrorCtx(r.Context(), w, err) + return + } + + l := verification_admin.NewApproveVerificationLogic(r.Context(), svcCtx) + resp, err := l.ApproveVerification(&req) + if err != nil { + httpx.ErrorCtx(r.Context(), w, err) + } else { + httpx.OkJsonCtx(r.Context(), w, resp) + } + } +} diff --git a/app/users/api/internal/handler/verification_admin/getVerificationsHandler.go b/app/users/api/internal/handler/verification_admin/getVerificationsHandler.go new file mode 100644 index 0000000..4f1247f --- /dev/null +++ b/app/users/api/internal/handler/verification_admin/getVerificationsHandler.go @@ -0,0 +1,32 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.2 + +package verification_admin + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "juwan-backend/app/users/api/internal/logic/verification_admin" + "juwan-backend/app/users/api/internal/svc" + "juwan-backend/app/users/api/internal/types" +) + +// 管理员获取认证申请列表 (分页) +func GetVerificationsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.GetPendingListReq + if err := httpx.Parse(r, &req); err != nil { + httpx.ErrorCtx(r.Context(), w, err) + return + } + + l := verification_admin.NewGetVerificationsLogic(r.Context(), svcCtx) + resp, err := l.GetVerifications(&req) + if err != nil { + httpx.ErrorCtx(r.Context(), w, err) + } else { + httpx.OkJsonCtx(r.Context(), w, resp) + } + } +} diff --git a/app/users/api/internal/handler/verification_admin/rejectVerificationHandler.go b/app/users/api/internal/handler/verification_admin/rejectVerificationHandler.go new file mode 100644 index 0000000..932ef74 --- /dev/null +++ b/app/users/api/internal/handler/verification_admin/rejectVerificationHandler.go @@ -0,0 +1,32 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.2 + +package verification_admin + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "juwan-backend/app/users/api/internal/logic/verification_admin" + "juwan-backend/app/users/api/internal/svc" + "juwan-backend/app/users/api/internal/types" +) + +// 管理员驳回申请 +func RejectVerificationHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.RejectVerificationReq + if err := httpx.Parse(r, &req); err != nil { + httpx.ErrorCtx(r.Context(), w, err) + return + } + + l := verification_admin.NewRejectVerificationLogic(r.Context(), svcCtx) + resp, err := l.RejectVerification(&req) + if err != nil { + httpx.ErrorCtx(r.Context(), w, err) + } else { + httpx.OkJsonCtx(r.Context(), w, resp) + } + } +} diff --git a/app/users/api/internal/handler/verification_user/applyVerificationHandler.go b/app/users/api/internal/handler/verification_user/applyVerificationHandler.go new file mode 100644 index 0000000..fa92226 --- /dev/null +++ b/app/users/api/internal/handler/verification_user/applyVerificationHandler.go @@ -0,0 +1,32 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.2 + +package verification_user + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "juwan-backend/app/users/api/internal/logic/verification_user" + "juwan-backend/app/users/api/internal/svc" + "juwan-backend/app/users/api/internal/types" +) + +// 提交或修改角色认证申请 (支持幂等更新) +func ApplyVerificationHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.ApplyVerificationReq + if err := httpx.Parse(r, &req); err != nil { + httpx.ErrorCtx(r.Context(), w, err) + return + } + + l := verification_user.NewApplyVerificationLogic(r.Context(), svcCtx) + resp, err := l.ApplyVerification(&req) + if err != nil { + httpx.ErrorCtx(r.Context(), w, err) + } else { + httpx.OkJsonCtx(r.Context(), w, resp) + } + } +} diff --git a/app/users/api/internal/handler/verification_user/getMyVerificationsHandler.go b/app/users/api/internal/handler/verification_user/getMyVerificationsHandler.go new file mode 100644 index 0000000..86846d8 --- /dev/null +++ b/app/users/api/internal/handler/verification_user/getMyVerificationsHandler.go @@ -0,0 +1,25 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.2 + +package verification_user + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "juwan-backend/app/users/api/internal/logic/verification_user" + "juwan-backend/app/users/api/internal/svc" +) + +// 获取我的所有认证状态 +func GetMyVerificationsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + l := verification_user.NewGetMyVerificationsLogic(r.Context(), svcCtx) + resp, err := l.GetMyVerifications() + if err != nil { + httpx.ErrorCtx(r.Context(), w, err) + } else { + httpx.OkJsonCtx(r.Context(), w, resp) + } + } +} diff --git a/app/users/api/internal/logic/auth/forgotPasswordLogic.go b/app/users/api/internal/logic/auth/forgotPasswordLogic.go new file mode 100644 index 0000000..49ead4f --- /dev/null +++ b/app/users/api/internal/logic/auth/forgotPasswordLogic.go @@ -0,0 +1,33 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.2 + +package auth + +import ( + "context" + "juwan-backend/app/users/api/internal/svc" + "juwan-backend/app/users/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type ForgotPasswordLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// 忘记密码-发送验证码 +func NewForgotPasswordLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ForgotPasswordLogic { + return &ForgotPasswordLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *ForgotPasswordLogic) ForgotPassword(req *types.ForgotPasswordReq) (resp *types.EmptyResp, err error) { + // todo: add your logic here and delete this line + + return +} diff --git a/app/users/api/internal/logic/user/loginLogic.go b/app/users/api/internal/logic/auth/loginLogic.go similarity index 87% rename from app/users/api/internal/logic/user/loginLogic.go rename to app/users/api/internal/logic/auth/loginLogic.go index 1d12dca..c54d14a 100644 --- a/app/users/api/internal/logic/user/loginLogic.go +++ b/app/users/api/internal/logic/auth/loginLogic.go @@ -1,15 +1,15 @@ // Code scaffolded by goctl. Safe to edit. // goctl 1.9.2 -package user +package auth import ( "context" "errors" + "juwan-backend/app/users/rpc/usercenter" + "juwan-backend/app/users/api/internal/svc" "juwan-backend/app/users/api/internal/types" - "juwan-backend/app/users/rpc/usercenter" - "time" "github.com/zeromicro/go-zero/core/logx" ) @@ -20,7 +20,7 @@ type LoginLogic struct { svcCtx *svc.ServiceContext } -// 用户登录接口 +// 用户登录 func NewLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *LoginLogic { return &LoginLogic{ Logger: logx.WithContext(ctx), @@ -30,6 +30,7 @@ func NewLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *LoginLogic } func (l *LoginLogic) Login(req *types.LoginReq) (resp *types.LoginResp, err error) { + // todo: add your logic here and delete this line if len(req.Username) < 3 || len(req.Password) < 8 || len(req.Password) > 20 { return nil, errors.New("the information is illegal") } @@ -50,10 +51,8 @@ func (l *LoginLogic) Login(req *types.LoginReq) (resp *types.LoginResp, err erro } return &types.LoginResp{ - UserId: res.Id, - Username: res.Username, - Email: res.Email, - Token: res.Token, - Expires: int64((7 * 24 * time.Hour).Seconds()), + AccessToken: "", + RefreshToken: res.Token, + User: types.User{}, }, nil } diff --git a/app/users/api/internal/logic/user/logoutLogic.go b/app/users/api/internal/logic/auth/logoutLogic.go similarity index 64% rename from app/users/api/internal/logic/user/logoutLogic.go rename to app/users/api/internal/logic/auth/logoutLogic.go index b740558..eeab5ea 100644 --- a/app/users/api/internal/logic/user/logoutLogic.go +++ b/app/users/api/internal/logic/auth/logoutLogic.go @@ -1,15 +1,16 @@ // Code scaffolded by goctl. Safe to edit. // goctl 1.9.2 -package user +package auth import ( "context" "errors" + "juwan-backend/app/users/rpc/usercenter" + "juwan-backend/common/utils/contextx" "juwan-backend/app/users/api/internal/svc" "juwan-backend/app/users/api/internal/types" - "juwan-backend/app/users/rpc/usercenter" "github.com/zeromicro/go-zero/core/logx" ) @@ -20,7 +21,7 @@ type LogoutLogic struct { svcCtx *svc.ServiceContext } -// 用户登出 +// 退出登录 func NewLogoutLogic(ctx context.Context, svcCtx *svc.ServiceContext) *LogoutLogic { return &LogoutLogic{ Logger: logx.WithContext(ctx), @@ -29,15 +30,19 @@ func NewLogoutLogic(ctx context.Context, svcCtx *svc.ServiceContext) *LogoutLogi } } -func (l *LogoutLogic) Logout(req *types.LogoutReq) (resp *types.LogoutResp, err error) { - if req.UserId <= 0 { +func (l *LogoutLogic) Logout(_ *types.LogoutReq) (resp *types.EmptyResp, err error) { + // todo: add your logic here and delete this line + userId, err := contextx.UserIDFrom(l.ctx) + if err != nil { + return nil, errors.New("illegal id") + } + if userId <= 0 { return nil, errors.New("invalid userId") } - _, err = l.svcCtx.UserRpc.Logout(l.ctx, &usercenter.LogoutReq{UserId: req.UserId}) + _, err = l.svcCtx.UserRpc.Logout(l.ctx, &usercenter.LogoutReq{UserId: userId}) if err != nil { return nil, err } - - return &types.LogoutResp{Message: "logout success"}, nil + return &types.EmptyResp{}, nil } diff --git a/app/users/api/internal/logic/user/registerLogic.go b/app/users/api/internal/logic/auth/registerLogic.go similarity index 84% rename from app/users/api/internal/logic/user/registerLogic.go rename to app/users/api/internal/logic/auth/registerLogic.go index 0ba0a0a..e9e36d0 100644 --- a/app/users/api/internal/logic/user/registerLogic.go +++ b/app/users/api/internal/logic/auth/registerLogic.go @@ -1,19 +1,19 @@ // Code scaffolded by goctl. Safe to edit. // goctl 1.9.2 -package user +package auth import ( "context" "errors" - "juwan-backend/app/users/api/internal/contextx" + "juwan-backend/app/users/rpc/pb" + "juwan-backend/app/users/rpc/usercenter" + "juwan-backend/common/utils/contextx" + "juwan-backend/common/utils/pwdUtils" "regexp" "juwan-backend/app/users/api/internal/svc" "juwan-backend/app/users/api/internal/types" - "juwan-backend/app/users/rpc/pb" - "juwan-backend/app/users/rpc/usercenter" - "juwan-backend/common/utils" "github.com/zeromicro/go-zero/core/logx" ) @@ -24,7 +24,7 @@ type RegisterLogic struct { svcCtx *svc.ServiceContext } -// 用户注册接口 +// 用户注册 func NewRegisterLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RegisterLogic { return &RegisterLogic{ Logger: logx.WithContext(ctx), @@ -52,7 +52,7 @@ func (l *RegisterLogic) Register(req *types.RegisterReq) (resp *types.RegisterRe return nil, errors.New("user already exists") } - hashedPassword, err := utils.HashPassword(req.Password) + hashedPassword, err := pwdUtils.HashPassword(req.Password) if err != nil { return nil, errors.New("hash password failed") } @@ -63,7 +63,7 @@ func (l *RegisterLogic) Register(req *types.RegisterReq) (resp *types.RegisterRe return nil, errors.New("contextx.RequestIdFrom failed") } - _, err = l.svcCtx.UserRpc.Register(l.ctx, &usercenter.RegisterReq{ + res, err := l.svcCtx.UserRpc.Register(l.ctx, &usercenter.RegisterReq{ Username: req.Username, Passwd: hashedPassword, Phone: req.Username, @@ -78,9 +78,8 @@ func (l *RegisterLogic) Register(req *types.RegisterReq) (resp *types.RegisterRe // 返回响应 return &types.RegisterResp{ - UserId: 0, - Username: req.Username, - Email: req.Email, - Message: "register success", + AccessToken: "", + RefreshToken: res.Res, + User: types.User{}, }, nil } diff --git a/app/users/api/internal/logic/user/updatePasswordByVcodeLogic.go b/app/users/api/internal/logic/auth/resetPasswordLogic.go similarity index 64% rename from app/users/api/internal/logic/user/updatePasswordByVcodeLogic.go rename to app/users/api/internal/logic/auth/resetPasswordLogic.go index 8f64f51..1b9a767 100644 --- a/app/users/api/internal/logic/user/updatePasswordByVcodeLogic.go +++ b/app/users/api/internal/logic/auth/resetPasswordLogic.go @@ -1,14 +1,14 @@ // Code scaffolded by goctl. Safe to edit. // goctl 1.9.2 -package user +package auth import ( "context" "errors" - "juwan-backend/app/users/api/internal/contextx" "juwan-backend/app/users/rpc/usercenter" - "juwan-backend/common/utils" + "juwan-backend/common/utils/contextx" + "juwan-backend/common/utils/pwdUtils" "juwan-backend/app/users/api/internal/svc" "juwan-backend/app/users/api/internal/types" @@ -16,29 +16,28 @@ import ( "github.com/zeromicro/go-zero/core/logx" ) -type UpdatePasswordByVcodeLogic struct { +type ResetPasswordLogic struct { logx.Logger ctx context.Context svcCtx *svc.ServiceContext } -// 修改密码-使用验证码 -func NewUpdatePasswordByVcodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdatePasswordByVcodeLogic { - return &UpdatePasswordByVcodeLogic{ +// 重置密码 +func NewResetPasswordLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ResetPasswordLogic { + return &ResetPasswordLogic{ Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx, } } -func (l *UpdatePasswordByVcodeLogic) UpdatePasswordByVcode(req *types.ResetPasswordByVcode) (resp *types.EmptyResp, err error) { - // todo: add your logic here and delete this line +func (l *ResetPasswordLogic) ResetPassword(req *types.ResetPasswordReq) (resp *types.EmptyResp, err error) { requestId, err := contextx.RequestIdFrom(l.ctx) if err != nil { logx.Errorf("get request id from context failed, err:%v.", err) return nil, errors.New("bad request") } - hashedPassword, err := utils.HashPassword(req.Password) + hashedPassword, err := pwdUtils.HashPassword(req.NewPassword) if err != nil { logx.Errorf("hash password failed, err:%v.", err) return nil, errors.New("bad password") diff --git a/app/users/api/internal/logic/user/followUserLogic.go b/app/users/api/internal/logic/user/followUserLogic.go new file mode 100644 index 0000000..49bd0cf --- /dev/null +++ b/app/users/api/internal/logic/user/followUserLogic.go @@ -0,0 +1,34 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.2 + +package user + +import ( + "context" + + "juwan-backend/app/users/api/internal/svc" + "juwan-backend/app/users/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type FollowUserLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// 关注用户 +func NewFollowUserLogic(ctx context.Context, svcCtx *svc.ServiceContext) *FollowUserLogic { + return &FollowUserLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *FollowUserLogic) FollowUser(req *types.FollowUserReq) (resp *types.EmptyResp, err error) { + // todo: add your logic here and delete this line + + return +} diff --git a/app/users/api/internal/logic/user/getMeLogic.go b/app/users/api/internal/logic/user/getMeLogic.go index a291f33..9dcfb8a 100644 --- a/app/users/api/internal/logic/user/getMeLogic.go +++ b/app/users/api/internal/logic/user/getMeLogic.go @@ -6,9 +6,10 @@ package user import ( "context" "errors" - "juwan-backend/app/users/api/internal/contextx" "juwan-backend/app/users/rpc/usercenter" "juwan-backend/common/converter" + "juwan-backend/common/utils/contextx" + "time" "juwan-backend/app/users/api/internal/svc" "juwan-backend/app/users/api/internal/types" @@ -31,7 +32,7 @@ func NewGetMeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetMeLogic } } -func (l *GetMeLogic) GetMe() (resp *types.UserInfo, err error) { +func (l *GetMeLogic) GetMe() (resp *types.User, err error) { userId, err := contextx.UserIDFrom(l.ctx) if err != nil { return nil, errors.New("illegal id") @@ -43,6 +44,8 @@ func (l *GetMeLogic) GetMe() (resp *types.UserInfo, err error) { return nil, errors.New("get user by id error") } err = converter.StructToStruct(user, &resp) + createAt := time.Unix(user.Users.CreatedAt, 0) + resp.CreatedAt = createAt.Format(time.DateTime) if err != nil { return nil, errors.New("to struct error") } diff --git a/app/users/api/internal/logic/user/getUserInfoLogic.go b/app/users/api/internal/logic/user/getUserInfoLogic.go index e427495..d5b244d 100644 --- a/app/users/api/internal/logic/user/getUserInfoLogic.go +++ b/app/users/api/internal/logic/user/getUserInfoLogic.go @@ -6,12 +6,11 @@ package user import ( "context" "errors" - "juwan-backend/app/users/rpc/usercenter" - "juwan-backend/common/converter" - "juwan-backend/app/users/api/internal/svc" "juwan-backend/app/users/api/internal/types" + "juwan-backend/app/users/rpc/usercenter" + "github.com/jinzhu/copier" "github.com/zeromicro/go-zero/core/logx" ) @@ -30,21 +29,21 @@ func NewGetUserInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetUs } } -func (l *GetUserInfoLogic) GetUserInfo(req *types.GetUserInfoReq) (resp types.UserInfo, err error) { - +func (l *GetUserInfoLogic) GetUserInfo(req *types.GetUserReq) (resp types.User, err error) { + pbUser, err := l.svcCtx.UserRpc.GetUsersById(l.ctx, &usercenter.GetUsersByIdReq{ - Id: req.UserId, + Id: req.Id, }) if err != nil { - return types.UserInfo{}, errors.New("failed to get user info by userid") - } - user := types.UserInfo{} - err = converter.StructToStruct(&pbUser.Users, &user) - if err != nil { - logx.Errorf("struct to user info failed, err:%v.", err) - return types.UserInfo{}, errors.New("failed to get user info by userid") + return types.User{}, errors.New("failed to get user info by userid") + } + + user := types.User{} + err = copier.Copy(&user, &pbUser.Users) + if err != nil { + logx.Errorf("struct to user info failed, err:%v.", err) + return types.User{}, errors.New("failed to get user info by userid") } - //req.UserId return user, nil } diff --git a/app/users/api/internal/logic/user/switchRoleLogic.go b/app/users/api/internal/logic/user/switchRoleLogic.go new file mode 100644 index 0000000..66bf3c1 --- /dev/null +++ b/app/users/api/internal/logic/user/switchRoleLogic.go @@ -0,0 +1,51 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.2 + +package user + +import ( + "context" + "errors" + "juwan-backend/app/users/rpc/usercenter" + "juwan-backend/common/utils/contextx" + + "juwan-backend/app/users/api/internal/svc" + "juwan-backend/app/users/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" + "google.golang.org/protobuf/proto" +) + +type SwitchRoleLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// 切换当前激活角色 +func NewSwitchRoleLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SwitchRoleLogic { + return &SwitchRoleLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *SwitchRoleLogic) SwitchRole(req *types.SwitchRoleReq) (resp *types.EmptyResp, err error) { + // todo: add your logic here and delete this line + id, err := contextx.UserIDFrom(l.ctx) + if err != nil { + logx.Errorf("get user id from context: %v", err) + return nil, errors.New("illegal id") + } + _, err = l.svcCtx.UserRpc.UpdateUsers(l.ctx, &usercenter.UpdateUsersReq{ + Id: id, + CurrentRole: proto.String(req.Role), + }) + + if err != nil { + logx.Errorf("update user info by id: %v", err) + return nil, errors.New("update user info by userid") + } + return +} diff --git a/app/users/api/internal/logic/user/unfollowUserLogic.go b/app/users/api/internal/logic/user/unfollowUserLogic.go new file mode 100644 index 0000000..3fb1cdd --- /dev/null +++ b/app/users/api/internal/logic/user/unfollowUserLogic.go @@ -0,0 +1,34 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.2 + +package user + +import ( + "context" + + "juwan-backend/app/users/api/internal/svc" + "juwan-backend/app/users/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type UnfollowUserLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// 取消关注用户 +func NewUnfollowUserLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UnfollowUserLogic { + return &UnfollowUserLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *UnfollowUserLogic) UnfollowUser(req *types.UnfollowUserReq) (resp *types.EmptyResp, err error) { + // todo: add your logic here and delete this line + + return +} diff --git a/app/users/api/internal/logic/user/updateMeLogic.go b/app/users/api/internal/logic/user/updateMeLogic.go index 4690fb9..1ceda46 100644 --- a/app/users/api/internal/logic/user/updateMeLogic.go +++ b/app/users/api/internal/logic/user/updateMeLogic.go @@ -6,9 +6,10 @@ package user import ( "context" "errors" - "juwan-backend/app/users/api/internal/contextx" "juwan-backend/app/users/rpc/usercenter" "juwan-backend/common/converter" + "juwan-backend/common/utils/contextx" + "strings" "juwan-backend/app/users/api/internal/svc" "juwan-backend/app/users/api/internal/types" @@ -31,16 +32,16 @@ func NewUpdateMeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateMe } } -func (l *UpdateMeLogic) UpdateMe(req *types.UpdateUserInfoReq) (resp *types.UserInfo, err error) { +func (l *UpdateMeLogic) UpdateMe(req *types.UpdateUserProfileReq) (resp *types.UpdateUserProfileReq, err error) { userId, err := contextx.UserIDFrom(l.ctx) if err != nil { return nil, err } res, err := l.svcCtx.UserRpc.UpdateUsers(l.ctx, &usercenter.UpdateUsersReq{ Id: userId, - Nickname: req.Nickname, - Avatar: req.Avatar, - Bio: req.Bio, + Nickname: proto_string(req.Nickname), + Avatar: proto_string(req.Avatar), + Bio: proto_string(req.Bio), VerifiedRoles: nil, }) if err != nil { @@ -53,3 +54,10 @@ func (l *UpdateMeLogic) UpdateMe(req *types.UpdateUserInfoReq) (resp *types.User } return } + +func proto_string(s string) *string { + if len(s) == 0 || strings.Contains(s, " ") { + return nil + } + return &s +} diff --git a/app/users/api/internal/logic/user/updateNotificationSettingsLogic.go b/app/users/api/internal/logic/user/updateNotificationSettingsLogic.go new file mode 100644 index 0000000..c1c3161 --- /dev/null +++ b/app/users/api/internal/logic/user/updateNotificationSettingsLogic.go @@ -0,0 +1,34 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.2 + +package user + +import ( + "context" + + "juwan-backend/app/users/api/internal/svc" + "juwan-backend/app/users/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type UpdateNotificationSettingsLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// 更新通知偏好 +func NewUpdateNotificationSettingsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateNotificationSettingsLogic { + return &UpdateNotificationSettingsLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *UpdateNotificationSettingsLogic) UpdateNotificationSettings(req *types.UpdateNotifySettingsReq) (resp *types.EmptyResp, err error) { + // todo: add your logic here and delete this line + + return +} diff --git a/app/users/api/internal/logic/user/updatePasswordLogic.go b/app/users/api/internal/logic/user/updatePasswordLogic.go deleted file mode 100644 index 790c55a..0000000 --- a/app/users/api/internal/logic/user/updatePasswordLogic.go +++ /dev/null @@ -1,72 +0,0 @@ -// Code scaffolded by goctl. Safe to edit. -// goctl 1.9.2 - -package user - -import ( - "context" - "errors" - "juwan-backend/app/users/api/internal/contextx" - "juwan-backend/app/users/rpc/usercenter" - "juwan-backend/common/utils" - - "juwan-backend/app/users/api/internal/svc" - "juwan-backend/app/users/api/internal/types" - - "github.com/zeromicro/go-zero/core/logx" -) - -var ChangeUserPassFailed = errors.New("change user pass failed") - -type UpdatePasswordLogic struct { - logx.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -// 修改用户密码 -func NewUpdatePasswordLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdatePasswordLogic { - return &UpdatePasswordLogic{ - Logger: logx.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *UpdatePasswordLogic) UpdatePassword(req *types.UpdatePasswordReq) (resp *types.UpdatePasswordResp, err error) { - // todo: add your logic here and delete this line - userId, err := contextx.UserIDFrom(l.ctx) - if err != nil { - logx.Errorf("get user id from context failed, err:%v.", err) - return nil, ChangeUserPassFailed - } - - user, err := l.svcCtx.UserRpc.GetUsersById(l.ctx, &usercenter.GetUsersByIdReq{ - Id: userId, - }) - if err != nil { - logx.Errorf("get user info failed, err:%v.", err) - return nil, ChangeUserPassFailed - } - - oldPasswd, err := utils.HashPassword(req.OldPassword) - if err != nil { - logx.Errorf("hash old password failed, err:%v.", err) - return nil, ChangeUserPassFailed - } - - if oldPasswd != user.Users.PasswordHash { - return nil, ChangeUserPassFailed - } - - _, err = l.svcCtx.UserRpc.UpdateUsers(l.ctx, &usercenter.UpdateUsersReq{ - Id: userId, - Username: &user.Users.Username, - PasswordHash: &req.NewPassword, - }) - if err != nil { - logx.Errorf("update user password failed, err:%v.", err) - return nil, ChangeUserPassFailed - } - return -} diff --git a/app/users/api/internal/logic/user/updateThemeSettingsLogic.go b/app/users/api/internal/logic/user/updateThemeSettingsLogic.go new file mode 100644 index 0000000..8bc13d5 --- /dev/null +++ b/app/users/api/internal/logic/user/updateThemeSettingsLogic.go @@ -0,0 +1,34 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.2 + +package user + +import ( + "context" + + "juwan-backend/app/users/api/internal/svc" + "juwan-backend/app/users/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type UpdateThemeSettingsLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// 更新主题偏好 +func NewUpdateThemeSettingsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateThemeSettingsLogic { + return &UpdateThemeSettingsLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *UpdateThemeSettingsLogic) UpdateThemeSettings(req *types.UpdateThemeSettingsReq) (resp *types.EmptyResp, err error) { + // todo: add your logic here and delete this line + + return +} diff --git a/app/users/api/internal/logic/user/updateUserInfoLogic.go b/app/users/api/internal/logic/user/updateUserInfoLogic.go deleted file mode 100644 index d517698..0000000 --- a/app/users/api/internal/logic/user/updateUserInfoLogic.go +++ /dev/null @@ -1,48 +0,0 @@ -// Code scaffolded by goctl. Safe to edit. -// goctl 1.9.2 - -package user - -import ( - "context" - "errors" - "juwan-backend/app/users/api/internal/contextx" - "juwan-backend/app/users/rpc/usercenter" - - "juwan-backend/app/users/api/internal/svc" - "juwan-backend/app/users/api/internal/types" - - "github.com/zeromicro/go-zero/core/logx" -) - -type UpdateUserInfoLogic struct { - logx.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -// 修改用户信息 -func NewUpdateUserInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateUserInfoLogic { - return &UpdateUserInfoLogic{ - Logger: logx.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *UpdateUserInfoLogic) UpdateUserInfo(req *types.UpdateUserInfoReq) (resp *types.UpdateUserInfoResp, err error) { - userId, err := contextx.UserIDFrom(l.ctx) - if err != nil { - return nil, errors.New("user not found") - } - _, err = l.svcCtx.UserRpc.UpdateUsers(l.ctx, &usercenter.UpdateUsersReq{ - Id: userId, - Nickname: req.Nickname, - Avatar: req.Avatar, - Bio: req.Bio, - }) - if err != nil { - return nil, errors.New("update user info failed") - } - return -} diff --git a/app/users/api/internal/logic/verification_admin/approveVerificationLogic.go b/app/users/api/internal/logic/verification_admin/approveVerificationLogic.go new file mode 100644 index 0000000..6d9834d --- /dev/null +++ b/app/users/api/internal/logic/verification_admin/approveVerificationLogic.go @@ -0,0 +1,52 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.2 + +package verification_admin + +import ( + "context" + "juwan-backend/app/user_verifications/rpc/pb" + "juwan-backend/common/utils/contextx" + "time" + + "juwan-backend/app/users/api/internal/svc" + "juwan-backend/app/users/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type ApproveVerificationLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// 管理员通过申请 +func NewApproveVerificationLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ApproveVerificationLogic { + return &ApproveVerificationLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +var ( + APPROVE = "approved" +) + +func (l *ApproveVerificationLogic) ApproveVerification(req *types.VerificationIdReq) (resp *types.VerificationEmptyResp, err error) { + adminId, err := contextx.AdminIdFrom(l.ctx) + if err != nil { + return nil, err + } + _, err = l.svcCtx.UserVerificationsRpc.UpdateUserVerifications(l.ctx, &pb.UpdateUserVerificationsReq{ + Id: req.Id, + Status: &APPROVE, + ReviewedBy: adminId, + ReviewedAt: time.Now().Unix(), + }) + if err != nil { + return nil, err + } + return +} diff --git a/app/users/api/internal/logic/verification_admin/getVerificationsLogic.go b/app/users/api/internal/logic/verification_admin/getVerificationsLogic.go new file mode 100644 index 0000000..797eb34 --- /dev/null +++ b/app/users/api/internal/logic/verification_admin/getVerificationsLogic.go @@ -0,0 +1,66 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.2 + +package verification_admin + +import ( + "context" + "juwan-backend/app/user_verifications/rpc/pb" + "juwan-backend/common/utils/contextx" + "time" + + "juwan-backend/app/users/api/internal/svc" + "juwan-backend/app/users/api/internal/types" + + "github.com/jinzhu/copier" + "github.com/zeromicro/go-zero/core/logx" +) + +type GetVerificationsLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// 管理员获取认证申请列表 (分页) +func NewGetVerificationsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetVerificationsLogic { + return &GetVerificationsLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetVerificationsLogic) GetVerifications(req *types.GetPendingListReq) (resp *types.GetPendingListResp, err error) { + _, err = contextx.AdminIdFrom(l.ctx) + if err != nil { + return nil, err + } + verifications, err := l.svcCtx.UserVerificationsRpc.SearchUserVerifications(l.ctx, &pb.SearchUserVerificationsReq{ + Page: req.Page, + Limit: req.Size, + Role: req.Role, + Status: req.Status, + }) + if err != nil { + return nil, err + } + + var searchResults []types.VerificationItem + for _, v := range verifications.UserVerifications { + temp := types.VerificationItem{} + err = copier.Copy(&temp, v) + if err != nil { + logx.Errorf("copy verification item err: %s", err.Error()) + continue + } + temp.CreatedAt = time.Unix(v.CreatedAt, 0).Format(time.DateTime) + temp.ReviewedAt = time.Unix(v.ReviewedAt, 0).Format(time.DateTime) + searchResults = append(searchResults, temp) + } + resp = &types.GetPendingListResp{ + List: searchResults, + Total: 0, + } + return +} diff --git a/app/users/api/internal/logic/verification_admin/rejectVerificationLogic.go b/app/users/api/internal/logic/verification_admin/rejectVerificationLogic.go new file mode 100644 index 0000000..6da01f9 --- /dev/null +++ b/app/users/api/internal/logic/verification_admin/rejectVerificationLogic.go @@ -0,0 +1,52 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.2 + +package verification_admin + +import ( + "context" + "juwan-backend/app/user_verifications/rpc/pb" + "juwan-backend/common/utils/contextx" + "time" + + "juwan-backend/app/users/api/internal/svc" + "juwan-backend/app/users/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type RejectVerificationLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// 管理员驳回申请 +func NewRejectVerificationLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RejectVerificationLogic { + return &RejectVerificationLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +var REJECTED = "rejected" + +func (l *RejectVerificationLogic) RejectVerification(req *types.RejectVerificationReq) (resp *types.VerificationEmptyResp, err error) { + // todo: add your logic here and delete this line + adminId, err := contextx.AdminIdFrom(l.ctx) + if err != nil { + return nil, err + } + _, err = l.svcCtx.UserVerificationsRpc.UpdateUserVerifications(l.ctx, &pb.UpdateUserVerificationsReq{ + Id: req.Id, + Status: &REJECTED, + RejectReason: &req.Reason, + ReviewedBy: adminId, + ReviewedAt: time.Now().Unix(), + }) + if err != nil { + return nil, err + } + return +} diff --git a/app/users/api/internal/logic/verification_user/applyVerificationLogic.go b/app/users/api/internal/logic/verification_user/applyVerificationLogic.go new file mode 100644 index 0000000..b7c40f3 --- /dev/null +++ b/app/users/api/internal/logic/verification_user/applyVerificationLogic.go @@ -0,0 +1,68 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.2 + +package verification_user + +import ( + "context" + "encoding/json" + "errors" + "juwan-backend/app/user_verifications/rpc/pb" + "juwan-backend/app/users/api/internal/svc" + "juwan-backend/app/users/api/internal/types" + "juwan-backend/common/utils/contextx" + + "github.com/zeromicro/go-zero/core/logx" +) + +type ApplyVerificationLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// 提交或修改角色认证申请 (支持幂等更新) +func NewApplyVerificationLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ApplyVerificationLogic { + return &ApplyVerificationLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *ApplyVerificationLogic) ApplyVerification(req *types.ApplyVerificationReq) (resp *types.VerificationEmptyResp, err error) { + userId, err := contextx.UserIDFrom(l.ctx) + if err != nil { + logx.Errorf("get user id from context: %v", err) + return nil, contextx.ERRILLEGALUSER + } + verifications, err := l.svcCtx.UserVerificationsRpc.SearchUserVerifications(l.ctx, &pb.SearchUserVerificationsReq{ + UserId: userId, + }) + if err != nil { + logx.Errorf("search user verifications: %v", err) + return nil, errors.New("search user verifications failed") + } + + materials, err := json.Marshal(req.Materials) + if err != nil { + logx.Errorf("marshal materials: %v", err) + return nil, err + } + + if verifications == nil || len(verifications.UserVerifications) == 0 { + // 如果没有则增加 + _, err = l.svcCtx.UserVerificationsRpc.AddUserVerifications(l.ctx, &pb.AddUserVerificationsReq{ + Role: req.Role, + Materials: string(materials), + }) + if err != nil { + logx.Errorf("add user verifications: %v", err) + return nil, errors.New("add user verifications failed") + } + } else { + + } + + return &types.VerificationEmptyResp{}, nil +} diff --git a/app/users/api/internal/logic/verification_user/getMyVerificationsLogic.go b/app/users/api/internal/logic/verification_user/getMyVerificationsLogic.go new file mode 100644 index 0000000..b03bfd2 --- /dev/null +++ b/app/users/api/internal/logic/verification_user/getMyVerificationsLogic.go @@ -0,0 +1,68 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.2 + +package verification_user + +import ( + "context" + "juwan-backend/app/user_verifications/rpc/pb" + "juwan-backend/common/utils/contextx" + "time" + + "juwan-backend/app/users/api/internal/svc" + "juwan-backend/app/users/api/internal/types" + + "github.com/jinzhu/copier" + "github.com/zeromicro/go-zero/core/logx" +) + +type GetMyVerificationsLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// 获取我的所有认证状态 +func NewGetMyVerificationsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetMyVerificationsLogic { + return &GetMyVerificationsLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetMyVerificationsLogic) GetMyVerifications() (resp *types.GetMyVerificationsResp, err error) { + // todo: add your logic here and delete this line + userId, err := contextx.UserIDFrom(l.ctx) + if err != nil { + logx.Errorf("get user id from context: %v", err) + return nil, contextx.ERRILLEGALUSER + } + + verifications, err := l.svcCtx.UserVerificationsRpc.SearchUserVerifications(l.ctx, &pb.SearchUserVerificationsReq{ + UserId: userId, + Page: 1, + Limit: 100, // assuming a user won't have more than 100 verification records, adjust as needed + }) + if err != nil { + return nil, err + } + + var searchResults []types.VerificationItem + for _, v := range verifications.UserVerifications { + temp := types.VerificationItem{} + err = copier.Copy(&temp, v) + if err != nil { + logx.Errorf("copy verification item err: %s", err.Error()) + continue + } + temp.CreatedAt = time.Unix(v.CreatedAt, 0).Format(time.DateTime) + temp.ReviewedAt = time.Unix(v.ReviewedAt, 0).Format(time.DateTime) + searchResults = append(searchResults, temp) + } + + resp = &types.GetMyVerificationsResp{ + List: searchResults, + } + return +} diff --git a/app/users/api/internal/middleware/jwtauthMiddleware.go b/app/users/api/internal/middleware/jwtauthMiddleware.go new file mode 100644 index 0000000..c51e5ba --- /dev/null +++ b/app/users/api/internal/middleware/jwtauthMiddleware.go @@ -0,0 +1,22 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.2 + +package middleware + +import "net/http" + +type JwtAuthMiddleware struct { +} + +func NewJwtAuthMiddleware() *JwtAuthMiddleware { + return &JwtAuthMiddleware{} +} + +func (m *JwtAuthMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // TODO generate middleware implement function, delete after code implementation + + // Passthrough to next handler if need + next(w, r) + } +} diff --git a/app/users/api/internal/svc/serviceContext.go b/app/users/api/internal/svc/serviceContext.go index b83e976..5b6c751 100644 --- a/app/users/api/internal/svc/serviceContext.go +++ b/app/users/api/internal/svc/serviceContext.go @@ -4,28 +4,27 @@ package svc import ( - "juwan-backend/app/snowflake/rpc/snowflake" + "juwan-backend/app/user_verifications/rpc/userverifications" "juwan-backend/app/users/api/internal/config" "juwan-backend/app/users/api/internal/middleware" "juwan-backend/app/users/rpc/usercenter" - "juwan-backend/common/snowflakex" "github.com/zeromicro/go-zero/rest" "github.com/zeromicro/go-zero/zrpc" ) type ServiceContext struct { - Config config.Config - Logger rest.Middleware - UserRpc usercenter.Usercenter - SnowflakeRpc snowflake.SnowflakeServiceClient + Config config.Config + Logger rest.Middleware + UserRpc usercenter.Usercenter + UserVerificationsRpc userverifications.UserVerificationsZrpcClient } func NewServiceContext(c config.Config) *ServiceContext { return &ServiceContext{ - Config: c, - Logger: middleware.NewLoggerMiddleware().Handle, - UserRpc: usercenter.NewUsercenter(zrpc.MustNewClient(c.UsercenterRpcConf)), - SnowflakeRpc: snowflakex.NewClient(c.SnowflakeRpcConf), + Config: c, + Logger: middleware.NewLoggerMiddleware().Handle, + UserRpc: usercenter.NewUsercenter(zrpc.MustNewClient(c.UsercenterRpcConf)), + UserVerificationsRpc: userverifications.NewUserVerificationsZrpcClient(zrpc.MustNewClient(c.UserVerificationRpc)), } } diff --git a/app/users/api/internal/types/types.go b/app/users/api/internal/types/types.go index f6944b4..00eee78 100644 --- a/app/users/api/internal/types/types.go +++ b/app/users/api/internal/types/types.go @@ -3,89 +3,137 @@ package types +type ApplyVerificationReq struct { + Role string `json:"role"` // 申请什么角色 + Materials map[string]string `json:"materials"` // 证明材料键值对 {"idCardFront": "http...", "license": "http..."} +} + type EmptyResp struct { } -type ErrorResp struct { - Code int `json:"code"` - Message string `json:"message"` +type FollowUserReq struct { + Id int64 `path:"id"` } -type GetUserInfoReq struct { - UserId int64 `path:"userId" binding:"required,gt=0"` +type ForgotPasswordReq struct { + Phone string `json:"phone,omitempty"` + Email string `json:"email,omitempty"` +} + +type GetMyVerificationsResp struct { + List []VerificationItem `json:"list"` +} + +type GetPendingListReq struct { + Page int64 `form:"page,default=1"` + Size int64 `form:"size,default=20"` + Role string `form:"role,optional"` // 筛选角色 + Status string `form:"status,optional"` // 筛选状态,默认 pending +} + +type GetPendingListResp struct { + List []VerificationItem `json:"list"` + Total int64 `json:"total"` +} + +type GetUserReq struct { + Id int64 `path:"id"` } type LoginReq struct { - Username string `json:"username" binding:"required"` - Password string `json:"password" binding:"required"` + Phone string `json:"phone,omitempty"` // 手机号登录 + Username string `json:"username,omitempty"` // 或用户名登录 + Password string `json:"password"` + Remember bool `json:"remember,optional"` } type LoginResp struct { - UserId int64 `json:"userId"` - Username string `json:"username"` - Email string `json:"email"` - Token string `json:"token"` - Expires int64 `json:"expires"` + AccessToken string `json:"accessToken"` + RefreshToken string `json:"refreshToken"` + User User `json:"user"` } type LogoutReq struct { - UserId int64 `path:"userId" binding:"required,gt=0"` - Token string `header:"Authorization" binding:"required"` -} - -type LogoutResp struct { - Message string `json:"message"` } type RegisterReq struct { - Username string `json:"username" binding:"required,min=3,max=50"` - Password string `json:"password" binding:"required,min=6,max=128"` - Email string `json:"email,omitempty" binding:"omitempty,email"` - Phone string `json:"phone,omitempty" binding:"omitempty,len=11"` - Vcode int32 `json:"vcode"` + Phone string `json:"phone,omitempty"` + Email string `json:"email,omitempty"` + Username string `json:"username"` + Password string `json:"password"` + Vcode string `json:"vcode,omitempty"` // 验证码 } type RegisterResp struct { - UserId int64 `json:"userId"` - Username string `json:"username"` - Email string `json:"email"` - Message string `json:"message"` + AccessToken string `json:"accessToken"` + RefreshToken string `json:"refreshToken"` + User User `json:"user"` } -type ResetPasswordByVcode struct { - Email string `json:"email"` - Password string `json:"password"` - Vcode string `json:"vcode"` +type RejectVerificationReq struct { + Id int64 `path:"id"` + Reason string `json:"reason"` // 必填:驳回原因 } -type UpdatePasswordReq struct { - UserId int64 `path:"userId" binding:"required,gt=0"` - OldPassword string `json:"oldPassword" binding:"required"` - NewPassword string `json:"newPassword" binding:"required,min=6,max=128"` +type ResetPasswordReq struct { + Phone string `json:"phone,omitempty"` + Email string `json:"email,omitempty"` + Vcode string `json:"vcode"` + NewPassword string `json:"newPassword"` } -type UpdatePasswordResp struct { - Message string `json:"message"` +type SwitchRoleReq struct { + Role string `json:"role"` // 目标角色 } -type UpdateUserInfoReq struct { - Nickname *string `json:"nickname,omitempty"` - Avatar *string `json:"avatar,omitempty"` - Bio *string `json:"bio,omitempty"` +type UnfollowUserReq struct { + Id int64 `path:"id"` } -type UpdateUserInfoResp struct { - UserId int64 `json:"userId"` - Message string `json:"message"` +type UpdateNotifySettingsReq struct { + Order bool `json:"order,optional"` + Community bool `json:"community,optional"` + System bool `json:"system,optional"` } -type UserInfo struct { - UserId int64 `json:"userId"` - Username string `json:"username"` - Email string `json:"email"` - Phone string `json:"phone"` - Avatar string `json:"avatar"` - Status int `json:"status"` - CreateAt int64 `json:"createAt"` - UpdateAt int64 `json:"updateAt"` +type UpdateThemeSettingsReq struct { + Theme string `json:"theme"` // dark, light +} + +type UpdateUserProfileReq struct { + Nickname string `json:"nickname,optional"` + Avatar string `json:"avatar,optional"` + Bio string `json:"bio,optional"` +} + +type User struct { + Id int64 `json:"id"` + Username string `json:"username"` + Nickname string `json:"nickname"` + Avatar string `json:"avatar"` + Role string `json:"role"` // consumer, player, owner, admin + VerifiedRoles []string `json:"verifiedRoles"` // e.g. ["consumer", "player"] + VerificationStatus map[string]string `json:"verificationStatus"` // e.g. {"player": "approved"} + Phone string `json:"phone,omitempty"` + Bio string `json:"bio,omitempty"` + CreatedAt string `json:"createdAt"` // ISO 8601 +} + +type VerificationEmptyResp struct { +} + +type VerificationIdReq struct { + Id int64 `path:"id"` // 注意:这是 user_verifications.id +} + +type VerificationItem struct { + Id int64 `json:"id"` // 认证记录ID (主键,用于管理员操作) + UserId int64 `json:"userId"` // 申请人ID (外键) + UserNickname string `json:"userNickname"` // 冗余显示,方便前端展示 + Role string `json:"role"` // 申请角色: player, owner + Status string `json:"status"` // pending, approved, rejected + Materials map[string]string `json:"materials"` // 核心字段:对应 DB 的 JSONB + RejectReason string `json:"rejectReason"` // 驳回原因 + CreatedAt string `json:"createdAt"` // 申请时间 + ReviewedAt string `json:"reviewedAt"` // 审核时间 } diff --git a/app/users/rpc/internal/logic/loginLogic.go b/app/users/rpc/internal/logic/loginLogic.go index 93f7caf..5f21188 100644 --- a/app/users/rpc/internal/logic/loginLogic.go +++ b/app/users/rpc/internal/logic/loginLogic.go @@ -7,7 +7,7 @@ import ( "juwan-backend/app/users/rpc/internal/svc" utils2 "juwan-backend/app/users/rpc/internal/utils" "juwan-backend/app/users/rpc/pb" - "juwan-backend/common/utils" + "juwan-backend/common/utils/pwdUtils" "github.com/zeromicro/go-zero/core/logx" ) @@ -36,7 +36,7 @@ func (l *LoginLogic) Login(in *pb.LoginReq) (*pb.LoginResp, error) { return nil, err } logx.Infof("user:%v", user) - if !utils.VerifyPassword(user.PasswordHash, in.Passwd) { + if !pwdUtils.VerifyPassword(user.PasswordHash, in.Passwd) { logx.WithContext(l.ctx).Errorf("User %s Login failed", user.Username) return nil, errors.New("incorrect password") } diff --git a/app/users/rpc/internal/logic/registerLogic.go b/app/users/rpc/internal/logic/registerLogic.go index 0c33d2f..34b5416 100644 --- a/app/users/rpc/internal/logic/registerLogic.go +++ b/app/users/rpc/internal/logic/registerLogic.go @@ -8,9 +8,9 @@ import ( "fmt" "juwan-backend/app/snowflake/rpc/snowflake" "juwan-backend/app/users/rpc/internal/svc" + "juwan-backend/app/users/rpc/internal/utils" "juwan-backend/app/users/rpc/pb" "juwan-backend/common/redisx" - "strconv" "strings" "github.com/zeromicro/go-zero/core/logx" @@ -56,8 +56,7 @@ func (l *RegisterLogic) Register(in *pb.RegisterReq) (*pb.RegisterResp, error) { return nil, errors.New("invalid verification code") } - code, err := strconv.ParseInt(vcode, 10, 32) - if err != nil || int32(code) != in.Vcode { + if vcode != in.Vcode { logx.Error("invalid verification code") return nil, errors.New("invalid verification code") } @@ -80,7 +79,16 @@ func (l *RegisterLogic) Register(in *pb.RegisterReq) (*pb.RegisterResp, error) { return nil, err } + token, err := l.svcCtx.JwtManager.New(l.ctx, &utils.TokenPayload{ + UserId: resp.Id, + IsAdmin: false, + }) + if err != nil { + logx.Errorf("generate token failed, err:%v", err) + return nil, errors.New("generate token failed, but user registered successfully") + } + return &pb.RegisterResp{ - Res: "user registered successfully", + Res: token, }, nil } diff --git a/app/users/rpc/internal/logic/searchUsersLogic.go b/app/users/rpc/internal/logic/searchUsersLogic.go index 4e518e0..2dd4a1c 100644 --- a/app/users/rpc/internal/logic/searchUsersLogic.go +++ b/app/users/rpc/internal/logic/searchUsersLogic.go @@ -30,6 +30,14 @@ func NewSearchUsersLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Searc var SearUsersErr = errors.New("search users failed") func (l *SearchUsersLogic) SearchUsers(in *pb.SearchUsersReq) (out *pb.SearchUsersResp, err error) { + if in.Page == nil || *in.Page < 0 { + logx.Errorf("Invalid page number: %v", in.Page) + return nil, errors.New("invalid page number") + } + if *in.Limit > 1000 { + logx.Errorf("Limit exceeds max limit: %d", in.Limit) + return nil, errors.New("limit exceeds max limit") + } user, err := l.svcCtx.UsersModelRO.Query(). Where(users.Or( users.UsernameContainsFold(*in.Username), @@ -37,6 +45,8 @@ func (l *SearchUsersLogic) SearchUsers(in *pb.SearchUsersReq) (out *pb.SearchUse users.EmailContainsFold(*in.Username), users.CurrentRole(*in.CurrentRole), )). + Offset(int(*in.Page * *in.Limit)). + Limit(int(*in.Limit)). All(l.ctx) if err != nil { logx.Errorf("search users failed, err:%v.", err) diff --git a/app/users/rpc/internal/models/users.go b/app/users/rpc/internal/models/users.go index 3c01873..5b7a5ab 100644 --- a/app/users/rpc/internal/models/users.go +++ b/app/users/rpc/internal/models/users.go @@ -14,7 +14,7 @@ import ( "entgo.io/ent/dialect/sql" ) -// Users is the model entity for the Users schema. +// Users is the models entity for the Users schema. type Users struct { config `json:"-"` // ID of the ent. diff --git a/app/users/rpc/pb/users.pb.go b/app/users/rpc/pb/users.pb.go index ff0d0d2..eb133db 100644 --- a/app/users/rpc/pb/users.pb.go +++ b/app/users/rpc/pb/users.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.11 -// protoc v3.19.4 +// protoc v5.29.6 // source: users.proto package pb @@ -1201,10 +1201,10 @@ func (x *ValidateTokenReq) GetUserId() int64 { type ValidateTokenResp struct { state protoimpl.MessageState `protogen:"open.v1"` - Valid bool `protobuf:"varint,1,opt,name=valid,proto3" json:"valid,omitempty"` // token 是否有效(不在黑名单中) - Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` // 验证失败原因 - UserId int64 `protobuf:"varint,3,opt,name=userId,proto3" json:"userId,omitempty"` // 用户ID - RoleType string `protobuf:"varint,4,opt,name=roleType,proto3" json:"roleType,omitempty"` // 用户角色 + Valid bool `protobuf:"varint,1,opt,name=valid,proto3" json:"valid,omitempty"` // token 是否有效(不在黑名单中) + Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` // 验证失败原因 + UserId int64 `protobuf:"varint,3,opt,name=userId,proto3" json:"userId,omitempty"` // 用户ID + RoleType string `protobuf:"bytes,4,opt,name=roleType,proto3" json:"roleType,omitempty"` // 用户角色 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1384,7 +1384,7 @@ type RegisterReq struct { Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"` Passwd string `protobuf:"bytes,2,opt,name=passwd,proto3" json:"passwd,omitempty"` Phone string `protobuf:"bytes,3,opt,name=phone,proto3" json:"phone,omitempty"` - Vcode int32 `protobuf:"varint,4,opt,name=vcode,proto3" json:"vcode,omitempty"` + Vcode string `protobuf:"bytes,4,opt,name=vcode,proto3" json:"vcode,omitempty"` Email string `protobuf:"bytes,5,opt,name=email,proto3" json:"email,omitempty"` RequestId string `protobuf:"bytes,6,opt,name=requestId,proto3" json:"requestId,omitempty"` unknownFields protoimpl.UnknownFields @@ -1442,11 +1442,11 @@ func (x *RegisterReq) GetPhone() string { return "" } -func (x *RegisterReq) GetVcode() int32 { +func (x *RegisterReq) GetVcode() string { if x != nil { return x.Vcode } - return 0 + return "" } func (x *RegisterReq) GetEmail() string { @@ -1834,7 +1834,7 @@ const file_users_proto_rawDesc = "" + "\x05valid\x18\x01 \x01(\bR\x05valid\x12\x18\n" + "\amessage\x18\x02 \x01(\tR\amessage\x12\x16\n" + "\x06userId\x18\x03 \x01(\x03R\x06userId\x12\x1a\n" + - "\broleType\x18\x04 \x01(\x03R\broleType\"`\n" + + "\broleType\x18\x04 \x01(\tR\broleType\"`\n" + "\x12CheckPermissionReq\x12\x16\n" + "\x06userId\x18\x01 \x01(\x03R\x06userId\x12\x1a\n" + "\bresource\x18\x02 \x01(\tR\bresource\x12\x16\n" + @@ -1846,7 +1846,7 @@ const file_users_proto_rawDesc = "" + "\busername\x18\x01 \x01(\tR\busername\x12\x16\n" + "\x06passwd\x18\x02 \x01(\tR\x06passwd\x12\x14\n" + "\x05phone\x18\x03 \x01(\tR\x05phone\x12\x14\n" + - "\x05vcode\x18\x04 \x01(\x05R\x05vcode\x12\x14\n" + + "\x05vcode\x18\x04 \x01(\tR\x05vcode\x12\x14\n" + "\x05email\x18\x05 \x01(\tR\x05email\x12\x1c\n" + "\trequestId\x18\x06 \x01(\tR\trequestId\" \n" + "\fRegisterResp\x12\x10\n" + diff --git a/app/users/rpc/pb/users_grpc.pb.go b/app/users/rpc/pb/users_grpc.pb.go index 2918bff..4f7a28e 100644 --- a/app/users/rpc/pb/users_grpc.pb.go +++ b/app/users/rpc/pb/users_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.6.1 -// - protoc v3.19.4 +// - protoc v5.29.6 // source: users.proto package pb diff --git a/cnpg_for_specific_namespace.yaml b/backup/cnpg_for_specific_namespace.yaml similarity index 100% rename from cnpg_for_specific_namespace.yaml rename to backup/cnpg_for_specific_namespace.yaml diff --git a/common/converter/README.md b/common/converter/README.md index df38334..38fbe67 100644 --- a/common/converter/README.md +++ b/common/converter/README.md @@ -40,7 +40,7 @@ func StructToStruct(src, dst interface{}) error ```go import "app/common/converter" -// 单个 model 转 pb +// 单个 models 转 pb user, _ := m.FindOne(ctx, userId) pbUser := &pb.Users{} converter.StructToStruct(user, pbUser) @@ -63,7 +63,7 @@ func SliceToSlice(src interface{}, dstSliceType interface{}) (interface{}, error **示例:** ```go -// 多个 model 转 pb +// 多个 models 转 pb users := []*models.Users{user1, user2, user3} pbUsersIface, _ := converter.SliceToSlice(users, []*pb.Users{}) pbUsers := pbUsersIface.([]*pb.Users) diff --git a/common/converter/generic.go b/common/converter/generic.go index ca2f31b..5fdc89d 100644 --- a/common/converter/generic.go +++ b/common/converter/generic.go @@ -1,207 +1,207 @@ -package converter - -import ( - "database/sql" - "reflect" - "time" -) - -// StructToStruct 通用结构体转换函数,利用反射将源结构体的字段值复制到目标结构体 -// src: 源结构体(通常是 model) -// dst: 目标结构体(通常是 pb),必须是指针 -// 支持的自动转换: -// - time.Time -> int64 (Unix 时间戳) -// - sql.NullTime -> int64 (如果有效) -// - sql.NullInt64 -> int64 -// - sql.NullString -> string -// - 相同名称和兼容类型的字段 -func StructToStruct(src, dst interface{}) error { - if src == nil { - return nil - } - - srcVal := reflect.ValueOf(src) - dstVal := reflect.ValueOf(dst) - - // 确保 dst 是指针 - if dstVal.Kind() != reflect.Ptr { - return newError("destination must be a pointer") - } - - dstVal = dstVal.Elem() - - // 如果 src 是指针,解引用 - if srcVal.Kind() == reflect.Ptr { - srcVal = srcVal.Elem() - } - - // 都必须是结构体 - if srcVal.Kind() != reflect.Struct || dstVal.Kind() != reflect.Struct { - return newError("both source and destination must be structs") - } - - srcType := srcVal.Type() - - // 遍历源结构体的所有字段 - for i := 0; i < srcVal.NumField(); i++ { - srcField := srcVal.Field(i) - srcFieldName := srcType.Field(i).Name - - // 在目标结构体中查找同名字段 - dstField := dstVal.FieldByName(srcFieldName) - if !dstField.IsValid() || !dstField.CanSet() { - continue - } - - // 进行类型转换和赋值 - if err := assignValue(srcField, dstField); err != nil { - continue // 如果单个字段转换失败,继续处理其他字段 - } - } - - return nil -} - -// assignValue 尝试将源字段值赋给目标字段 -func assignValue(srcField, dstField reflect.Value) error { - // 如果是可直接赋值的类型 - if srcField.Type() == dstField.Type() { - dstField.Set(srcField) - return nil - } - - srcType := srcField.Type() - dstType := dstField.Type() - - // 处理 time.Time -> int64 的转换 - if srcType == reflect.TypeOf(time.Time{}) && dstType.Kind() == reflect.Int64 { - t := srcField.Interface().(time.Time) - dstField.SetInt(t.Unix()) - return nil - } - - // 处理 sql.NullTime -> int64 的转换 - if srcType == reflect.TypeOf(sql.NullTime{}) && dstType.Kind() == reflect.Int64 { - nt := srcField.Interface().(sql.NullTime) - if nt.Valid { - dstField.SetInt(nt.Time.Unix()) - } - return nil - } - - // 处理 sql.NullTime -> time.Time 的转换 - if srcType == reflect.TypeOf(sql.NullTime{}) && dstType == reflect.TypeOf(time.Time{}) { - nt := srcField.Interface().(sql.NullTime) - if nt.Valid { - dstField.Set(reflect.ValueOf(nt.Time)) - } - return nil - } - - // 处理 sql.NullInt64 -> int64 的转换 - if srcType == reflect.TypeOf(sql.NullInt64{}) && dstType.Kind() == reflect.Int64 { - ni := srcField.Interface().(sql.NullInt64) - if ni.Valid { - dstField.SetInt(ni.Int64) - } - return nil - } - - // 处理 sql.NullString -> string 的转换 - if srcType == reflect.TypeOf(sql.NullString{}) && dstType.Kind() == reflect.String { - ns := srcField.Interface().(sql.NullString) - if ns.Valid { - dstField.SetString(ns.String) - } - return nil - } - - // 处理 sql.NullBool -> bool 的转换 - if srcType == reflect.TypeOf(sql.NullBool{}) && dstType.Kind() == reflect.Bool { - nb := srcField.Interface().(sql.NullBool) - if nb.Valid { - dstField.SetBool(nb.Bool) - } - return nil - } - - // 处理 int -> int64 的转换 - if srcType.Kind() == reflect.Int && dstType.Kind() == reflect.Int64 { - dstField.SetInt(int64(srcField.Int())) - return nil - } - - // 处理 int64 -> int 的转换 - if srcType.Kind() == reflect.Int64 && dstType.Kind() == reflect.Int { - dstField.SetInt(srcField.Int()) - return nil - } - - // 处理 string -> string(某些情况下可能存在复制) - if srcType.Kind() == reflect.String && dstType.Kind() == reflect.String { - dstField.SetString(srcField.String()) - return nil - } - - // 处理 bool -> bool - if srcType.Kind() == reflect.Bool && dstType.Kind() == reflect.Bool { - dstField.SetBool(srcField.Bool()) - return nil - } - - return newError("unsupported type conversion from " + srcType.String() + " to " + dstType.String()) -} - -// SliceToSlice 通用切片转换函数,使用 StructToStruct 转换每个元素 -func SliceToSlice(src interface{}, dstSliceType interface{}) (interface{}, error) { - srcVal := reflect.ValueOf(src) - - // src 必须是切片 - if srcVal.Kind() != reflect.Slice { - return nil, newError("source must be a slice") - } - - // 获取原始 dst slice type - dstSliceVal := reflect.ValueOf(dstSliceType) - if dstSliceVal.Kind() != reflect.Slice { - return nil, newError("dstSliceType must be a slice type") - } - - dstSliceElemType := dstSliceVal.Type().Elem() - - // 创建新的目标切片 - dstSlice := reflect.MakeSlice(dstSliceVal.Type(), srcVal.Len(), srcVal.Len()) - - // 逐个转换元素 - for i := 0; i < srcVal.Len(); i++ { - srcElem := srcVal.Index(i) - dstElem := reflect.New(dstSliceElemType) - - // 如果 src 元素是指针,需要解引用 - if srcElem.Kind() == reflect.Ptr { - srcElem = srcElem.Elem() - } - - // 转换单个元素 - dstElemIface := dstElem.Interface() - if err := StructToStruct(srcElem.Interface(), dstElemIface); err != nil { - return nil, err - } - - dstSlice.Index(i).Set(dstElem.Elem()) - } - - return dstSlice.Interface(), nil -} - -type Error struct { - msg string -} - -func (e *Error) Error() string { - return e.msg -} - -func newError(msg string) error { - return &Error{msg: msg} -} +package converter + +import ( + "database/sql" + "reflect" + "time" +) + +// StructToStruct 通用结构体转换函数,利用反射将源结构体的字段值复制到目标结构体 +// src: 源结构体(通常是 models) +// dst: 目标结构体(通常是 pb),必须是指针 +// 支持的自动转换: +// - time.Time -> int64 (Unix 时间戳) +// - sql.NullTime -> int64 (如果有效) +// - sql.NullInt64 -> int64 +// - sql.NullString -> string +// - 相同名称和兼容类型的字段 +func StructToStruct(src, dst interface{}) error { + if src == nil { + return nil + } + + srcVal := reflect.ValueOf(src) + dstVal := reflect.ValueOf(dst) + + // 确保 dst 是指针 + if dstVal.Kind() != reflect.Ptr { + return newError("destination must be a pointer") + } + + dstVal = dstVal.Elem() + + // 如果 src 是指针,解引用 + if srcVal.Kind() == reflect.Ptr { + srcVal = srcVal.Elem() + } + + // 都必须是结构体 + if srcVal.Kind() != reflect.Struct || dstVal.Kind() != reflect.Struct { + return newError("both source and destination must be structs") + } + + srcType := srcVal.Type() + + // 遍历源结构体的所有字段 + for i := 0; i < srcVal.NumField(); i++ { + srcField := srcVal.Field(i) + srcFieldName := srcType.Field(i).Name + + // 在目标结构体中查找同名字段 + dstField := dstVal.FieldByName(srcFieldName) + if !dstField.IsValid() || !dstField.CanSet() { + continue + } + + // 进行类型转换和赋值 + if err := assignValue(srcField, dstField); err != nil { + continue // 如果单个字段转换失败,继续处理其他字段 + } + } + + return nil +} + +// assignValue 尝试将源字段值赋给目标字段 +func assignValue(srcField, dstField reflect.Value) error { + // 如果是可直接赋值的类型 + if srcField.Type() == dstField.Type() { + dstField.Set(srcField) + return nil + } + + srcType := srcField.Type() + dstType := dstField.Type() + + // 处理 time.Time -> int64 的转换 + if srcType == reflect.TypeOf(time.Time{}) && dstType.Kind() == reflect.Int64 { + t := srcField.Interface().(time.Time) + dstField.SetInt(t.Unix()) + return nil + } + + // 处理 sql.NullTime -> int64 的转换 + if srcType == reflect.TypeOf(sql.NullTime{}) && dstType.Kind() == reflect.Int64 { + nt := srcField.Interface().(sql.NullTime) + if nt.Valid { + dstField.SetInt(nt.Time.Unix()) + } + return nil + } + + // 处理 sql.NullTime -> time.Time 的转换 + if srcType == reflect.TypeOf(sql.NullTime{}) && dstType == reflect.TypeOf(time.Time{}) { + nt := srcField.Interface().(sql.NullTime) + if nt.Valid { + dstField.Set(reflect.ValueOf(nt.Time)) + } + return nil + } + + // 处理 sql.NullInt64 -> int64 的转换 + if srcType == reflect.TypeOf(sql.NullInt64{}) && dstType.Kind() == reflect.Int64 { + ni := srcField.Interface().(sql.NullInt64) + if ni.Valid { + dstField.SetInt(ni.Int64) + } + return nil + } + + // 处理 sql.NullString -> string 的转换 + if srcType == reflect.TypeOf(sql.NullString{}) && dstType.Kind() == reflect.String { + ns := srcField.Interface().(sql.NullString) + if ns.Valid { + dstField.SetString(ns.String) + } + return nil + } + + // 处理 sql.NullBool -> bool 的转换 + if srcType == reflect.TypeOf(sql.NullBool{}) && dstType.Kind() == reflect.Bool { + nb := srcField.Interface().(sql.NullBool) + if nb.Valid { + dstField.SetBool(nb.Bool) + } + return nil + } + + // 处理 int -> int64 的转换 + if srcType.Kind() == reflect.Int && dstType.Kind() == reflect.Int64 { + dstField.SetInt(int64(srcField.Int())) + return nil + } + + // 处理 int64 -> int 的转换 + if srcType.Kind() == reflect.Int64 && dstType.Kind() == reflect.Int { + dstField.SetInt(srcField.Int()) + return nil + } + + // 处理 string -> string(某些情况下可能存在复制) + if srcType.Kind() == reflect.String && dstType.Kind() == reflect.String { + dstField.SetString(srcField.String()) + return nil + } + + // 处理 bool -> bool + if srcType.Kind() == reflect.Bool && dstType.Kind() == reflect.Bool { + dstField.SetBool(srcField.Bool()) + return nil + } + + return newError("unsupported type conversion from " + srcType.String() + " to " + dstType.String()) +} + +// SliceToSlice 通用切片转换函数,使用 StructToStruct 转换每个元素 +func SliceToSlice(src interface{}, dstSliceType interface{}) (interface{}, error) { + srcVal := reflect.ValueOf(src) + + // src 必须是切片 + if srcVal.Kind() != reflect.Slice { + return nil, newError("source must be a slice") + } + + // 获取原始 dst slice type + dstSliceVal := reflect.ValueOf(dstSliceType) + if dstSliceVal.Kind() != reflect.Slice { + return nil, newError("dstSliceType must be a slice type") + } + + dstSliceElemType := dstSliceVal.Type().Elem() + + // 创建新的目标切片 + dstSlice := reflect.MakeSlice(dstSliceVal.Type(), srcVal.Len(), srcVal.Len()) + + // 逐个转换元素 + for i := 0; i < srcVal.Len(); i++ { + srcElem := srcVal.Index(i) + dstElem := reflect.New(dstSliceElemType) + + // 如果 src 元素是指针,需要解引用 + if srcElem.Kind() == reflect.Ptr { + srcElem = srcElem.Elem() + } + + // 转换单个元素 + dstElemIface := dstElem.Interface() + if err := StructToStruct(srcElem.Interface(), dstElemIface); err != nil { + return nil, err + } + + dstSlice.Index(i).Set(dstElem.Elem()) + } + + return dstSlice.Interface(), nil +} + +type Error struct { + msg string +} + +func (e *Error) Error() string { + return e.msg +} + +func newError(msg string) error { + return &Error{msg: msg} +} diff --git a/app/users/api/internal/contextx/contextx.go b/common/utils/contextx/contextx.go similarity index 53% rename from app/users/api/internal/contextx/contextx.go rename to common/utils/contextx/contextx.go index cfed81d..9b9d29c 100644 --- a/app/users/api/internal/contextx/contextx.go +++ b/common/utils/contextx/contextx.go @@ -3,6 +3,15 @@ package contextx import ( "context" "errors" + + "github.com/zeromicro/go-zero/core/logx" +) + +var ( + ERRILLEGALUSER = errors.New("illegal user") + ERRILLEGALTOKEN = errors.New("illegal token") + ERRILLEGALREQUESTID = errors.New("illegal request id") + ERRILLEGALISADMIN = errors.New("illegal is_admin") ) func WithRequestId(c context.Context, requestId string) context.Context { @@ -41,6 +50,8 @@ func UserIDFrom(c context.Context) (int64, error) { } } +// request_id is used for tracing and logging, not for authentication or authorization, +// so it can be set by clients or generated by the server. func WithRequestID(c context.Context, requestID string) context.Context { return context.WithValue(c, "request_id", requestID) } @@ -52,3 +63,34 @@ func RequestIDFrom(c context.Context) (string, error) { return requestID, nil } } + +func WithIsAdmin(c context.Context, isAdmin bool) context.Context { + return context.WithValue(c, "is_admin", isAdmin) +} + +func IsAdminFrom(c context.Context) (bool, error) { + if isAdmin, ok := c.Value("is_admin").(bool); !ok { + return false, errors.New("is_admin not found in context") + } else { + return isAdmin, nil + } +} + +func AdminIdFrom(c context.Context) (adminId int64, err error) { + + adminId, err = UserIDFrom(c) + if err != nil { + logx.Errorf("get user id from context: %v", err) + return 0, ERRILLEGALUSER + } + isAdmin, err := IsAdminFrom(c) + if err != nil { + logx.Errorf("get isAdmin from context: %v", err) + return 0, ERRILLEGALUSER + } + if !isAdmin { + logx.Errorf("user %d is not admin", adminId) + return 0, ERRILLEGALUSER + } + return +} diff --git a/common/utils/httpx/httpx.go b/common/utils/httpx/httpx.go new file mode 100644 index 0000000..8d480f6 --- /dev/null +++ b/common/utils/httpx/httpx.go @@ -0,0 +1,30 @@ +package httpx + +import ( + "net/http" + "strconv" +) + +func GetUserIdFromHeader(header http.Header) (int64, error) { + id := header.Get("x-auth-user-id") + if id == "" { + return 0, http.ErrNoCookie + } + intId, err := strconv.ParseInt(id, 10, 64) + if err != nil { + return 0, err + } + return intId, nil +} + +func GetUserIsAdminFromHeader(header http.Header) (bool, error) { + isAdmin := header.Get("x-auth-is-admin") + if isAdmin == "" { + return false, nil + } + boolIsAdmin, err := strconv.ParseBool(isAdmin) + if err != nil { + return false, err + } + return boolIsAdmin, nil +} diff --git a/common/utils/password.go b/common/utils/pwdUtils/password.go similarity index 93% rename from common/utils/password.go rename to common/utils/pwdUtils/password.go index 96851d3..fcb0113 100644 --- a/common/utils/password.go +++ b/common/utils/pwdUtils/password.go @@ -1,25 +1,25 @@ -package utils - -import ( - "golang.org/x/crypto/bcrypt" -) - -const ( - // bcrypt 密钥成本 - bcryptCost = bcrypt.DefaultCost -) - -// HashPassword 对密码进行哈希加密 -func HashPassword(password string) (string, error) { - hash, err := bcrypt.GenerateFromPassword([]byte(password), bcryptCost) - if err != nil { - return "", err - } - return string(hash), nil -} - -// VerifyPassword 验证密码是否正确 -func VerifyPassword(hashedPassword, password string) bool { - err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password)) - return err == nil -} +package pwdUtils + +import ( + "golang.org/x/crypto/bcrypt" +) + +const ( + // bcrypt 密钥成本 + bcryptCost = bcrypt.DefaultCost +) + +// HashPassword 对密码进行哈希加密 +func HashPassword(password string) (string, error) { + hash, err := bcrypt.GenerateFromPassword([]byte(password), bcryptCost) + if err != nil { + return "", err + } + return string(hash), nil +} + +// VerifyPassword 验证密码是否正确 +func VerifyPassword(hashedPassword, password string) bool { + err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password)) + return err == nil +} diff --git a/common/utils/responses.go b/common/utils/responses/responses.go similarity index 95% rename from common/utils/responses.go rename to common/utils/responses/responses.go index 23c0693..5000c4e 100644 --- a/common/utils/responses.go +++ b/common/utils/responses/responses.go @@ -1,4 +1,4 @@ -package utils +package responses import "encoding/json" diff --git a/deploy/k8s/envoy/envoy.yaml b/deploy/k8s/envoy/envoy.yaml index 2923373..86aa780 100644 --- a/deploy/k8s/envoy/envoy.yaml +++ b/deploy/k8s/envoy/envoy.yaml @@ -49,7 +49,7 @@ data: disabled: true - match: - prefix: /api/v1/auth/login + path: /api/v1/auth/login route: cluster: user_api_cluster timeout: 30s @@ -59,7 +59,7 @@ data: disabled: true - match: - prefix: /api/v1/auth/register + path: /api/v1/auth/register route: cluster: user_api_cluster timeout: 30s @@ -79,7 +79,7 @@ data: disabled: true - match: - path: /api/email/verification-code/send + path: /api/v1/email/verification-code/send route: cluster: email_api_cluster timeout: 30s diff --git a/desc/api/chat.api b/desc/api/chat.api new file mode 100644 index 0000000..1989878 --- /dev/null +++ b/desc/api/chat.api @@ -0,0 +1,68 @@ +syntax = "v1" +import "common.api" + +type ( + ChatSession { + Id string `json:"id"` + Type string `json:"type"` // order, consultation + OrderId string `json:"orderId,optional"` + Participants []SimpleUser `json:"participants"` + LastMessage string `json:"lastMessage"` + UnreadCount int `json:"unreadCount"` + } + + ChatSessionListResp { + Items []ChatSession `json:"items"` + Meta PageMeta `json:"meta"` + } + + ChatMessage { + Id string `json:"id"` + SessionId string `json:"sessionId"` + SenderId string `json:"senderId"` + Type string `json:"type"` // text, image, system + Content string `json:"content"` + CreatedAt string `json:"createdAt"` + } + + ChatMessageListResp { + Items []ChatMessage `json:"items"` + Meta PageMeta `json:"meta"` + } + + SendMessageReq { + Type string `json:"type"` + Content string `json:"content"` + } +) + +@server( + prefix: api/v1/chat + group: chat + jwt: Auth +) +service juwan-api { + @doc "获取会话列表" + @handler ListSessions + get /sessions (PageReq) returns (ChatSessionListResp) + + @doc "获取会话详情" + @handler GetSession + get /sessions/:id (EmptyResp) returns (ChatSession) + + @doc "获取消息历史" + @handler ListMessages + get /sessions/:id/messages (PageReq) returns (ChatMessageListResp) + + @doc "发送消息" + @handler SendMessage + post /sessions/:id/messages (SendMessageReq) returns (ChatMessage) + + @doc "创建/获取订单会话" + @handler EnsureOrderSession + post /sessions/order (EmptyResp) returns (ChatSession) + + @doc "创建咨询会话" + @handler CreateConsultation + post /sessions/consultation (EmptyResp) returns (ChatSession) +} \ No newline at end of file diff --git a/desc/api/common.api b/desc/api/common.api new file mode 100644 index 0000000..4ffa5c8 --- /dev/null +++ b/desc/api/common.api @@ -0,0 +1,47 @@ +syntax = "v1" + +info( + title: "公共定义" + desc: "Common structures" + author: "Juwan Team" + version: "1.0" +) + +type ( + // 分页请求基础 + PageReq { + Offset int64 `form:"offset,default=0"` + Limit int64 `form:"limit,default=20"` + } + + // 分页响应元数据 + PageMeta { + Total int64 `json:"total"` + Offset int64 `json:"offset"` + Limit int64 `json:"limit"` + } + + // 空响应 + EmptyResp {} + + // 核心用户画像 (被 Auth, Community, Shop 等多个服务引用) + UserProfile { + Id string `json:"id"` + Username string `json:"username"` + Nickname string `json:"nickname"` + Avatar string `json:"avatar"` + Role string `json:"role"` // consumer, player, owner, admin + VerifiedRoles []string `json:"verifiedRoles"` + VerificationStatus map[string]string `json:"verificationStatus"` + Phone string `json:"phone,optional"` + Bio string `json:"bio,optional"` + CreatedAt string `json:"createdAt"` + } + + // 简略用户信息 (用于列表、聊天头像等) + SimpleUser { + Id string `json:"id"` + Nickname string `json:"nickname"` + Avatar string `json:"avatar"` + } +) \ No newline at end of file diff --git a/desc/api/community.api b/desc/api/community.api new file mode 100644 index 0000000..97a93cf --- /dev/null +++ b/desc/api/community.api @@ -0,0 +1,115 @@ +syntax = "v1" +import "common.api" + +type ( + Post { + Id string `json:"id"` + Title string `json:"title"` + Content string `json:"content"` + Images []string `json:"images"` + Tags []string `json:"tags"` + LikeCount int64 `json:"likeCount"` + CommentCount int64 `json:"commentCount"` + Liked bool `json:"liked"` + Author UserProfile `json:"author"` + CreatedAt string `json:"createdAt"` + } + + CreatePostReq { + Title string `json:"title"` + Content string `json:"content"` + Images []string `json:"images"` + Tags []string `json:"tags"` + LinkedOrderId string `json:"linkedOrderId,optional"` + } + + PostListReq { + PageReq + Tags string `form:"tags,optional"` + SortBy string `form:"sortBy,optional"` + } + + PostListResp { + Items []Post `json:"items"` + Meta PageMeta `json:"meta"` + } + + Comment { + Id string `json:"id"` + Content string `json:"content"` + Author UserProfile `json:"author"` + LikeCount int64 `json:"likeCount"` + Liked bool `json:"liked"` + CreatedAt string `json:"createdAt"` + } + + CommentListResp { + Items []Comment `json:"items"` + Meta PageMeta `json:"meta"` + } + + CreateCommentReq { + Content string `json:"content"` + } +) + +@server( + prefix: api/v1 + group: community +) +service juwan-api { + @doc "获取帖子列表" + @handler ListPosts + get /posts (PostListReq) returns (PostListResp) + + @doc "获取帖子详情" + @handler GetPost + get /posts/:id (EmptyResp) returns (Post) + + @doc "获取帖子评论" + @handler ListComments + get /posts/:id/comments (PageReq) returns (CommentListResp) + + @doc "获取用户帖子" + @handler ListUserPosts + get /users/:id/posts (PageReq) returns (PostListResp) +} + +@server( + prefix: api/v1 + group: community + jwt: Auth +) +service juwan-api { + @doc "发布帖子" + @handler CreatePost + post /posts (CreatePostReq) returns (Post) + + @doc "点赞帖子" + @handler LikePost + post /posts/:id/like (EmptyResp) returns (EmptyResp) + + @doc "取消点赞帖子" + @handler UnlikePost + delete /posts/:id/like (EmptyResp) returns (EmptyResp) + + @doc "置顶帖子" + @handler PinPost + post /posts/:id/pin (EmptyResp) returns (EmptyResp) + + @doc "取消置顶" + @handler UnpinPost + delete /posts/:id/pin (EmptyResp) returns (EmptyResp) + + @doc "发表评论" + @handler CreateComment + post /posts/:id/comments (CreateCommentReq) returns (Comment) + + @doc "点赞评论" + @handler LikeComment + post /comments/:id/like (EmptyResp) returns (EmptyResp) + + @doc "取消点赞评论" + @handler UnlikeComment + delete /comments/:id/like (EmptyResp) returns (EmptyResp) +} \ No newline at end of file diff --git a/desc/api/dispute.api b/desc/api/dispute.api new file mode 100644 index 0000000..7798e32 --- /dev/null +++ b/desc/api/dispute.api @@ -0,0 +1,60 @@ +syntax = "v1" +import "common.api" + +type ( + Dispute { + Id string `json:"id"` + OrderId string `json:"orderId"` + Reason string `json:"reason"` + Status string `json:"status"` + Evidence []string `json:"evidence"` + Result string `json:"result,optional"` + CreatedAt string `json:"createdAt"` + } + + DisputeListResp { + Items []Dispute `json:"items"` + Meta PageMeta `json:"meta"` + } + + CreateDisputeReq { + Reason string `json:"reason"` + Evidence []string `json:"evidence"` + } + + DisputeResponseReq { + Reason string `json:"reason"` + Evidence []string `json:"evidence"` + } + + AppealReq { + Reason string `json:"reason"` + } +) + +@server( + prefix: api/v1 + group: dispute + jwt: Auth +) +service juwan-api { + @doc "获取争议列表" + @handler ListDisputes + get /disputes (PageReq) returns (DisputeListResp) + + @doc "获取订单争议" + @handler GetOrderDispute + get /orders/:id/dispute (EmptyResp) returns (Dispute) + + @doc "发起争议" + @handler CreateDispute + post /orders/:id/dispute (CreateDisputeReq) returns (EmptyResp) + + @doc "回应争议" + @handler RespondDispute + post /disputes/:id/response (DisputeResponseReq) returns (EmptyResp) + + @doc "申诉" + @handler AppealDispute + post /disputes/:id/appeal (AppealReq) returns (EmptyResp) +} \ No newline at end of file diff --git a/desc/api/email.api b/desc/api/email.api index 72d22f0..e4f171d 100644 --- a/desc/api/email.api +++ b/desc/api/email.api @@ -7,7 +7,7 @@ info ( ) type ( - EmptyResp {} + EmptyResp {} ForgotPasswordReq { Email string `json:"email"` } @@ -24,7 +24,7 @@ type ( @server ( group: email - prefix: /api/email + prefix: /api/v1/email middleware: Logger ) service email-api { @@ -34,14 +34,11 @@ service email-api { ) @handler SendVerificationCode post /verification-code/send (SendVerificationCodeReq) returns (SendVerificationCodeResp) - - } @server ( - group: user + group: email prefix: /api/v1/auth - ) service email-api { @doc "忘记密码-发送验证码" diff --git a/desc/api/game.api b/desc/api/game.api new file mode 100644 index 0000000..5254893 --- /dev/null +++ b/desc/api/game.api @@ -0,0 +1,30 @@ +syntax = "v1" +import "common.api" + +type ( + Game { + Id string `json:"id"` + Name string `json:"name"` + Icon string `json:"icon"` + Category string `json:"category"` + } + + GameListResp { + Items []Game `json:"items"` + Meta PageMeta `json:"meta"` + } +) + +@server( + prefix: api/v1/games + group: game +) +service juwan-api { + @doc "获取游戏列表" + @handler ListGames + get / (PageReq) returns (GameListResp) + + @doc "获取游戏详情" + @handler GetGame + get /:id (EmptyResp) returns (Game) +} \ No newline at end of file diff --git a/desc/api/notification.api b/desc/api/notification.api new file mode 100644 index 0000000..253af69 --- /dev/null +++ b/desc/api/notification.api @@ -0,0 +1,38 @@ +syntax = "v1" +import "common.api" + +type ( + Notification { + Id string `json:"id"` + Type string `json:"type"` + Title string `json:"title"` + Content string `json:"content"` + Read bool `json:"read"` + Link string `json:"link,optional"` + CreatedAt string `json:"createdAt"` + } + + NotificationListResp { + Items []Notification `json:"items"` + Meta PageMeta `json:"meta"` + } +) + +@server( + prefix: api/v1 + group: notification + jwt: Auth +) +service juwan-api { + @doc "获取通知列表" + @handler ListNotifications + get /notifications (PageReq) returns (NotificationListResp) + + @doc "标记已读" + @handler ReadNotification + put /notifications/:id/read (EmptyResp) returns (EmptyResp) + + @doc "全部已读" + @handler ReadAllNotifications + put /notifications/read-all (EmptyResp) returns (EmptyResp) +} \ No newline at end of file diff --git a/desc/api/objectstory.api b/desc/api/objectstory.api new file mode 100644 index 0000000..5bcd95d --- /dev/null +++ b/desc/api/objectstory.api @@ -0,0 +1,40 @@ +syntax = "v1" + +info ( + title: "文件服务" + desc: "处理文件上传与获取" + author: "Asadz" + version: "v1" +) + +type ( + // 上传请求参数(File文件流在Handler中通过 r.FormFile 获取) + UploadReq { + Type string `form:"type,options=avatar|chat|post|verification|dispute"` // 文件类型限制 + } + // 上传响应 + UploadResp { + Url string `json:"url"` // 返回 CDN 地址或访问地址 + } + // 获取文件请求(用于私有文件或代理访问) + GetFileReq { + FileId string `path:"fileId"` + } +) + +@server ( + prefix: /api/v1 + group: file + jwt: Logger + middleware: FileSizeLimit // 建议添加中间件限制文件大小 +) +service file-api { + @doc "文件上传接口" + @handler Upload + post /upload (UploadReq) returns (UploadResp) + + @doc "文件获取接口 (如果是私有文件,通过此接口获取或重定向)" + @handler GetFile + get /files/:fileId (GetFileReq) +} + diff --git a/desc/api/order.api b/desc/api/order.api index e69de29..cf03c16 100644 --- a/desc/api/order.api +++ b/desc/api/order.api @@ -0,0 +1,93 @@ +syntax = "v1" +import "common.api" +import "player.api" // 为了使用 PlayerService 定义 + +type ( + Order { + Id string `json:"id"` + ConsumerId string `json:"consumerId"` + ConsumerName string `json:"consumerName"` + PlayerId string `json:"playerId"` + PlayerName string `json:"playerName"` + ShopId string `json:"shopId,optional"` + ShopName string `json:"shopName,optional"` + Service PlayerService `json:"service"` + Status string `json:"status"` + TotalPrice float64 `json:"totalPrice"` + Note string `json:"note,optional"` + CreatedAt string `json:"createdAt"` + AcceptedAt string `json:"acceptedAt,optional"` + CompletedAt string `json:"completedAt,optional"` + } + + OrderListReq { + PageReq + Role string `form:"role"` // consumer, player, owner + Status string `form:"status,optional"` + } + + OrderListResp { + Items []Order `json:"items"` + Meta PageMeta `json:"meta"` + } + + CreateOrderReq { + PlayerId string `json:"playerId"` + ShopId string `json:"shopId,optional"` + ServiceId string `json:"serviceId"` + Quantity int `json:"quantity"` + Note string `json:"note,optional"` + } + + CreateOrderResp { + Ok bool `json:"ok"` + Order Order `json:"order"` + } +) + +@server( + prefix: api/v1/orders + group: order + jwt: Auth +) +service juwan-api { + @doc "获取订单列表" + @handler ListOrders + get / (OrderListReq) returns (OrderListResp) + + @doc "获取订单详情" + @handler GetOrder + get /:id (EmptyResp) returns (Order) + + @doc "创建订单" + @handler CreateOrder + post / (CreateOrderReq) returns (CreateOrderResp) + + @doc "创建并支付订单" + @handler CreateAndPayOrder + post /paid (CreateOrderReq) returns (CreateOrderResp) + + @doc "支付订单" + @handler PayOrder + post /:id/pay (EmptyResp) returns (EmptyResp) + + @doc "接单" + @handler AcceptOrder + post /:id/accept (EmptyResp) returns (EmptyResp) + + @doc "申请结算" + @handler RequestCloseOrder + post /:id/request-close (EmptyResp) returns (EmptyResp) + + @doc "确认结算" + @handler ConfirmCloseOrder + post /:id/confirm-close (EmptyResp) returns (EmptyResp) + + @doc "取消订单" + @handler CancelOrder + post /:id/cancel (EmptyResp) returns (EmptyResp) + + @doc "再来一单" + @handler Reorder + post /:id/reorder (EmptyResp) returns (CreateOrderResp) +} \ No newline at end of file diff --git a/desc/api/player.api b/desc/api/player.api new file mode 100644 index 0000000..a03f1b0 --- /dev/null +++ b/desc/api/player.api @@ -0,0 +1,110 @@ +syntax = "v1" +import "common.api" + +type ( + PlayerService { + Id string `json:"id"` + PlayerId string `json:"playerId"` + GameId string `json:"gameId"` + GameName string `json:"gameName"` + Title string `json:"title"` + Description string `json:"description"` + Price float64 `json:"price"` + Unit string `json:"unit"` + RankRange string `json:"rankRange,optional"` + Availability []string `json:"availability"` + } + + PlayerServiceListResp { + Items []PlayerService `json:"items"` + Meta PageMeta `json:"meta"` + } + + CreateServiceReq { + GameId string `json:"gameId"` + Title string `json:"title"` + Description string `json:"description,optional"` + Price float64 `json:"price"` + Unit string `json:"unit"` + RankRange string `json:"rankRange,optional"` + Availability []string `json:"availability,optional"` + } + + PlayerProfile { + Id string `json:"id"` + User UserProfile `json:"user"` + Rating float64 `json:"rating"` + TotalOrders int64 `json:"totalOrders"` + CompletionRate float64 `json:"completionRate"` + Status string `json:"status"` + Games []string `json:"games"` + Services []PlayerService `json:"services"` + ShopId string `json:"shopId,optional"` + ShopName string `json:"shopName,optional"` + Tags []string `json:"tags"` + } + + PlayerListReq { + PageReq + GameId string `form:"gameId,optional"` + Gender int `form:"gender,optional"` + } + + PlayerListResp { + Items []PlayerProfile `json:"items"` + Meta PageMeta `json:"meta"` + } + + UpdatePlayerStatusReq { + Status string `json:"status"` + } +) + +@server( + prefix: api/v1 + group: player +) +service juwan-api { + @doc "获取打手列表" + @handler ListPlayers + get /players (PlayerListReq) returns (PlayerListResp) + + @doc "获取打手详情" + @handler GetPlayer + get /players/:id (EmptyResp) returns (PlayerProfile) + + @doc "获取所有服务列表" + @handler ListServices + get /services (PageReq) returns (PlayerServiceListResp) + + @doc "获取服务详情" + @handler GetService + get /services/:id (EmptyResp) returns (PlayerService) + + @doc "获取指定打手的服务列表" + @handler ListPlayerServices + get /players/:id/services (PageReq) returns (PlayerServiceListResp) +} + +@server( + prefix: api/v1 + group: player + jwt: Auth +) +service juwan-api { + @doc "更新接单状态" + @handler UpdatePlayerStatus + put /players/me/status (UpdatePlayerStatusReq) returns (EmptyResp) + + @doc "创建服务" + @handler CreateService + post /services (CreateServiceReq) returns (PlayerService) + + @doc "更新服务" + @handler UpdateService + put /services/:id (CreateServiceReq) returns (PlayerService) + + @doc "删除服务" + @handler DeleteService + delete /services/:id (EmptyResp) returns (EmptyResp) +} \ No newline at end of file diff --git a/desc/api/review.api b/desc/api/review.api new file mode 100644 index 0000000..6f0d8e1 --- /dev/null +++ b/desc/api/review.api @@ -0,0 +1,54 @@ +syntax = "v1" +import "common.api" + +type ( + Review { + Id string `json:"id"` + OrderId string `json:"orderId"` + FromUserId string `json:"fromUserId"` + FromUserName string `json:"fromUserName"` + Rating int `json:"rating"` + Content string `json:"content"` + Sealed bool `json:"sealed"` + CreatedAt string `json:"createdAt"` + } + + ReviewListResp { + Items []Review `json:"items"` + Meta PageMeta `json:"meta"` + } + + SubmitReviewReq { + Rating int `json:"rating"` + Content string `json:"content,optional"` + } +) + +@server( + prefix: api/v1 + group: review + jwt: Auth +) +service juwan-api { + @doc "提交评价" + @handler SubmitReview + post /orders/:id/review (SubmitReviewReq) returns (EmptyResp) + + @doc "获取订单评价" + @handler GetOrderReviews + get /orders/:id/reviews (EmptyResp) returns ([]Review) +} + +@server( + prefix: api/v1 + group: review +) +service juwan-api { + @doc "获取公开评价列表" + @handler ListReviews + get /reviews (PageReq) returns (ReviewListResp) + + @doc "获取用户收到的评价" + @handler ListUserReviews + get /users/:id/reviews (PageReq) returns (ReviewListResp) +} \ No newline at end of file diff --git a/desc/api/search.api b/desc/api/search.api new file mode 100644 index 0000000..41c9142 --- /dev/null +++ b/desc/api/search.api @@ -0,0 +1,69 @@ +syntax = "v1" +import "common.api" + +type ( + SearchReq { + PageReq + Q string `form:"q"` + MinPrice float64 `form:"min,optional"` + MaxPrice float64 `form:"max,optional"` + OnlyOnline bool `form:"onlyOnline,optional"` + Sort string `form:"sort,optional"` + } + + SearchResp { + Items []interface{} `json:"items"` // Mixed items + Meta PageMeta `json:"meta"` + } + + FavoriteReq { + TargetType string `json:"targetType"` // player, shop + TargetId string `json:"targetId"` + } + + FavoriteCheckReq { + TargetType string `form:"targetType"` + TargetId string `form:"targetId"` + } + + FavoriteCheckResp { + Favorited bool `json:"favorited"` + } +) + +@server( + prefix: api/v1 + group: search +) +service juwan-api { + @doc "统一搜索" + @handler Search + get /search (SearchReq) returns (SearchResp) + + @doc "首页推荐" + @handler Recommendations + get /recommendations/home (PageReq) returns (SearchResp) +} + +@server( + prefix: api/v1 + group: favorites + jwt: Auth +) +service juwan-api { + @doc "获取收藏列表" + @handler ListFavorites + get /favorites (PageReq) returns (SearchResp) + + @doc "添加收藏" + @handler AddFavorite + post /favorites (FavoriteReq) returns (EmptyResp) + + @doc "取消收藏" + @handler RemoveFavorite + delete /favorites/:id (EmptyResp) returns (EmptyResp) + + @doc "检查收藏状态" + @handler CheckFavorite + get /users/:id/favorites/check (FavoriteCheckReq) returns (FavoriteCheckResp) +} \ No newline at end of file diff --git a/desc/api/shop.api b/desc/api/shop.api new file mode 100644 index 0000000..c86767d --- /dev/null +++ b/desc/api/shop.api @@ -0,0 +1,130 @@ +syntax = "v1" +import "common.api" + +type ( + ShopProfile { + Id string `json:"id"` + Owner UserProfile `json:"owner"` + Name string `json:"name"` + Banner string `json:"banner,optional"` + Description string `json:"description"` + Rating float64 `json:"rating"` + TotalOrders int64 `json:"totalOrders"` + PlayerCount int64 `json:"playerCount"` + CommissionType string `json:"commissionType"` + CommissionValue float64 `json:"commissionValue"` + Announcements []string `json:"announcements"` + TemplateConfig interface{} `json:"templateConfig"` + } + + ShopListResp { + Items []ShopProfile `json:"items"` + Meta PageMeta `json:"meta"` + } + + CreateShopReq { + Name string `json:"name"` + Description string `json:"description"` + CommissionType string `json:"commissionType"` + CommissionValue float64 `json:"commissionValue"` + } + + UpdateShopReq { + Name string `json:"name,optional"` + Description string `json:"description,optional"` + CommissionType string `json:"commissionType,optional"` + CommissionValue float64 `json:"commissionValue,optional"` + AllowMultiShop bool `json:"allowMultiShop,optional"` + AllowIndependentOrders bool `json:"allowIndependentOrders,optional"` + DispatchMode string `json:"dispatchMode,optional"` + } + + UpdateTemplateReq { + Sections interface{} `json:"sections"` + } + + AnnouncementReq { + Content string `json:"content"` + } + + IncomeStatsResp { + MonthlyIncome float64 `json:"monthlyIncome"` + PendingSettlement float64 `json:"pendingSettlement"` + TotalWithdrawn float64 `json:"totalWithdrawn"` + TotalOrders int64 `json:"totalOrders"` + CompletedOrders int64 `json:"completedOrders"` + } + + InvitationReq { + PlayerId string `json:"playerId"` + } +) + +@server( + prefix: api/v1 + group: shop +) +service juwan-api { + @doc "获取店铺列表" + @handler ListShops + get /shops (PageReq) returns (ShopListResp) + + @doc "获取店铺详情" + @handler GetShop + get /shops/:id (EmptyResp) returns (ShopProfile) + + @doc "获取店长的店铺" + @handler GetUserShop + get /users/:id/shop (EmptyResp) returns (ShopProfile) +} + +@server( + prefix: api/v1 + group: shop + jwt: Auth +) +service juwan-api { + @doc "创建店铺" + @handler CreateShop + post /shops (CreateShopReq) returns (ShopProfile) + + @doc "获取当前用户的店铺" + @handler GetMyShop + get /shops/mine (EmptyResp) returns (ShopProfile) + + @doc "更新店铺信息" + @handler UpdateShop + put /shops/:id (UpdateShopReq) returns (ShopProfile) + + @doc "更新店铺模板" + @handler UpdateShopTemplate + put /shops/:id/template (UpdateTemplateReq) returns (EmptyResp) + + @doc "新增店铺公告" + @handler AddAnnouncement + post /shops/:id/announcements (AnnouncementReq) returns (EmptyResp) + + @doc "删除店铺公告" + @handler DeleteAnnouncement + delete /shops/:id/announcements/:index (EmptyResp) returns (EmptyResp) + + @doc "邀请打手" + @handler InvitePlayer + post /shops/:id/invitations (InvitationReq) returns (EmptyResp) + + @doc "接受邀请" + @handler AcceptInvitation + post /shops/invitations/:id/accept (EmptyResp) returns (EmptyResp) + + @doc "拒绝邀请" + @handler RejectInvitation + delete /shops/invitations/:id (EmptyResp) returns (EmptyResp) + + @doc "移除打手" + @handler RemovePlayer + delete /shops/:id/players/:playerId (EmptyResp) returns (EmptyResp) + + @doc "获取收入统计" + @handler GetShopIncomeStats + get /shops/:id/income-stats (EmptyResp) returns (IncomeStatsResp) +} \ No newline at end of file diff --git a/desc/api/user_verifications.api b/desc/api/user_verifications.api new file mode 100644 index 0000000..685a5a6 --- /dev/null +++ b/desc/api/user_verifications.api @@ -0,0 +1,55 @@ +syntax = "v1" + +info ( + title: "聚玩认证审核服务" + desc: "处理用户角色认证申请(打手/店长)及管理员审核流程" + author: "Asadz" + version: "1.0" +) + +// ================================================================================= +// 数据结构定义 (Data Structures) +// ================================================================================= + +// ================================================================================= +// 用户端接口 (User Side) +// 路径前缀: /api/v1/users +// ================================================================================= +@server ( + group: verification_user + prefix: /api/v1/users + jwt: Auth // 必须登录 +) +service verification-api { + @doc "提交或修改角色认证申请 (支持幂等更新)" + @handler ApplyVerification + post /me/verification (ApplyVerificationReq) returns (VerificationEmptyResp) + + @doc "获取我的所有认证状态" + @handler GetMyVerifications + get /me/verification returns (GetMyVerificationsResp) +} + +// ================================================================================= +// 管理端接口 (Admin Side) +// 路径前缀: /api/v1/admin +// ================================================================================= +@server ( + group: verification_admin + prefix: /api/v1/admin + jwt: Auth // 需要登录,且 Logic 层需校验 IsAdmin +) +service verification-api { + @doc "管理员获取认证申请列表 (分页)" + @handler GetVerifications + get /verifications (GetPendingListReq) returns (GetPendingListResp) + + @doc "管理员通过申请" + @handler ApproveVerification + post /verifications/:id/approve (VerificationIdReq) returns (VerificationEmptyResp) + + @doc "管理员驳回申请" + @handler RejectVerification + post /verifications/:id/reject (RejectVerificationReq) returns (VerificationEmptyResp) +} + diff --git a/desc/api/users.api b/desc/api/users.api index 181c728..0dee23e 100644 --- a/desc/api/users.api +++ b/desc/api/users.api @@ -1,164 +1,269 @@ syntax = "v1" -info( - author: "Asadz" - date: "2024-06-19" - version: "1.0" +info ( + title: "聚玩用户服务" + desc: "处理用户注册、登录、个人信息管理及关注系统" + author: "Asadz" + version: "1.0" ) type ( - EmptyResp { - } - SearchUserResp { - Username string `json:"username"` - UserId int64 `json:"userId"` - Nickname string `json:"nickname"` - Avatar string `json:"avatar"` - Bio string `json:"bio"` - Page int64 `json:"page"` - Limit int64 `json:"limit"` - } - ResetPasswordByVcode { - Email string `json:"email"` - Password string `json:"password"` - Vcode string `json:"vcode"` - } - RegisterReq { - Username string `json:"username" binding:"required,min=3,max=50"` - Password string `json:"password" binding:"required,min=6,max=128"` - Email string `json:"email,omitempty" binding:"omitempty,email"` - Phone string `json:"phone,omitempty" binding:"omitempty,len=11"` - Vcode int32 `json:"vcode"` - } - RegisterResp { - UserId int64 `json:"userId"` - Username string `json:"username"` - Email string `json:"email"` - Message string `json:"message"` - } - LoginReq { - Username string `json:"username" binding:"required"` - Password string `json:"password" binding:"required"` - } - LoginResp { - UserId int64 `json:"userId"` - Username string `json:"username"` - Email string `json:"email"` - Token string `json:"token"` - Expires int64 `json:"expires"` - } - GetUserInfoReq { - UserId int64 `path:"userId" binding:"required,gt=0"` - } - UserInfo { - UserId int64 `json:"userId"` - Username string `json:"username"` - Email string `json:"email"` - Phone string `json:"phone"` - Avatar string `json:"avatar"` - Status int `json:"status"` - CreateAt int64 `json:"createAt"` - UpdateAt int64 `json:"updateAt"` - } - UpdateUserInfoReq { - Nickname *string `json:"nickname,omitempty"` - Avatar *string `json:"avatar,omitempty"` - Bio *string `json:"bio,omitempty"` - } - UpdateUserInfoResp { - UserId int64 `json:"userId"` - Message string `json:"message"` - } - UpdatePasswordReq { - UserId int64 `path:"userId" binding:"required,gt=0"` - OldPassword string `json:"oldPassword" binding:"required"` - NewPassword string `json:"newPassword" binding:"required,min=6,max=128"` - } - UpdatePasswordResp { - Message string `json:"message"` - } - LogoutReq { - UserId int64 `path:"userId" binding:"required,gt=0"` - Token string `header:"Authorization" binding:"required"` - } - LogoutResp { - Message string `json:"message"` - } - ErrorResp { - Code int `json:"code"` - Message string `json:"message"` - } + // 认证记录展示对象 + // 对应数据库 user_verifications 表 + VerificationItem { + Id int64 `json:"id"` // 认证记录ID (主键,用于管理员操作) + UserId int64 `json:"userId"` // 申请人ID (外键) + UserNickname string `json:"userNickname"` // 冗余显示,方便前端展示 + Role string `json:"role"` // 申请角色: player, owner + Status string `json:"status"` // pending, approved, rejected + Materials map[string]string `json:"materials"` // 核心字段:对应 DB 的 JSONB + RejectReason string `json:"rejectReason"` // 驳回原因 + CreatedAt string `json:"createdAt"` // 申请时间 + ReviewedAt string `json:"reviewedAt"` // 审核时间 + } + // 提交申请请求 + ApplyVerificationReq { + Role string `json:"role"` // 申请什么角色 + Materials map[string]string `json:"materials"` // 证明材料键值对 {"idCardFront": "http...", "license": "http..."} + } + // 获取我的申请记录响应 + GetMyVerificationsResp { + List []VerificationItem `json:"list"` + } + // 管理员:获取待审核列表请求 + GetPendingListReq { + Page int64 `form:"page,default=1"` + Size int64 `form:"size,default=20"` + Role string `form:"role,optional"` // 筛选角色 + Status string `form:"status,optional"` // 筛选状态,默认 pending + } + // 管理员:列表响应 + GetPendingListResp { + List []VerificationItem `json:"list"` + Total int64 `json:"total"` + } + // 管理员:审核操作请求 (只包含 ID 路径参数) + VerificationIdReq { + Id int64 `path:"id"` // 注意:这是 user_verifications.id + } + // 管理员:驳回请求 + RejectVerificationReq { + Id int64 `path:"id"` + Reason string `json:"reason"` // 必填:驳回原因 + } + // 通用空响应 + VerificationEmptyResp {} ) -@server( - group: user - prefix: /api/v1/auth - middleware: Logger +// ================================================================================= +// 基础数据模型 (Data Models) +// ================================================================================= +type ( + // 通用空响应 + EmptyResp {} + // 用户信息核心模型 (User) + User { + Id int64 `json:"id"` + Username string `json:"username"` + Nickname string `json:"nickname"` + Avatar string `json:"avatar"` + Role string `json:"role"` // consumer, player, owner, admin + VerifiedRoles []string `json:"verifiedRoles"` // e.g. ["consumer", "player"] + VerificationStatus map[string]string `json:"verificationStatus"` // e.g. {"player": "approved"} + Phone string `json:"phone,omitempty"` + Bio string `json:"bio,omitempty"` + CreatedAt string `json:"createdAt"` // ISO 8601 + } +) + +// ================================================================================= +// 认证相关 (Auth) +// ================================================================================= +type ( + RegisterReq { + Phone string `json:"phone,omitempty"` + Email string `json:"email,omitempty"` + Username string `json:"username"` + Password string `json:"password"` + Vcode string `json:"vcode,omitempty"` // 验证码 + } + RegisterResp { + AccessToken string `json:"accessToken"` + RefreshToken string `json:"refreshToken"` + User User `json:"user"` + } + LoginReq { + Phone string `json:"phone,omitempty"` // 手机号登录 + Username string `json:"username,omitempty"` // 或用户名登录 + Password string `json:"password"` + Remember bool `json:"remember,optional"` + } + LoginResp { + AccessToken string `json:"accessToken"` + RefreshToken string `json:"refreshToken"` + User User `json:"user"` + } + LogoutReq {} + ForgotPasswordReq { + Phone string `json:"phone,omitempty"` + Email string `json:"email,omitempty"` + } + ResetPasswordReq { + Phone string `json:"phone,omitempty"` + Email string `json:"email,omitempty"` + Vcode string `json:"vcode"` + NewPassword string `json:"newPassword"` + } +) + +// ================================================================================= +// 用户个人中心 (User Me) +// ================================================================================= +type ( + UpdateUserProfileReq { + Nickname string `json:"nickname,optional"` + Avatar string `json:"avatar,optional"` + Bio string `json:"bio,optional"` + } + SwitchRoleReq { + Role string `json:"role"` // 目标角色 + } + UpdateNotifySettingsReq { + Order bool `json:"order,optional"` + Community bool `json:"community,optional"` + System bool `json:"system,optional"` + } + UpdateThemeSettingsReq { + Theme string `json:"theme"` // dark, light + } +) + +// ================================================================================= +// 公开用户信息与社交 (Public User & Social) +// ================================================================================= +type ( + GetUserReq { + Id int64 `path:"id"` + } + FollowUserReq { + Id int64 `path:"id"` + } + UnfollowUserReq { + Id int64 `path:"id"` + } +) + +// ================================================================================= +// 服务定义 +// ================================================================================= +@server ( + group: auth + prefix: /api/v1/auth ) service user-api { - @doc( - summary: "用户注册接口" - description: "通过用户名、密码、邮箱、电话号码注册新用户账户" - ) - @handler Register - post /register (RegisterReq) returns (RegisterResp) + @doc "用户注册" + @handler Register + post /register (RegisterReq) returns (RegisterResp) - @doc( - summary: "用户登录接口" - description: "使用用户名和密码进行登录,返回访问令牌和用户信息" - ) - @handler Login - post /login (LoginReq) returns (LoginResp) + @doc "用户登录" + @handler Login + post /login (LoginReq) returns (LoginResp) - @doc( - summary: "修改用户密码" - description: "验证旧密码后修改为新密码,需要提供原密码" - ) - @handler UpdatePassword - put /:userId/password (UpdatePasswordReq) returns (UpdatePasswordResp) + @doc "忘记密码-发送验证码" + @handler ForgotPassword + post /forgot-password (ForgotPasswordReq) returns (EmptyResp) - @doc( - summary: "用户登出" - description: "需要携带 Authorization 令牌,清除用户会话并使令牌失效" - ) - @handler Logout - post /:userId/logout (LogoutReq) returns (LogoutResp) - - @doc "修改密码-使用验证码" - @handler UpdatePasswordByVcode - put /forgot-password/reset (ResetPasswordByVcode) returns (EmptyResp) + @doc "重置密码" + @handler ResetPassword + post /reset-password (ResetPasswordReq) returns (EmptyResp) } -@server( - group: user - prefix: /api/v1/user - middleware: Logger +@server ( + group: auth + prefix: /api/v1/auth ) service user-api { - @doc "获取当前登录用户信息" - @handler GetMe - get /me returns (UserInfo) - - @doc "通过用户名搜索用户" - @handler SearchUsersByUsername - get /search () returns ([]UserInfo) - - @doc "更改当前登录用户信息" - @handler UpdateMe - put /me (UpdateUserInfoReq) returns (UserInfo) - - @doc( - summary: "获取用户信息" - description: "根据用户ID获取用户的详细信息,包含个人资料和账户状态" - ) - @handler GetUserInfo - get /:userId (GetUserInfoReq) returns (UserInfo) - - @doc( - summary: "修改用户信息" - description: "修改用户的邮箱、电话号码、头像等信息" - ) - @handler UpdateUserInfo - put /:userId (UpdateUserInfoReq) returns (UpdateUserInfoResp) + @doc "退出登录" + @handler Logout + post /logout (LogoutReq) returns (EmptyResp) +} + +@server ( + group: user + prefix: /api/v1/users + middleware: Logger +) +service user-api { + @doc "获取当前登录用户信息" + @handler GetMe + get /me returns (User) + + @doc "更新个人资料" + @handler UpdateMe + put /me (UpdateUserProfileReq) returns (User) + + @doc "切换当前激活角色" + @handler SwitchRole + post /me/switch-role (SwitchRoleReq) returns (EmptyResp) + + @doc "更新通知偏好" + @handler UpdateNotificationSettings + put /me/preferences/notifications (UpdateNotifySettingsReq) returns (EmptyResp) + + @doc "更新主题偏好" + @handler UpdateThemeSettings + put /me/preferences/theme (UpdateThemeSettingsReq) returns (EmptyResp) + + @doc "关注用户" + @handler FollowUser + post /:id/follow (FollowUserReq) returns (EmptyResp) + + @doc "取消关注用户" + @handler UnfollowUser + delete /:id/follow (UnfollowUserReq) returns (EmptyResp) +} + +// 公开接口,不需要 JWT +@server ( + group: user + prefix: /api/v1/users +) +service user-api { + @doc "获取指定用户信息" + @handler GetUserInfo + get /:id (GetUserReq) returns (User) +} + +@server ( + group: verification_user + prefix: /api/v1/users + middleware: Logger // 必须登录 +) +service user-api { + @doc "提交或修改角色认证申请 (支持幂等更新)" + @handler ApplyVerification + post /me/verification (ApplyVerificationReq) returns (VerificationEmptyResp) + + @doc "获取我的所有认证状态" + @handler GetMyVerifications + get /me/verification returns (GetMyVerificationsResp) +} + +@server ( + group: verification_admin + prefix: /api/v1/admin +) +service user-api { + @doc "管理员获取认证申请列表 (分页)" + @handler GetVerifications + get /verifications (GetPendingListReq) returns (GetPendingListResp) + + @doc "管理员通过申请" + @handler ApproveVerification + post /verifications/:id/approve (VerificationIdReq) returns (VerificationEmptyResp) + + @doc "管理员驳回申请" + @handler RejectVerification + post /verifications/:id/reject (RejectVerificationReq) returns (VerificationEmptyResp) } diff --git a/desc/api/wallet.api b/desc/api/wallet.api new file mode 100644 index 0000000..61aae1b --- /dev/null +++ b/desc/api/wallet.api @@ -0,0 +1,51 @@ +syntax = "v1" +import "common.api" + +type ( + WalletBalance { + Balance float64 `json:"balance"` + FrozenBalance float64 `json:"frozenBalance"` + } + + Transaction { + Id string `json:"id"` + Type string `json:"type"` + Amount float64 `json:"amount"` + Description string `json:"description"` + OrderId string `json:"orderId,optional"` + CreatedAt string `json:"createdAt"` + } + + TransactionListResp { + Items []Transaction `json:"items"` + Meta PageMeta `json:"meta"` + } + + TopupReq { + Amount float64 `json:"amount"` + Method string `json:"method"` + } +) + +@server( + prefix: api/v1/wallet + group: wallet + jwt: Auth +) +service juwan-api { + @doc "获取余额" + @handler GetBalance + get /balance (EmptyResp) returns (WalletBalance) + + @doc "获取流水" + @handler ListTransactions + get /transactions (PageReq) returns (TransactionListResp) + + @doc "充值" + @handler Topup + post /topup (TopupReq) returns (EmptyResp) + + @doc "提现" + @handler Withdraw + post /withdraw (TopupReq) returns (EmptyResp) +} \ No newline at end of file diff --git a/desc/rpc/objectstory.proto b/desc/rpc/objectstory.proto new file mode 100644 index 0000000..c8b5fb9 --- /dev/null +++ b/desc/rpc/objectstory.proto @@ -0,0 +1,34 @@ +syntax = "proto3"; + +package file; +option go_package = "./pb"; + +// 文件上传的元数据信息 +message UploadFileMetadataReq { + string fileName = 1; + int64 fileSize = 2; + string fileType = 3; // avatar, chat, etc. + string userId = 4; + bytes fileData = 5; // 如果文件很小可以直接传,大文件建议API层直接传S3,RPC只传元数据 +} + +message UploadFileResp { + string url = 1; + string fileId = 2; +} + +message GetFileUrlReq { + string fileId = 1; + string userId = 2; // 用于鉴权 +} + +message GetFileUrlResp { + string url = 1; // 可能是带签名的临时 URL +} + +service FileService { + // 简单上传(适合小文件,或保存元数据) + rpc Upload(UploadFileMetadataReq) returns (UploadFileResp); + // 获取文件访问链接(处理私有文件的鉴权) + rpc GetFileUrl(GetFileUrlReq) returns (GetFileUrlResp); +} diff --git a/desc/rpc/user_verifications.proto b/desc/rpc/user_verifications.proto index bb92da9..bfcfa65 100644 --- a/desc/rpc/user_verifications.proto +++ b/desc/rpc/user_verifications.proto @@ -39,11 +39,11 @@ message AddUserVerificationsResp { message UpdateUserVerificationsReq { int64 id = 1; //id - int64 userId = 2; //userId - string role = 3; //role - string status = 4; //status - string materials = 5; //materials - string rejectReason = 6; //rejectReason + optional int64 userId = 2; //userId + optional string role = 3; //role + optional string status = 4; //status + optional string materials = 5; //materials + optional string rejectReason = 6; //rejectReason int64 reviewedBy = 7; //reviewedBy int64 reviewedAt = 8; //reviewedAt int64 createdAt = 9; //createdAt diff --git a/desc/rpc/users.proto b/desc/rpc/users.proto index 7ef7119..8c6385e 100644 --- a/desc/rpc/users.proto +++ b/desc/rpc/users.proto @@ -155,7 +155,7 @@ message RegisterReq { string username = 1; string passwd = 2; string phone = 3; - int32 vcode = 4; + string vcode = 4; string email = 5; string requestId = 6; } diff --git a/desc/sql/community/comment_likes.sql b/desc/sql/community/comment_likes.sql new file mode 100644 index 0000000..9318be7 --- /dev/null +++ b/desc/sql/community/comment_likes.sql @@ -0,0 +1,9 @@ +CREATE TABLE comment_likes ( + comment_id BIGINT NOT NULL, + user_id BIGINT NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + + PRIMARY KEY (comment_id, user_id) +); + +CREATE INDEX idx_comment_likes_lookup ON comment_likes(user_id, comment_id); \ No newline at end of file diff --git a/desc/sql/community/comments.sql b/desc/sql/community/comments.sql new file mode 100644 index 0000000..3e7d90e --- /dev/null +++ b/desc/sql/community/comments.sql @@ -0,0 +1,21 @@ +CREATE TABLE comments ( + id BIGINT PRIMARY KEY, + post_id BIGINT NOT NULL REFERENCES posts(id), + author_id BIGINT NOT NULL, + content TEXT NOT NULL, + like_count INT DEFAULT 0, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + deleted_at TIMESTAMPTZ +); + +-- 基础索引 +CREATE INDEX idx_comments_post ON comments(post_id, created_at) WHERE deleted_at IS NULL; +CREATE INDEX idx_comments_author ON comments(author_id, created_at DESC) WHERE deleted_at IS NULL; + +-- 三元组索引用于评论内容搜索 +CREATE INDEX idx_comments_content_trgm ON comments USING gin(content gin_trgm_ops) + WHERE deleted_at IS NULL; + +-- 热门评论索引 +CREATE INDEX idx_comments_post_likes ON comments(post_id, like_count DESC, created_at) + WHERE deleted_at IS NULL; \ No newline at end of file diff --git a/desc/sql/community/post_likes.sql b/desc/sql/community/post_likes.sql new file mode 100644 index 0000000..10e5110 --- /dev/null +++ b/desc/sql/community/post_likes.sql @@ -0,0 +1,14 @@ +CREATE TABLE post_likes ( + post_id BIGINT NOT NULL, + user_id BIGINT NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + + -- 复合主键,防止重复点赞 + PRIMARY KEY (post_id, user_id) +); + +-- [核心索引] 优化 "feed流中判断我是否已赞" (user_id = ? AND post_id IN (...)) +CREATE INDEX idx_post_likes_lookup ON post_likes(user_id, post_id); + +-- [核心索引] 优化 "我赞过的帖子" 列表 +CREATE INDEX idx_post_likes_user_timeline ON post_likes(user_id, created_at DESC); \ No newline at end of file diff --git a/desc/sql/community/posts.sql b/desc/sql/community/posts.sql new file mode 100644 index 0000000..68a059f --- /dev/null +++ b/desc/sql/community/posts.sql @@ -0,0 +1,58 @@ +CREATE TABLE posts ( + id BIGINT PRIMARY KEY, + author_id BIGINT NOT NULL, + author_role VARCHAR(20) NOT NULL, + title VARCHAR(500) NOT NULL, + content TEXT NOT NULL, + images TEXT[] DEFAULT ARRAY[]::TEXT[], + tags TEXT[] DEFAULT ARRAY[]::TEXT[], + linked_order_id BIGINT, + quoted_post_id BIGINT REFERENCES posts(id), + like_count INT DEFAULT 0, + comment_count INT DEFAULT 0, + pinned BOOLEAN DEFAULT FALSE, + search_text TEXT GENERATED ALWAYS AS ( + title || ' ' || content + ) STORED, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + deleted_at TIMESTAMPTZ +); + +-- 基础索引 +CREATE INDEX idx_posts_author ON posts(author_id, created_at DESC) WHERE deleted_at IS NULL; +CREATE INDEX idx_posts_created ON posts(created_at DESC) WHERE deleted_at IS NULL; + +-- 三元组索引用于帖子内容搜索 +CREATE INDEX idx_posts_search_trgm ON posts USING gin(search_text gin_trgm_ops) + WHERE deleted_at IS NULL; + +-- 全文搜索索引 +CREATE INDEX idx_posts_fulltext ON posts USING gin( + to_tsvector('simple', title || ' ' || content) + ) WHERE deleted_at IS NULL; + +-- 标签 GIN 索引 +CREATE INDEX idx_posts_tags_gin ON posts USING gin(tags) WHERE deleted_at IS NULL; + +-- 复合索引优化热门排序 +CREATE INDEX idx_posts_hot_score ON posts( + (like_count * 2 + comment_count) DESC, + created_at DESC +) WHERE deleted_at IS NULL; + +-- 置顶+时间索引 +CREATE INDEX idx_posts_pinned_created ON posts(author_id, pinned DESC, created_at DESC) + WHERE deleted_at IS NULL; + +-- 关联订单索引 +CREATE INDEX idx_posts_linked_order ON posts(linked_order_id) + WHERE linked_order_id IS NOT NULL AND deleted_at IS NULL; + +-- 图片数组索引 +CREATE INDEX idx_posts_images ON posts USING gin(images) WHERE deleted_at IS NULL; + +CREATE TRIGGER trigger_posts_updated_at + BEFORE UPDATE ON posts + FOR EACH ROW + EXECUTE FUNCTION update_updated_at_column(); \ No newline at end of file diff --git a/desc/sql/dispute/dispute_timeline.sql b/desc/sql/dispute/dispute_timeline.sql new file mode 100644 index 0000000..b3a8f37 --- /dev/null +++ b/desc/sql/dispute/dispute_timeline.sql @@ -0,0 +1,17 @@ +CREATE TABLE dispute_timeline ( + id BIGINT PRIMARY KEY, + dispute_id BIGINT NOT NULL REFERENCES disputes(id), + event_type VARCHAR(30) NOT NULL, + actor_id BIGINT, + actor_name VARCHAR(100), + details JSONB, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + + CONSTRAINT chk_event_type CHECK (event_type IN ( + 'created', 'response', 'reviewing', 'resolved', 'appealed' + )) +); + +CREATE INDEX idx_timeline_dispute ON dispute_timeline(dispute_id, created_at); +CREATE INDEX idx_timeline_dispute_created ON dispute_timeline(dispute_id, created_at); +CREATE INDEX idx_timeline_details ON dispute_timeline USING gin(details); \ No newline at end of file diff --git a/desc/sql/dispute/disputes.sql b/desc/sql/dispute/disputes.sql new file mode 100644 index 0000000..7d511f8 --- /dev/null +++ b/desc/sql/dispute/disputes.sql @@ -0,0 +1,49 @@ +CREATE TABLE disputes ( + id BIGINT PRIMARY KEY, + order_id BIGINT NOT NULL UNIQUE, + initiator_id BIGINT NOT NULL, + initiator_name VARCHAR(100) NOT NULL, + respondent_id BIGINT NOT NULL, + reason TEXT NOT NULL, + evidence TEXT[] DEFAULT ARRAY[]::TEXT[], + status VARCHAR(20) NOT NULL DEFAULT 'open', + result VARCHAR(30), + respondent_reason TEXT, + respondent_evidence TEXT[] DEFAULT ARRAY[]::TEXT[], + appeal_reason TEXT, + appealed_at TIMESTAMPTZ, + resolved_by BIGINT, + resolved_at TIMESTAMPTZ, + search_text TEXT GENERATED ALWAYS AS ( + initiator_name || ' ' || reason || ' ' || coalesce(respondent_reason, '') || ' ' || coalesce(appeal_reason, '') + ) STORED, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + + CONSTRAINT chk_dispute_status CHECK (status IN ('open', 'reviewing', 'resolved', 'appealed')), + CONSTRAINT chk_dispute_result CHECK (result IS NULL OR result IN ('full_refund', 'full_payment', 'partial_refund')) +); + +-- 基础索引 +CREATE INDEX idx_disputes_order ON disputes(order_id); +CREATE INDEX idx_disputes_status ON disputes(status, created_at DESC); +CREATE INDEX idx_disputes_initiator ON disputes(initiator_id); + +-- 三元组索引用于争议内容搜索 +CREATE INDEX idx_disputes_search_trgm ON disputes USING gin(search_text gin_trgm_ops); + +-- 复合索引优化状态查询 +CREATE INDEX idx_disputes_status_created ON disputes(status, created_at DESC); + +-- 参与者索引 +CREATE INDEX idx_disputes_initiator_status ON disputes(initiator_id, status, created_at DESC); +CREATE INDEX idx_disputes_respondent_status ON disputes(respondent_id, status, created_at DESC); + +-- 数组索引优化证据查询 +CREATE INDEX idx_disputes_evidence ON disputes USING gin(evidence); +CREATE INDEX idx_disputes_respondent_evidence ON disputes USING gin(respondent_evidence); + +CREATE TRIGGER trigger_disputes_updated_at + BEFORE UPDATE ON disputes + FOR EACH ROW + EXECUTE FUNCTION update_updated_at_column(); \ No newline at end of file diff --git a/desc/sql/game/games.sql b/desc/sql/game/games.sql new file mode 100644 index 0000000..bbb0065 --- /dev/null +++ b/desc/sql/game/games.sql @@ -0,0 +1,30 @@ +CREATE TABLE games ( + id BIGINT PRIMARY KEY, + name VARCHAR(100) NOT NULL UNIQUE, + icon TEXT NOT NULL, + category VARCHAR(50) NOT NULL, + sort_order INT DEFAULT 0, + is_active BOOLEAN DEFAULT TRUE, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- 基础索引 +CREATE INDEX idx_games_category ON games(category) WHERE is_active = TRUE; +CREATE INDEX idx_games_sort ON games(sort_order, id) WHERE is_active = TRUE; + +-- 三元组索引用于游戏名称模糊搜索 +CREATE INDEX idx_games_name_trgm ON games USING gin(name gin_trgm_ops) WHERE is_active = TRUE; + +-- 复合索引优化分类+排序查询 +CREATE INDEX idx_games_category_sort ON games(category, sort_order, id) WHERE is_active = TRUE; + +-- 全文搜索索引 +CREATE INDEX idx_games_fulltext ON games USING gin( + to_tsvector('simple', name || ' ' || category) + ) WHERE is_active = TRUE; + +CREATE TRIGGER trigger_games_updated_at + BEFORE UPDATE ON games + FOR EACH ROW + EXECUTE FUNCTION update_updated_at_column(); \ No newline at end of file diff --git a/desc/sql/game/player_services.sql b/desc/sql/game/player_services.sql new file mode 100644 index 0000000..a12012a --- /dev/null +++ b/desc/sql/game/player_services.sql @@ -0,0 +1,49 @@ +CREATE TABLE player_services ( + id BIGINT PRIMARY KEY, + player_id BIGINT NOT NULL REFERENCES players(id), + game_id BIGINT NOT NULL, + title VARCHAR(200) NOT NULL, + description TEXT, + price DECIMAL(10,2) NOT NULL, + unit VARCHAR(20) NOT NULL, + rank_range VARCHAR(100), + availability TEXT[] DEFAULT ARRAY[]::TEXT[], + rating DECIMAL(3,2) DEFAULT 5.00, + is_active BOOLEAN DEFAULT TRUE, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + + CONSTRAINT chk_price_positive CHECK (price > 0), + CONSTRAINT chk_service_rating CHECK (rating >= 0 AND rating <= 5) +); + +-- 基础索引 +CREATE INDEX idx_services_player ON player_services(player_id) WHERE is_active = TRUE; +CREATE INDEX idx_services_game ON player_services(game_id) WHERE is_active = TRUE; +CREATE INDEX idx_services_price ON player_services(price); + +-- 三元组索引用于服务标题模糊搜索 +CREATE INDEX idx_services_title_trgm ON player_services USING gin(title gin_trgm_ops) + WHERE is_active = TRUE; + +-- 全文搜索索引 +CREATE INDEX idx_services_fulltext ON player_services USING gin( + to_tsvector('simple', title || ' ' || coalesce(description, '')) + ) WHERE is_active = TRUE; + +-- 复合索引优化价格区间查询 +CREATE INDEX idx_services_game_price ON player_services(game_id, price, rating DESC) + WHERE is_active = TRUE; + +-- 打手+游戏复合索引 +CREATE INDEX idx_services_player_game ON player_services(player_id, game_id) + WHERE is_active = TRUE; + +-- GIN 索引优化时间段查询 +CREATE INDEX idx_services_availability ON player_services USING gin(availability) + WHERE is_active = TRUE; + +CREATE TRIGGER trigger_services_updated_at + BEFORE UPDATE ON player_services + FOR EACH ROW + EXECUTE FUNCTION update_updated_at_column(); \ No newline at end of file diff --git a/desc/sql/game/players.sql b/desc/sql/game/players.sql new file mode 100644 index 0000000..b9e0603 --- /dev/null +++ b/desc/sql/game/players.sql @@ -0,0 +1,40 @@ +CREATE TABLE players +( + id BIGINT PRIMARY KEY, + CREATE TABLE players ( + id BIGINT PRIMARY KEY, + user_id BIGINT NOT NULL UNIQUE, + status VARCHAR(20) NOT NULL DEFAULT 'offline', + rating DECIMAL(3,2) DEFAULT 5.00, + total_orders INT DEFAULT 0, + completed_orders INT DEFAULT 0, + + -- [注意] 此字段为冗余缓存,通过消息队列与 shop_players 表保持一致 + shop_id BIGINT, + + tags TEXT[] DEFAULT ARRAY[]::TEXT[], + games BIGINT[] DEFAULT ARRAY[]::BIGINT[], + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); +-- 基础索引 +CREATE INDEX idx_players_user ON players (user_id); +CREATE INDEX idx_players_shop ON players (shop_id) WHERE shop_id IS NOT NULL; +CREATE INDEX idx_players_status ON players (status); +CREATE INDEX idx_players_rating ON players (rating DESC); + +-- 复合索引优化多条件筛选 +CREATE INDEX idx_players_status_rating ON players (status, rating DESC) WHERE status IN ('available', 'busy'); + +-- GIN 索引优化数组查询 +CREATE INDEX idx_players_games_gin ON players USING gin(games); +CREATE INDEX idx_players_tags_gin ON players USING gin(tags); + +-- 店铺+状态复合索引 +CREATE INDEX idx_players_shop_status ON players (shop_id, status, rating DESC) WHERE shop_id IS NOT NULL; + +-- CREATE TRIGGER trigger_players_updated_at +-- BEFORE UPDATE +-- ON players +-- FOR EACH ROW +-- EXECUTE FUNCTION update_updated_at_column(); diff --git a/desc/sql/game/shop.sql b/desc/sql/game/shop.sql new file mode 100644 index 0000000..93a4bd3 --- /dev/null +++ b/desc/sql/game/shop.sql @@ -0,0 +1,49 @@ +CREATE TABLE shops ( + id BIGINT PRIMARY KEY, + owner_id BIGINT NOT NULL UNIQUE, + name VARCHAR(200) NOT NULL, + banner TEXT, + description TEXT, + rating DECIMAL(3,2) DEFAULT 5.00, + total_orders INT DEFAULT 0, + player_count INT DEFAULT 0, + commission_type VARCHAR(20) NOT NULL DEFAULT 'percentage', + commission_value DECIMAL(10,2) NOT NULL, + allow_multi_shop BOOLEAN DEFAULT FALSE, + allow_independent_orders BOOLEAN DEFAULT TRUE, + dispatch_mode VARCHAR(20) NOT NULL DEFAULT 'manual', + announcements TEXT[] DEFAULT ARRAY[]::TEXT[], + template_config JSONB, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + + CONSTRAINT chk_commission_type CHECK (commission_type IN ('fixed', 'percentage')), + CONSTRAINT chk_dispatch_mode CHECK (dispatch_mode IN ('manual', 'auto')), + CONSTRAINT chk_rating_range CHECK (rating >= 0 AND rating <= 5) +); + +-- 基础索引 +CREATE INDEX idx_shops_owner ON shops(owner_id); +CREATE INDEX idx_shops_rating ON shops(rating DESC); + +-- 三元组索引用于店铺名称模糊搜索 +CREATE INDEX idx_shops_name_trgm ON shops USING gin(name gin_trgm_ops); + +-- 全文搜索索引 +CREATE INDEX idx_shops_fulltext ON shops USING gin( + to_tsvector('simple', name || ' ' || coalesce(description, '')) + ); + +-- 复合索引优化排序查询 +CREATE INDEX idx_shops_rating_orders ON shops(rating DESC, total_orders DESC); + +-- 公告数组索引 +CREATE INDEX idx_shops_announcements ON shops USING gin(announcements); + +-- JSONB 索引优化模板配置查询 +CREATE INDEX idx_shops_template ON shops USING gin(template_config); + +CREATE TRIGGER trigger_shops_updated_at + BEFORE UPDATE ON shops + FOR EACH ROW + EXECUTE FUNCTION update_updated_at_column(); \ No newline at end of file diff --git a/desc/sql/game/shop_invitations.sql b/desc/sql/game/shop_invitations.sql new file mode 100644 index 0000000..56d5463 --- /dev/null +++ b/desc/sql/game/shop_invitations.sql @@ -0,0 +1,17 @@ +CREATE TABLE shop_invitations ( + id BIGINT PRIMARY KEY, + shop_id BIGINT NOT NULL REFERENCES shops(id), + player_id BIGINT NOT NULL, + status VARCHAR(20) NOT NULL DEFAULT 'pending', + invited_by BIGINT NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + responded_at TIMESTAMPTZ, + + CONSTRAINT chk_invitation_status CHECK (status IN ('pending', 'accepted', 'rejected', 'cancelled')), + UNIQUE(shop_id, player_id, status) WHERE status = 'pending' +); + +CREATE INDEX idx_invitations_shop ON shop_invitations(shop_id); +CREATE INDEX idx_invitations_player ON shop_invitations(player_id) WHERE status = 'pending'; +CREATE INDEX idx_invitations_player_status ON shop_invitations(player_id, status, created_at DESC); +CREATE INDEX idx_invitations_shop_status ON shop_invitations(shop_id, status, created_at DESC); \ No newline at end of file diff --git a/desc/sql/game/shop_players.sql b/desc/sql/game/shop_players.sql new file mode 100644 index 0000000..78ccc3a --- /dev/null +++ b/desc/sql/game/shop_players.sql @@ -0,0 +1,21 @@ +CREATE TABLE shop_players +( + shop_id BIGINT NOT NULL REFERENCES shops (id), + player_id BIGINT NOT NULL, + + -- [新增] 标记是否为主店铺。用于个人主页展示和 players.shop_id 缓存源 + is_primary BOOLEAN DEFAULT FALSE, + + joined_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + left_at TIMESTAMPTZ, -- 软删除,表示已离职 + + PRIMARY KEY (shop_id, player_id) +); + +-- 索引 +CREATE INDEX idx_shop_players_player ON shop_players (player_id) WHERE left_at IS NULL; +CREATE INDEX idx_shop_players_shop_active ON shop_players (shop_id, joined_at DESC) WHERE left_at IS NULL; + +-- [新增] 唯一索引:确保一个打手同一时间只能有一个主店铺 +CREATE UNIQUE INDEX idx_shop_players_one_primary + ON shop_players (player_id) WHERE is_primary = TRUE AND left_at IS NULL; diff --git a/desc/sql/order/order_state_log.sql b/desc/sql/order/order_state_log.sql new file mode 100644 index 0000000..f99791e --- /dev/null +++ b/desc/sql/order/order_state_log.sql @@ -0,0 +1,15 @@ +CREATE TABLE order_state_logs ( + id BIGINT PRIMARY KEY, + order_id BIGINT NOT NULL REFERENCES orders(id), + from_status VARCHAR(30), + to_status VARCHAR(30) NOT NULL, + action VARCHAR(50) NOT NULL, + actor_id BIGINT NOT NULL, + actor_role VARCHAR(20) NOT NULL, + metadata JSONB, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_state_logs_order ON order_state_logs(order_id, created_at); +CREATE INDEX idx_state_logs_order_created ON order_state_logs(order_id, created_at DESC); +CREATE INDEX idx_state_logs_actor ON order_state_logs(actor_id, created_at DESC); \ No newline at end of file diff --git a/desc/sql/order/orders.sql b/desc/sql/order/orders.sql new file mode 100644 index 0000000..206e192 --- /dev/null +++ b/desc/sql/order/orders.sql @@ -0,0 +1,65 @@ +CREATE TABLE orders ( + id BIGINT PRIMARY KEY, + consumer_id BIGINT NOT NULL, + consumer_name VARCHAR(100) NOT NULL, + player_id BIGINT NOT NULL, + player_name VARCHAR(100) NOT NULL, + shop_id BIGINT, + shop_name VARCHAR(200), + service_snapshot JSONB NOT NULL, + status VARCHAR(30) NOT NULL DEFAULT 'pending_payment', + total_price DECIMAL(10,2) NOT NULL, + note TEXT, + version INT NOT NULL DEFAULT 1, + timeout_job_id VARCHAR(100), + search_text TEXT GENERATED ALWAYS AS ( + consumer_name || ' ' || player_name || ' ' || coalesce(shop_name, '') || ' ' || coalesce(note, '') + ) STORED, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + accepted_at TIMESTAMPTZ, + closed_at TIMESTAMPTZ, + completed_at TIMESTAMPTZ, + cancelled_at TIMESTAMPTZ, + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + + CONSTRAINT chk_order_status CHECK (status IN ( + 'pending_payment', 'pending_accept', 'in_progress', + 'pending_close', 'pending_review', 'disputed', + 'completed', 'cancelled' + )), + CONSTRAINT chk_price_positive CHECK (total_price > 0) +); + +-- 基础索引 +CREATE INDEX idx_orders_consumer ON orders(consumer_id, created_at DESC); +CREATE INDEX idx_orders_player ON orders(player_id, created_at DESC); +CREATE INDEX idx_orders_shop ON orders(shop_id, created_at DESC) WHERE shop_id IS NOT NULL; +CREATE INDEX idx_orders_status ON orders(status, created_at DESC); +CREATE INDEX idx_orders_timeout ON orders(timeout_job_id) WHERE timeout_job_id IS NOT NULL; + +-- 三元组索引用于订单搜索 +CREATE INDEX idx_orders_search_trgm ON orders USING gin(search_text gin_trgm_ops); + +-- 复合索引优化多条件查询 +CREATE INDEX idx_orders_consumer_status_created ON orders(consumer_id, status, created_at DESC); +CREATE INDEX idx_orders_player_status_created ON orders(player_id, status, created_at DESC); +CREATE INDEX idx_orders_shop_status_created ON orders(shop_id, status, created_at DESC) + WHERE shop_id IS NOT NULL; + +-- 状态+时间复合索引 (用于超时任务扫描) +CREATE INDEX idx_orders_status_timeout ON orders(status, created_at) + WHERE status IN ('pending_accept', 'pending_close', 'pending_review'); + +-- JSONB 索引优化服务快照查询 +CREATE INDEX idx_orders_service_snapshot ON orders USING gin(service_snapshot); + +-- 价格区间索引 +CREATE INDEX idx_orders_price ON orders(total_price) WHERE status = 'completed'; + +-- 时间范围索引 (用于统计) +CREATE INDEX idx_orders_completed_at ON orders(completed_at DESC) WHERE completed_at IS NOT NULL; + +CREATE TRIGGER trigger_orders_updated_at + BEFORE UPDATE ON orders + FOR EACH ROW + EXECUTE FUNCTION update_updated_at_column(); \ No newline at end of file diff --git a/desc/sql/review/reviews.sql b/desc/sql/review/reviews.sql new file mode 100644 index 0000000..616ddd3 --- /dev/null +++ b/desc/sql/review/reviews.sql @@ -0,0 +1,37 @@ +CREATE TABLE reviews ( + id BIGINT PRIMARY KEY, + order_id BIGINT NOT NULL, + from_user_id BIGINT NOT NULL, + from_user_name VARCHAR(100) NOT NULL, + from_user_avatar TEXT, + to_user_id BIGINT NOT NULL, + rating SMALLINT NOT NULL, + content TEXT, + sealed BOOLEAN DEFAULT TRUE, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + unsealed_at TIMESTAMPTZ, + + CONSTRAINT chk_rating_range CHECK (rating >= 1 AND rating <= 5), + UNIQUE(order_id, from_user_id) +); + +-- 基础索引 +CREATE INDEX idx_reviews_order ON reviews(order_id); +CREATE INDEX idx_reviews_to_user ON reviews(to_user_id, created_at + + CREATE INDEX idx_reviews_to_user ON reviews(to_user_id, created_at DESC) WHERE sealed = FALSE; +CREATE INDEX idx_reviews_from_user ON reviews(from_user_id); + +-- 三元组索引用于评价内容搜索 +CREATE INDEX idx_reviews_content_trgm ON reviews USING gin(content gin_trgm_ops) + WHERE sealed = FALSE AND content IS NOT NULL; + +-- 复合索引优化用户评价列表 +CREATE INDEX idx_reviews_to_user_rating ON reviews(to_user_id, rating DESC, created_at DESC) + WHERE sealed = FALSE; + +-- 复合索引优化订单评价查询 +CREATE INDEX idx_reviews_order_sealed ON reviews(order_id, sealed); + +-- 评分统计索引 +CREATE INDEX idx_reviews_rating_created ON reviews(rating, created_at DESC) WHERE sealed = FALSE; \ No newline at end of file diff --git a/desc/sql/search/favorites.sql b/desc/sql/search/favorites.sql new file mode 100644 index 0000000..8207300 --- /dev/null +++ b/desc/sql/search/favorites.sql @@ -0,0 +1,20 @@ +CREATE TABLE favorites ( + id BIGINT PRIMARY KEY, + user_id BIGINT NOT NULL, + target_type VARCHAR(20) NOT NULL, + target_id BIGINT NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + + CONSTRAINT chk_target_type CHECK (target_type IN ('player', 'shop')), + UNIQUE(user_id, target_type, target_id) +); + +-- 基础索引 +CREATE INDEX idx_favorites_user ON favorites(user_id, created_at DESC); +CREATE INDEX idx_favorites_target ON favorites(target_type, target_id); + +-- 复合索引优化收藏列表查询 +CREATE INDEX idx_favorites_user_type_created ON favorites(user_id, target_type, created_at DESC); + +-- 目标反查索引(统计收藏数) +CREATE INDEX idx_favorites_target_count ON favorites(target_type, target_id); \ No newline at end of file diff --git a/desc/sql/users/user_follows.sql b/desc/sql/users/user_follows.sql new file mode 100644 index 0000000..d92ec67 --- /dev/null +++ b/desc/sql/users/user_follows.sql @@ -0,0 +1,12 @@ +CREATE TABLE user_follows ( + id BIGINT PRIMARY KEY, + follower_id BIGINT NOT NULL REFERENCES users(id), + followee_id BIGINT NOT NULL REFERENCES users(id), + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + + UNIQUE(follower_id, followee_id), + CONSTRAINT chk_no_self_follow CHECK (follower_id != followee_id) + ); + +CREATE INDEX idx_follows_follower ON user_follows(follower_id); +CREATE INDEX idx_follows_followee ON user_follows(followee_id); \ No newline at end of file diff --git a/desc/sql/users/user_preferences.sql b/desc/sql/users/user_preferences.sql new file mode 100644 index 0000000..0233742 --- /dev/null +++ b/desc/sql/users/user_preferences.sql @@ -0,0 +1,16 @@ +CREATE TABLE user_preferences ( + user_id BIGINT PRIMARY KEY REFERENCES users(id), + notification_order BOOLEAN DEFAULT TRUE, + notification_community BOOLEAN DEFAULT TRUE, + notification_system BOOLEAN DEFAULT TRUE, + theme VARCHAR(20) DEFAULT 'light', + language VARCHAR(10) DEFAULT 'zh-CN', + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + + CONSTRAINT chk_theme CHECK (theme IN ('light', 'dark', 'auto')) +); + +CREATE TRIGGER trigger_preferences_updated_at + BEFORE UPDATE ON user_preferences + FOR EACH ROW + EXECUTE FUNCTION update_updated_at_column(); \ No newline at end of file diff --git a/desc/sql/users/user_verifications.sql b/desc/sql/users/user_verifications.sql index 1ebcd0e..794fb1b 100644 --- a/desc/sql/users/user_verifications.sql +++ b/desc/sql/users/user_verifications.sql @@ -1,35 +1,52 @@ -CREATE TABLE user_verifications ( - id BIGINT PRIMARY KEY, - user_id BIGINT NOT NULL REFERENCES users(id), - role VARCHAR(20) NOT NULL, - status VARCHAR(20) NOT NULL DEFAULT 'pending', - materials JSONB NOT NULL, - reject_reason TEXT, - reviewed_by BIGINT, - reviewed_at TIMESTAMPTZ, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), +CREATE TABLE user_verifications +( + id BIGINT PRIMARY KEY, + user_id BIGINT NOT NULL REFERENCES users (id), + role VARCHAR(20) NOT NULL, + status VARCHAR(20) NOT NULL DEFAULT 'pending', + materials JSONB NOT NULL, + reject_reason TEXT, + reviewed_by BIGINT, + reviewed_at TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - CONSTRAINT chk_verification_status CHECK (status IN ('pending', 'approved', 'rejected')), - UNIQUE(user_id, role) -- 每个角色只有一条最新的认证记录 + CONSTRAINT chk_verification_status CHECK (status IN ('pending', 'approved', 'rejected')), + UNIQUE (user_id, role) -- 每个角色只有一条最新的认证记录 ); -- Note:Auto update verification_status of users table CREATE OR REPLACE FUNCTION sync_user_verification_status() -RETURNS TRIGGER AS $$ + RETURNS TRIGGER AS +$$ DECLARE -status_json JSONB; + status_json JSONB; BEGIN -SELECT jsonb_object_agg(role, status) -INTO status_json -FROM user_verifications -WHERE user_id = NEW.user_id; + SELECT jsonb_object_agg(role, status) + INTO status_json + FROM user_verifications + WHERE user_id = NEW.user_id; -UPDATE users SET verification_status = status_json WHERE id = NEW.user_id; -RETURN NEW; + UPDATE users SET verification_status = status_json WHERE id = NEW.user_id; + RETURN NEW; END; $$ LANGUAGE plpgsql; CREATE TRIGGER trigger_sync_verifications - AFTER INSERT OR UPDATE ON user_verifications - FOR EACH ROW EXECUTE FUNCTION sync_user_verification_status(); \ No newline at end of file + AFTER INSERT OR UPDATE + ON user_verifications + FOR EACH ROW +EXECUTE FUNCTION sync_user_verification_status(); + +-- | 字段名 | 类型 | 技术含义 | 业务目的与设计思考 | +-- | :--- | :--- | :--- | :--- | +-- | **`id`** | BIGINT | **主键 (Primary Key)** | **这张认证申请单的唯一编号**。
后端代码在处理“审核通过”这个动作时,操作的是这个 ID(例如:`Approve(verificationId)`)。 | +-- | **`user_id`** | BIGINT | **外键 (Foreign Key)** | **谁在申请?**
关联到 `users` 表。通过这个字段,我们可以查到申请人的昵称、手机号等信息。 | +-- | **`role`** | VARCHAR | 普通字段 | **申请什么身份?**
枚举值:`player` (打手), `owner` (店长)。
因为一个用户可以同时是打手和店长,所以需要区分这条记录是申请哪个身份的。 | +-- | **`status`** | VARCHAR | 状态字段 | **当前进度如何?**
枚举值:`pending` (待审核), `approved` (已通过), `rejected` (已驳回)。 | +-- | **`materials`** | **JSONB** | **非结构化数据** | **提交了什么证明材料?**
**精华设计**:这里不使用多张表或多个字段,而是用 JSON 存。因为打手需要传“身份证+段位图”,店长需要传“营业执照”。不同角色的材料结构不同,用 JSONB 最灵活,以后改规则不需要改表结构。 | +-- | **`reject_reason`** | TEXT | 文本 | **驳回理由**。
只有当 `status` = 'rejected' 时才有值,告诉用户哪里填错了。 | +-- | **`reviewed_by`** | BIGINT | 审计字段 | **谁审核的?**
记录是哪个管理员(Admin ID)点击了通过或拒绝。用于内部追责和审计。 | +-- | **`reviewed_at`** | TIMESTAMPTZ | 时间字段 | **什么时候审核的?**
用于统计管理员的工作效率,或者展示给用户“审核耗时”。 | +-- | **`created_at`** | TIMESTAMPTZ | 时间字段 | **申请提交时间**。 | +-- | **`updated_at`** | TIMESTAMPTZ | 时间字段 | **最后更新时间**。
比如用户重新上传了图片,这个时间会变。 | \ No newline at end of file diff --git a/desc/sql/wallet/wallet_transactions.sql b/desc/sql/wallet/wallet_transactions.sql new file mode 100644 index 0000000..9a307e7 --- /dev/null +++ b/desc/sql/wallet/wallet_transactions.sql @@ -0,0 +1,33 @@ +CREATE TABLE wallet_transactions ( + id BIGINT PRIMARY KEY, + user_id BIGINT NOT NULL, + type VARCHAR(20) NOT NULL, + amount DECIMAL(12,2) NOT NULL, + balance_after DECIMAL(12,2) NOT NULL, + description VARCHAR(500) NOT NULL, + order_id BIGINT, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + search_text TEXT GENERATED ALWAYS AS ( + type || ' ' || description + ) STORED, + + CONSTRAINT chk_transaction_type CHECK (type IN ('topup', 'payment', 'income', 'withdrawal', 'refund')) +); + +-- 基础索引 +CREATE INDEX idx_transactions_user ON wallet_transactions(user_id, created_at DESC); +CREATE INDEX idx_transactions_order ON wallet_transactions(order_id) WHERE order_id IS NOT NULL; +CREATE INDEX idx_transactions_type ON wallet_transactions(user_id, type, created_at DESC); + +-- 三元组索引用于交易描述搜索 +CREATE INDEX idx_transactions_search_trgm ON wallet_transactions USING gin(search_text gin_trgm_ops); + +-- 复合索引优化用户交易查询 +CREATE INDEX idx_transactions_user_type_created ON wallet_transactions(user_id, type, created_at DESC); + +-- 时间范围索引 (用于统计) +CREATE INDEX idx_transactions_created_amount ON wallet_transactions(created_at DESC, amount); + +-- 订单关联索引 +CREATE INDEX idx_transactions_order_type ON wallet_transactions(order_id, type) + WHERE order_id IS NOT NULL; \ No newline at end of file diff --git a/desc/sql/wallet/wallets.sql b/desc/sql/wallet/wallets.sql new file mode 100644 index 0000000..a2427c9 --- /dev/null +++ b/desc/sql/wallet/wallets.sql @@ -0,0 +1,17 @@ +CREATE TABLE wallets ( + user_id BIGINT PRIMARY KEY, + balance DECIMAL(12,2) NOT NULL DEFAULT 0.00, + frozen_balance DECIMAL(12,2) NOT NULL DEFAULT 0.00, + version INT NOT NULL DEFAULT 1, -- 必须使用乐观锁 + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + + CONSTRAINT chk_balance_non_negative CHECK (balance >= 0), + CONSTRAINT chk_frozen_non_negative CHECK (frozen_balance >= 0) +); + +CREATE INDEX idx_wallets_updated ON wallets(updated_at DESC); + +CREATE TRIGGER trigger_wallets_updated_at + BEFORE UPDATE ON wallets + FOR EACH ROW + EXECUTE FUNCTION update_updated_at_column(); \ No newline at end of file