├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── client ├── main.go └── service │ ├── access.go │ ├── auth.go │ ├── branch.go │ ├── company.go │ ├── employee.go │ ├── feature.go │ ├── group.go │ ├── region.go │ ├── user.go │ └── util.go ├── cmd └── cli.go ├── internal ├── config │ └── config.go ├── model │ ├── access.go │ ├── access_group.go │ ├── branch.go │ ├── branches_region.go │ ├── company.go │ ├── employee.go │ ├── feature.go │ ├── group.go │ ├── region.go │ ├── request_password.go │ └── user.go ├── route │ └── route.go ├── schema │ ├── migrate.go │ └── seed.go └── service │ ├── access.go │ ├── auth.go │ ├── branch.go │ ├── company.go │ ├── employee.go │ ├── feature.go │ ├── group.go │ ├── package_feature.go │ ├── region.go │ ├── user.go │ └── utils.go ├── makefile └── server.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | 17 | .DS_Store 18 | .vscode 19 | .env 20 | go.mod 21 | go.sum 22 | user-service 23 | cmd/cmd-user-service 24 | # client 25 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:alpine 2 | 3 | WORKDIR /app 4 | COPY user-service /app/ 5 | COPY ./cmd/cmd-user-service /app/ 6 | 7 | CMD ["/app/user-service"] 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # user-service 2 | user service using grpc go, postgresql and redis. The service is designed to be accessed by the internal network so that the grpc connection used is an insecure connection. This service is part of ERP Microservices. 3 | 4 | ## Get Started 5 | - git clone git@github.com:jacky-htg/user-service.git 6 | - make init 7 | - cp .env.example .env (and edit with your environment) 8 | - make migrate 9 | - make seed 10 | - make server 11 | - You can test the service using `go run client/main.go` and select the test case on file client/main.go 12 | 13 | ## Features 14 | - [X] Companies 15 | - [X] Regions 16 | - [X] Branches 17 | - [X] Employees 18 | - [X] Company Features 19 | - [X] Users 20 | - [X] Groups 21 | - [X] Auths 22 | - [X] Role Base Access Control (RBAC) 23 | 24 | ### Companies 25 | - [X] Multi companies 26 | - [X] Company registration 27 | - [X] Companies CRUD 28 | 29 | ### Regions 30 | - [X] Multi Regions 31 | - [X] One region can be assigned to many branches. 32 | - [X] Regions CRUD 33 | 34 | ### Branches 35 | - [X] Multi Branches 36 | - [X] Branches CRUD 37 | 38 | ### Employees 39 | - [X] Employees CRUD 40 | 41 | ### Auths 42 | - [X] Login 43 | - [X] Forgot Password 44 | - [X] Reset Password 45 | - [X] Change Password 46 | - [X] Check Authorization 47 | 48 | ### Users, Groups, Access and RBAC 49 | - [X] Users CRUD 50 | - [X] Group CRUD 51 | - [X] List Access 52 | - [X] Multi users 53 | - [X] One role can be assigned multi access 54 | - [X] Role Base Access Control (RBAC) 55 | 56 | ### Company Features 57 | - [X] List Features 58 | - [X] List Package Feature 59 | - [X] View Package Feature 60 | - [X] Company Feature Setting : The company can use the whole of features, or cherry pick part of features. 61 | 62 | ## How To Contribute 63 | - Give star or clone and fork the repository 64 | - Report the bug 65 | - Submit issue for request of enhancement 66 | - Pull Request for fixing bug or enhancement module 67 | 68 | ## License 69 | [The license of application is GPL-3.0](https://github.com/jacky-htg/user-service/blob/main/LICENSE), You can use this apllication for commercial use, distribution or modification. But there is no liability and warranty. Please read the license details carefully. 70 | 71 | ## Link Repository 72 | - [API Gateway for ERP](https://github.com/jacky-htg/api-gateway-service) 73 | - [Inventory Service](https://github.com/jacky-htg/inventory-service) 74 | - [Purchase Service](https://github.com/jacky-htg/purchase-service) 75 | - [Sales Service](https://github.com/jacky-htg/sales-service) 76 | - [Ledger Service](https://github.com/jacky-htg/ledger-service) 77 | - [Simple gRPC Skeleton](https://github.com/jacky-htg/grpc-skeleton) -------------------------------------------------------------------------------- /client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/jacky-htg/erp-proto/go/pb/users" 7 | "golang.org/x/net/context" 8 | "google.golang.org/grpc" 9 | 10 | "github.com/jacky-htg/user-service/client/service" 11 | ) 12 | 13 | func main() { 14 | 15 | var conn *grpc.ClientConn 16 | conn, err := grpc.Dial(":8000", grpc.WithInsecure()) 17 | if err != nil { 18 | log.Fatalf("did not connect: %s", err) 19 | } 20 | defer conn.Close() 21 | 22 | ctx := context.Background() 23 | 24 | // auth := users.NewAuthServiceClient(conn) 25 | // user := users.NewUserServiceClient(conn) 26 | company := users.NewCompanyServiceClient(conn) 27 | // region := users.NewRegionServiceClient(conn) 28 | // branch := users.NewBranchServiceClient(conn) 29 | // employee := users.NewEmployeeServiceClient(conn) 30 | // feature := users.NewFeatureServiceClient(conn) 31 | // packageFeature := users.NewPackageFeatureServiceClient(conn) 32 | // access := users.NewAccessServiceClient(conn) 33 | // group := users.NewGroupServiceClient(conn) 34 | 35 | // ctx = service.Login(ctx, auth) 36 | // service.ForgotPassword(ctx, auth) 37 | // service.ResetPassword(ctx, auth) 38 | // service.ChangePassword(ctx, auth) 39 | // service.IsAuth(ctx, auth) 40 | // service.CreateUser(ctx, user) 41 | // service.UpdateUser(ctx, user) 42 | // service.ViewUser(ctx, user) 43 | // service.DeleteUser(ctx, user) 44 | // service.GetUserByToken(ctx, user) 45 | // service.ListUser(ctx, user) 46 | service.Registration(ctx, company) 47 | // service.UpdateCompany(ctx, company) 48 | // service.ViewCompany(ctx, company) 49 | // service.CreateRegion(ctx, region) 50 | // service.UpdateRegion(ctx, region) 51 | // service.ViewRegion(ctx, region) 52 | // service.DeleteRegion(ctx, region) 53 | // service.ListRegion(ctx, region) 54 | // service.CreateBranch(ctx, branch) 55 | // service.UpdateBranch(ctx, branch) 56 | // service.ViewBranch(ctx, branch) 57 | // service.DeleteBranch(ctx, branch) 58 | // service.ListBranch(ctx, branch) 59 | // service.CreateEmployee(ctx, employee) 60 | // service.UpdateEmployee(ctx, employee) 61 | // service.ViewEmployee(ctx, employee) 62 | // service.DeleteEmployee(ctx, employee) 63 | // service.ListEmployee(ctx, employee) 64 | // service.ListFeature(ctx, feature) 65 | // service.ListPackageFeature(ctx, packageFeature) 66 | // service.ViewPackageFeature(ctx, packageFeature) 67 | // service.ViewAccessTree(ctx, access) 68 | // service.CreateGroup(ctx, group) 69 | // service.ViewGroup(ctx, group) 70 | // service.DeleteGroup(ctx, group) 71 | // service.ListGroup(ctx, group) 72 | // service.GrantAccess(ctx, group) 73 | // service.RevokeAccess(ctx, group) 74 | } 75 | -------------------------------------------------------------------------------- /client/service/access.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "log" 6 | 7 | "github.com/jacky-htg/erp-proto/go/pb/users" 8 | ) 9 | 10 | // ViewAccessTree service client 11 | func ViewAccessTree(ctx context.Context, access users.AccessServiceClient) { 12 | response, err := access.List(setMetadata(ctx), &users.MyEmpty{}) 13 | if err != nil { 14 | log.Fatalf("Error when calling grpc service: %s", err) 15 | } 16 | log.Printf("Resp received: %v", response) 17 | } 18 | -------------------------------------------------------------------------------- /client/service/auth.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "log" 6 | 7 | "github.com/jacky-htg/erp-pkg/app" 8 | "github.com/jacky-htg/erp-proto/go/pb/users" 9 | ) 10 | 11 | // Login service client 12 | func Login(ctx context.Context, auth users.AuthServiceClient) context.Context { 13 | response, err := auth.Login(ctx, &users.LoginRequest{Username: "wira-admin", Password: "1234"}) 14 | if err != nil { 15 | log.Fatalf("Error when calling grpc service: %s", err) 16 | } 17 | log.Printf("Resp received: %v", response) 18 | 19 | ctx = context.WithValue(ctx, app.Ctx("token"), response.GetToken()) 20 | ctx = context.WithValue(ctx, app.Ctx("userID"), response.GetUser().GetId()) 21 | ctx = context.WithValue(ctx, app.Ctx("companyID"), response.GetUser().GetCompanyId()) 22 | 23 | return ctx 24 | } 25 | 26 | // ForgotPassword service client 27 | func ForgotPassword(ctx context.Context, auth users.AuthServiceClient) { 28 | response, err := auth.ForgotPassword(ctx, &users.ForgotPasswordRequest{Email: "rijal.asep.nugroho@gmail.com"}) 29 | if err != nil { 30 | log.Fatalf("Error when calling grpc service: %s", err) 31 | } 32 | log.Printf("Resp received: %v", response) 33 | } 34 | 35 | // ResetPassword service client 36 | func ResetPassword(ctx context.Context, auth users.AuthServiceClient) { 37 | response, err := auth.ResetPassword(ctx, &users.ResetPasswordRequest{ 38 | Token: "4d5de8c6-46cd-453f-88f1-fe904ee01746", 39 | NewPassword: "12345", 40 | RePassword: "12345", 41 | }) 42 | 43 | if err != nil { 44 | log.Fatalf("Error when calling grpc service: %s", err) 45 | } 46 | log.Printf("Resp received: %v", response) 47 | } 48 | 49 | // ChangePassword service client 50 | func ChangePassword(ctx context.Context, auth users.AuthServiceClient) { 51 | response, err := auth.ChangePassword(setMetadata(ctx), &users.ChangePasswordRequest{ 52 | OldPassword: "12345", 53 | NewPassword: "1234", 54 | RePassword: "1234", 55 | }) 56 | 57 | if err != nil { 58 | log.Fatalf("Error when calling grpc service: %s", err) 59 | } 60 | log.Printf("Resp received: %v", response) 61 | } 62 | 63 | // IsAuth service client 64 | func IsAuth(ctx context.Context, auth users.AuthServiceClient) { 65 | response, err := auth.IsAuth(setMetadata(ctx), &users.MyString{String_: "asal"}) 66 | 67 | if err != nil { 68 | log.Fatalf("Error when calling grpc service: %s", err) 69 | } 70 | log.Printf("Resp received: %v", response) 71 | } 72 | -------------------------------------------------------------------------------- /client/service/branch.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "log" 7 | 8 | "github.com/jacky-htg/erp-proto/go/pb/users" 9 | ) 10 | 11 | // CreateBranch service client 12 | func CreateBranch(ctx context.Context, branch users.BranchServiceClient) { 13 | response, err := branch.Create(setMetadata(ctx), &users.Branch{ 14 | Code: "LAPG", 15 | Name: "Lampung Branch", 16 | Address: "Jl Address", 17 | City: "Lampung", 18 | Province: "Lampung", 19 | Phone: "0814111111111", 20 | Pic: "Mr Lampung", 21 | PicPhone: "081122222222", 22 | RegionId: "69927081-7fd4-4c2f-9ac1-32647aa055a7", 23 | }) 24 | if err != nil { 25 | log.Fatalf("Error when calling grpc service: %s", err) 26 | } 27 | log.Printf("Resp received: %v", response) 28 | } 29 | 30 | // UpdateBranch service client 31 | func UpdateBranch(ctx context.Context, branch users.BranchServiceClient) { 32 | response, err := branch.Update(setMetadata(ctx), &users.Branch{ 33 | Id: "f174f588-c10d-4ac4-ade3-6ab326764bf5", 34 | Name: "Lampung Branch", 35 | Address: "Jl Address", 36 | City: "Lampung", 37 | Province: "Lampung", 38 | Phone: "0814111111111", 39 | Pic: "Mr Lampung", 40 | PicPhone: "081122222222", 41 | RegionId: "69927081-7fd4-4c2f-9ac1-32647aa055a7", 42 | }) 43 | if err != nil { 44 | log.Fatalf("Error when calling grpc service: %s", err) 45 | } 46 | log.Printf("Resp received: %v", response) 47 | } 48 | 49 | // ViewBranch service client 50 | func ViewBranch(ctx context.Context, branch users.BranchServiceClient) { 51 | response, err := branch.View(setMetadata(ctx), &users.Id{Id: "f174f588-c10d-4ac4-ade3-6ab326764bf5"}) 52 | if err != nil { 53 | log.Fatalf("Error when calling grpc service: %s", err) 54 | } 55 | log.Printf("Resp received: %v", response) 56 | } 57 | 58 | // DeleteBranch service client 59 | func DeleteBranch(ctx context.Context, branch users.BranchServiceClient) { 60 | response, err := branch.Delete(setMetadata(ctx), &users.Id{Id: "f174f588-c10d-4ac4-ade3-6ab326764bf5"}) 61 | if err != nil { 62 | log.Fatalf("Error when calling grpc service: %s", err) 63 | } 64 | log.Printf("Resp received: %v", response) 65 | } 66 | 67 | // ListBranch service client 68 | func ListBranch(ctx context.Context, branch users.BranchServiceClient) { 69 | stream, err := branch.List(setMetadata(ctx), &users.ListBranchRequest{}) 70 | if err != nil { 71 | log.Fatalf("Error when calling grpc service: %s", err) 72 | } 73 | 74 | for { 75 | resp, err := stream.Recv() 76 | if err == io.EOF { 77 | log.Fatal("end stream") 78 | break 79 | } 80 | if err != nil { 81 | log.Fatalf("cannot receive %v", err) 82 | } 83 | log.Printf("Resp received: %s : %v", resp.GetBranch(), resp.GetPagination()) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /client/service/company.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "log" 6 | 7 | "github.com/jacky-htg/erp-proto/go/pb/users" 8 | ) 9 | 10 | // Registration service client 11 | func Registration(ctx context.Context, company users.CompanyServiceClient) { 12 | value, ok := users.EnumPackageOfFeature_value["ALL"] 13 | if !ok { 14 | log.Fatal("Invalid package of Feature") 15 | } 16 | 17 | response, err := company.Registration(ctx, &users.CompanyRegistration{ 18 | Company: &users.Company{ 19 | Address: "Jalan Minangkabau", 20 | City: "Jakarta Pusat", 21 | Code: "SRTU", 22 | Logo: "logo", 23 | Name: "Sri Ratu", 24 | Npwp: "npwp", 25 | PackageOfFeature: users.EnumPackageOfFeature(value), 26 | Phone: "081244444444", 27 | Pic: "Admin Pasaraya", 28 | PicPhone: "081312222222", 29 | Province: "Jakarta", 30 | }, 31 | User: &users.User{ 32 | Email: "admin.sriratu@gmail.com", 33 | Name: "Admin Sri Ratu", 34 | Username: "admin-srtu", 35 | }, 36 | }) 37 | if err != nil { 38 | log.Fatalf("Error when calling grpc service: %s", err) 39 | } 40 | log.Printf("Resp received: %v", response) 41 | } 42 | 43 | // UpdateCompany service client 44 | func UpdateCompany(ctx context.Context, company users.CompanyServiceClient) { 45 | value, ok := users.EnumPackageOfFeature_value["ALL"] 46 | if !ok { 47 | log.Fatal("Invalid package of Feature") 48 | } 49 | 50 | response, err := company.Update(setMetadata(ctx), &users.Company{ 51 | Id: "45e5719f-6797-4463-b454-7413ce3a58f7", 52 | Address: "Jalan Minangkabau", 53 | City: "Jakarta Pusat", 54 | Logo: "logo", 55 | Name: "Pasaraya", 56 | Npwp: "npwp", 57 | PackageOfFeature: users.EnumPackageOfFeature(value), 58 | Phone: "081244444444", 59 | Pic: "Admin Pasaraya", 60 | PicPhone: "081312222222", 61 | Province: "Jakarta", 62 | }) 63 | if err != nil { 64 | log.Fatalf("Error when calling grpc service: %s", err) 65 | } 66 | log.Printf("Resp received: %v", response) 67 | } 68 | 69 | // ViewCompany service client 70 | func ViewCompany(ctx context.Context, company users.CompanyServiceClient) { 71 | response, err := company.View(setMetadata(ctx), &users.Id{Id: "45e5719f-6797-4463-b454-7413ce3a58f7"}) 72 | if err != nil { 73 | log.Fatalf("Error when calling grpc service: %s", err) 74 | } 75 | log.Printf("Resp received: %v", response) 76 | } 77 | -------------------------------------------------------------------------------- /client/service/employee.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "log" 7 | 8 | "github.com/jacky-htg/erp-proto/go/pb/users" 9 | ) 10 | 11 | // CreateEmployee service client 12 | func CreateEmployee(ctx context.Context, employee users.EmployeeServiceClient) { 13 | response, err := employee.Create(setMetadata(ctx), &users.Employee{ 14 | Code: "RAS", 15 | Name: "Rijal Asepnugroho", 16 | Address: "Jl Address", 17 | City: "Lampung", 18 | Province: "Lampung", 19 | Jabatan: "Head of IT", 20 | User: &users.User{ 21 | Id: "362e5eb5-51b5-412d-8d5f-081c5aa494ce", 22 | }, 23 | }) 24 | if err != nil { 25 | log.Fatalf("Error when calling grpc service: %s", err) 26 | } 27 | log.Printf("Resp received: %v", response) 28 | } 29 | 30 | // UpdateEmployee service client 31 | func UpdateEmployee(ctx context.Context, employee users.EmployeeServiceClient) { 32 | response, err := employee.Update(setMetadata(ctx), &users.Employee{ 33 | Id: "382a011d-8c2f-4e6b-8e8f-7b41f1332dca", 34 | Name: "Rijal Asepnugroho", 35 | Address: "Jl Address", 36 | City: "Lampung", 37 | Province: "Lampung", 38 | Jabatan: "CTO", 39 | }) 40 | if err != nil { 41 | log.Fatalf("Error when calling grpc service: %s", err) 42 | } 43 | log.Printf("Resp received: %v", response) 44 | } 45 | 46 | // ViewEmployee service client 47 | func ViewEmployee(ctx context.Context, employee users.EmployeeServiceClient) { 48 | response, err := employee.View(setMetadata(ctx), &users.Id{Id: "382a011d-8c2f-4e6b-8e8f-7b41f1332dca"}) 49 | if err != nil { 50 | log.Fatalf("Error when calling grpc service: %s", err) 51 | } 52 | log.Printf("Resp received: %v", response) 53 | } 54 | 55 | // DeleteEmployee service client 56 | func DeleteEmployee(ctx context.Context, employee users.EmployeeServiceClient) { 57 | response, err := employee.Delete(setMetadata(ctx), &users.Id{Id: "382a011d-8c2f-4e6b-8e8f-7b41f1332dca"}) 58 | if err != nil { 59 | log.Fatalf("Error when calling grpc service: %s", err) 60 | } 61 | log.Printf("Resp received: %v", response) 62 | } 63 | 64 | // ListEmployee service client 65 | func ListEmployee(ctx context.Context, employee users.EmployeeServiceClient) { 66 | stream, err := employee.List(setMetadata(ctx), &users.ListEmployeeRequest{}) 67 | if err != nil { 68 | log.Fatalf("Error when calling grpc service: %s", err) 69 | } 70 | 71 | for { 72 | resp, err := stream.Recv() 73 | if err == io.EOF { 74 | log.Fatal("end stream") 75 | break 76 | } 77 | if err != nil { 78 | log.Fatalf("cannot receive %v", err) 79 | } 80 | log.Printf("Resp received: %s : %v", resp.GetEmployee(), resp.GetPagination()) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /client/service/feature.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "log" 7 | 8 | "github.com/jacky-htg/erp-proto/go/pb/users" 9 | ) 10 | 11 | // ListFeature service client 12 | func ListFeature(ctx context.Context, feature users.FeatureServiceClient) { 13 | stream, err := feature.List(setMetadata(ctx), &users.MyEmpty{}) 14 | if err != nil { 15 | log.Fatalf("Error when calling grpc service: %s", err) 16 | } 17 | 18 | for { 19 | resp, err := stream.Recv() 20 | if err == io.EOF { 21 | log.Fatal("end stream") 22 | break 23 | } 24 | if err != nil { 25 | log.Fatalf("cannot receive %v", err) 26 | } 27 | log.Printf("Resp received: %v", resp.GetFeature()) 28 | } 29 | } 30 | 31 | // ListPackageFeature service client 32 | func ListPackageFeature(ctx context.Context, packageFeature users.PackageFeatureServiceClient) { 33 | stream, err := packageFeature.List(setMetadata(ctx), &users.MyEmpty{}) 34 | if err != nil { 35 | log.Fatalf("Error when calling grpc service: %s", err) 36 | } 37 | 38 | for { 39 | resp, err := stream.Recv() 40 | if err == io.EOF { 41 | log.Fatal("end stream") 42 | break 43 | } 44 | if err != nil { 45 | log.Fatalf("cannot receive %v", err) 46 | } 47 | log.Printf("Resp received: %v", resp.GetPackageOfFeature()) 48 | } 49 | } 50 | 51 | // ViewPackageFeature service client 52 | func ViewPackageFeature(ctx context.Context, packageFeature users.PackageFeatureServiceClient) { 53 | response, err := packageFeature.View(setMetadata(ctx), &users.Id{Id: "e1c14424-9ec2-41e1-9709-f65fdaaeddce"}) 54 | if err != nil { 55 | log.Fatalf("Error when calling grpc service: %s", err) 56 | } 57 | log.Printf("Resp received: %v", response) 58 | } 59 | -------------------------------------------------------------------------------- /client/service/group.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "log" 7 | 8 | "github.com/jacky-htg/erp-proto/go/pb/users" 9 | ) 10 | 11 | // CreateGroup service client 12 | func CreateGroup(ctx context.Context, group users.GroupServiceClient) { 13 | response, err := group.Create(setMetadata(ctx), &users.Group{ 14 | Name: "Co-Admin", 15 | }) 16 | if err != nil { 17 | log.Fatalf("Error when calling grpc service: %s", err) 18 | } 19 | log.Printf("Resp received: %v", response) 20 | } 21 | 22 | // UpdateGroup service client 23 | func UpdateGroup(ctx context.Context, group users.GroupServiceClient) { 24 | response, err := group.Update(setMetadata(ctx), &users.Group{ 25 | Id: "749844e9-d4fa-4121-8a1f-b34c0cba5c02", 26 | Name: "admin ajah", 27 | }) 28 | if err != nil { 29 | log.Fatalf("Error when calling grpc service: %s", err) 30 | } 31 | log.Printf("Resp received: %v", response) 32 | } 33 | 34 | // ViewGroup service client 35 | func ViewGroup(ctx context.Context, group users.GroupServiceClient) { 36 | response, err := group.View(setMetadata(ctx), &users.Id{Id: "749844e9-d4fa-4121-8a1f-b34c0cba5c02"}) 37 | if err != nil { 38 | log.Fatalf("Error when calling grpc service: %s", err) 39 | } 40 | log.Printf("Resp received: %v", response) 41 | } 42 | 43 | // DeleteGroup service client 44 | func DeleteGroup(ctx context.Context, group users.GroupServiceClient) { 45 | response, err := group.Delete(setMetadata(ctx), &users.Id{Id: "749844e9-d4fa-4121-8a1f-b34c0cba5c02"}) 46 | if err != nil { 47 | log.Fatalf("Error when calling grpc service: %s", err) 48 | } 49 | log.Printf("Resp received: %v", response) 50 | } 51 | 52 | // ListGroup service client 53 | func ListGroup(ctx context.Context, group users.GroupServiceClient) { 54 | stream, err := group.List(setMetadata(ctx), &users.ListGroupRequest{}) 55 | if err != nil { 56 | log.Fatalf("Error when calling grpc service: %s", err) 57 | } 58 | 59 | for { 60 | resp, err := stream.Recv() 61 | if err == io.EOF { 62 | log.Fatal("end stream") 63 | break 64 | } 65 | if err != nil { 66 | log.Fatalf("cannot receive %v", err) 67 | } 68 | log.Printf("Resp received: %s : %v", resp.GetGroup(), resp.GetPagination()) 69 | } 70 | } 71 | 72 | // GrantAccess service client 73 | func GrantAccess(ctx context.Context, group users.GroupServiceClient) { 74 | response, err := group.GrantAccess(setMetadata(ctx), &users.GrantAccessRequest{ 75 | AccessId: "39306e07-6c0c-4a18-8d02-ae9f63bcad98", 76 | GroupId: "ce7ae637-56f8-46c2-ae3c-1be2ed63831d", 77 | }) 78 | if err != nil { 79 | log.Fatalf("Error when calling grpc service: %s", err) 80 | } 81 | log.Printf("Resp received: %v", response) 82 | } 83 | 84 | // RevokeAccess service client 85 | func RevokeAccess(ctx context.Context, group users.GroupServiceClient) { 86 | response, err := group.RevokeAccess(setMetadata(ctx), &users.GrantAccessRequest{ 87 | AccessId: "39306e07-6c0c-4a18-8d02-ae9f63bcad98", 88 | GroupId: "ce7ae637-56f8-46c2-ae3c-1be2ed63831d", 89 | }) 90 | if err != nil { 91 | log.Fatalf("Error when calling grpc service: %s", err) 92 | } 93 | log.Printf("Resp received: %v", response) 94 | } 95 | -------------------------------------------------------------------------------- /client/service/region.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "log" 7 | 8 | "github.com/jacky-htg/erp-proto/go/pb/users" 9 | ) 10 | 11 | // CreateRegion service client 12 | func CreateRegion(ctx context.Context, region users.RegionServiceClient) { 13 | response, err := region.Create(setMetadata(ctx), &users.Region{ 14 | Code: "WEST", 15 | Name: "Sumatera", 16 | }) 17 | if err != nil { 18 | log.Fatalf("Error when calling grpc service: %s", err) 19 | } 20 | log.Printf("Resp received: %v", response) 21 | } 22 | 23 | // UpdateRegion service client 24 | func UpdateRegion(ctx context.Context, region users.RegionServiceClient) { 25 | response, err := region.Update(setMetadata(ctx), &users.Region{ 26 | Id: "872df9ca-8758-4a50-a49b-b9ebc41314c5", 27 | Name: "Sumatera Raya", 28 | }) 29 | if err != nil { 30 | log.Fatalf("Error when calling grpc service: %s", err) 31 | } 32 | log.Printf("Resp received: %v", response) 33 | } 34 | 35 | // ViewRegion service client 36 | func ViewRegion(ctx context.Context, region users.RegionServiceClient) { 37 | response, err := region.View(setMetadata(ctx), &users.Id{Id: "872df9ca-8758-4a50-a49b-b9ebc41314c5"}) 38 | if err != nil { 39 | log.Fatalf("Error when calling grpc service: %s", err) 40 | } 41 | log.Printf("Resp received: %v", response) 42 | } 43 | 44 | // DeleteRegion service client 45 | func DeleteRegion(ctx context.Context, region users.RegionServiceClient) { 46 | response, err := region.Delete(setMetadata(ctx), &users.Id{Id: "872df9ca-8758-4a50-a49b-b9ebc41314c5"}) 47 | if err != nil { 48 | log.Fatalf("Error when calling grpc service: %s", err) 49 | } 50 | log.Printf("Resp received: %v", response) 51 | } 52 | 53 | // ListRegion service client 54 | func ListRegion(ctx context.Context, region users.RegionServiceClient) { 55 | stream, err := region.List(setMetadata(ctx), &users.ListRegionRequest{}) 56 | if err != nil { 57 | log.Fatalf("Error when calling grpc service: %s", err) 58 | } 59 | 60 | for { 61 | resp, err := stream.Recv() 62 | if err == io.EOF { 63 | log.Fatal("end stream") 64 | break 65 | } 66 | if err != nil { 67 | log.Fatalf("cannot receive %v", err) 68 | } 69 | log.Printf("Resp received: %s : %v", resp.GetRegion(), resp.GetPagination()) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /client/service/user.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "io" 5 | "log" 6 | 7 | "github.com/jacky-htg/erp-proto/go/pb/users" 8 | "golang.org/x/net/context" 9 | ) 10 | 11 | // CreateUser service Client 12 | func CreateUser(ctx context.Context, user users.UserServiceClient) { 13 | response, err := user.Create(setMetadata(ctx), &users.User{ 14 | Email: "admin@gmail.com", 15 | Group: &users.Group{Id: "b7365e19-4f39-43bd-9b4a-f53175ab37bd"}, 16 | Name: "Administrator", 17 | Username: "jackyhtg", 18 | }) 19 | 20 | if err != nil { 21 | log.Fatalf("Error when calling grpc service: %s", err) 22 | } 23 | log.Printf("Resp received: %v", response) 24 | } 25 | 26 | // UpdateUser service client 27 | func UpdateUser(ctx context.Context, user users.UserServiceClient) { 28 | response, err := user.Create(setMetadata(ctx), &users.User{ 29 | Email: "admin@gmail.com", 30 | Group: &users.Group{Id: "b7365e19-4f39-43bd-9b4a-f53175ab37bd"}, 31 | Name: "Administrator", 32 | Username: "jackyhtg", 33 | }) 34 | 35 | if err != nil { 36 | log.Fatalf("Error when calling grpc service: %s", err) 37 | } 38 | log.Printf("Resp received: %v", response) 39 | } 40 | 41 | // ViewUser service cient 42 | func ViewUser(ctx context.Context, user users.UserServiceClient) { 43 | response, err := user.View(setMetadata(ctx), &users.Id{Id: "f65b47d0-4fb6-4418-b97b-d736106c857e"}) 44 | 45 | if err != nil { 46 | log.Fatalf("Error when calling grpc service: %s", err) 47 | } 48 | log.Printf("Resp received: %v", response) 49 | } 50 | 51 | // DeleteUser service client 52 | func DeleteUser(ctx context.Context, user users.UserServiceClient) { 53 | response, err := user.Delete(setMetadata(ctx), &users.Id{Id: "f65b47d0-4fb6-4418-b97b-d736106c857e"}) 54 | 55 | if err != nil { 56 | log.Fatalf("Error when calling grpc service: %s", err) 57 | } 58 | log.Printf("Resp received: %v", response) 59 | } 60 | 61 | // ListUser service client 62 | func ListUser(ctx context.Context, user users.UserServiceClient) { 63 | stream, err := user.List(setMetadata(ctx), &users.ListUserRequest{}) 64 | if err != nil { 65 | log.Fatalf("Error when calling grpc service: %s", err) 66 | } 67 | 68 | for { 69 | resp, err := stream.Recv() 70 | if err == io.EOF { 71 | log.Fatal("end stream") 72 | break 73 | } 74 | if err != nil { 75 | log.Fatalf("cannot receive %v", err) 76 | } 77 | log.Printf("Resp received: %s : %v", resp.GetUser(), resp.GetPagination()) 78 | } 79 | } 80 | 81 | // GetUserByToken service client 82 | func GetUserByToken(ctx context.Context, user users.UserServiceClient) { 83 | response, err := user.GetByToken(setMetadataToken(ctx), &users.MyEmpty{}) 84 | 85 | if err != nil { 86 | log.Fatalf("Error when calling grpc service: %s", err) 87 | } 88 | log.Printf("Resp received: %v", response) 89 | } 90 | -------------------------------------------------------------------------------- /client/service/util.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/jacky-htg/erp-pkg/app" 7 | "google.golang.org/grpc/metadata" 8 | ) 9 | 10 | func setMetadata(ctx context.Context) context.Context { 11 | md := metadata.New(map[string]string{ 12 | "user_id": ctx.Value(app.Ctx("userID")).(string), 13 | "company_id": ctx.Value(app.Ctx("companyID")).(string), 14 | }) 15 | 16 | return metadata.NewOutgoingContext(ctx, md) 17 | } 18 | 19 | func setMetadataToken(ctx context.Context) context.Context { 20 | md := metadata.New(map[string]string{ 21 | "token": ctx.Value(app.Ctx("token")).(string), 22 | }) 23 | 24 | return metadata.NewOutgoingContext(ctx, md) 25 | } 26 | -------------------------------------------------------------------------------- /cmd/cli.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "os" 8 | 9 | _ "github.com/lib/pq" 10 | 11 | "github.com/jacky-htg/erp-pkg/db/postgres" 12 | "github.com/jacky-htg/user-service/internal/config" 13 | "github.com/jacky-htg/user-service/internal/schema" 14 | ) 15 | 16 | func main() { 17 | if _, ok := os.LookupEnv("APP_ENV"); !ok { 18 | _, err := os.Stat(".env.prod") 19 | if os.IsNotExist(err) { 20 | config.Setup(".env") 21 | } else { 22 | config.Setup(".env.prod") 23 | } 24 | } 25 | 26 | // ========================================================================= 27 | // Logging 28 | log := log.New(os.Stdout, "ERROR : ", log.LstdFlags|log.Lmicroseconds|log.Lshortfile) 29 | if err := run(log); err != nil { 30 | log.Printf("error: shutting down: %s", err) 31 | os.Exit(1) 32 | } 33 | } 34 | 35 | func run(log *log.Logger) error { 36 | // ========================================================================= 37 | // App Starting 38 | 39 | log.Printf("main : Started") 40 | defer log.Println("main : Completed") 41 | 42 | // ========================================================================= 43 | 44 | // Start Database 45 | 46 | db, err := postgres.Open() 47 | if err != nil { 48 | return fmt.Errorf("connecting to db: %v", err) 49 | } 50 | defer db.Close() 51 | 52 | // Handle cli command 53 | flag.Parse() 54 | 55 | switch flag.Arg(0) { 56 | case "migrate": 57 | if err := schema.Migrate(db); err != nil { 58 | return fmt.Errorf("applying migrations: %v", err) 59 | } 60 | log.Println("Migrations complete") 61 | return nil 62 | 63 | case "seed": 64 | if err := schema.Seed(db); err != nil { 65 | return fmt.Errorf("seeding database: %v", err) 66 | } 67 | log.Println("Seed data complete") 68 | return nil 69 | } 70 | 71 | return nil 72 | } 73 | -------------------------------------------------------------------------------- /internal/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "strings" 7 | ) 8 | 9 | //Setup environment from file .env 10 | func Setup(file string) error { 11 | data, err := ioutil.ReadFile(file) 12 | if err != nil { 13 | return err 14 | } 15 | 16 | datas := strings.Split(string(data), "\n") 17 | for _, env := range datas { 18 | e := strings.Split(env, "=") 19 | if len(e) >= 2 { 20 | os.Setenv(strings.TrimSpace(e[0]), strings.TrimSpace(strings.Join(e[1:], "="))) 21 | } 22 | } 23 | 24 | return nil 25 | } 26 | -------------------------------------------------------------------------------- /internal/model/access.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "encoding/json" 7 | 8 | "github.com/jacky-htg/erp-proto/go/pb/users" 9 | "google.golang.org/grpc/codes" 10 | "google.golang.org/grpc/status" 11 | ) 12 | 13 | // Access struct 14 | type Access struct { 15 | Pb users.Access 16 | } 17 | 18 | // Get Access 19 | func (u *Access) Get(ctx context.Context, db *sql.DB) error { 20 | err := db.QueryRowContext(ctx, `SELECT id, name FROM access WHERE id = $1`, u.Pb.GetId()).Scan(&u.Pb.Id, &u.Pb.Name) 21 | 22 | if err == sql.ErrNoRows { 23 | return status.Errorf(codes.NotFound, "Query Raw: %v", err) 24 | } 25 | 26 | if err != nil { 27 | return status.Errorf(codes.Internal, "Query Raw: %v", err) 28 | } 29 | 30 | return nil 31 | } 32 | 33 | // GetRoot Access 34 | func (u *Access) GetRoot(ctx context.Context, tx *sql.Tx, withChildren bool) error { 35 | err := tx.QueryRowContext(ctx, `SELECT id, name FROM access WHERE name = 'root'`).Scan(&u.Pb.Id, &u.Pb.Name) 36 | 37 | if err == sql.ErrNoRows { 38 | return status.Errorf(codes.NotFound, "Query Raw: %v", err) 39 | } 40 | 41 | if err != nil { 42 | return status.Errorf(codes.Internal, "Query Raw: %v", err) 43 | } 44 | 45 | /*if withChildren { 46 | u.Pb.Childrens, err = u.GetByParent(ctx, tx, u.Pb.GetId()) 47 | if err != nil { 48 | return err 49 | } 50 | }*/ 51 | 52 | return nil 53 | } 54 | 55 | // GetByParent Access 56 | func (u *Access) GetByParent(ctx context.Context, tx *sql.Tx, parent string) ([]*users.Access, error) { 57 | var list []*users.Access 58 | 59 | rows, err := tx.QueryContext(ctx, `SELECT access.id, access.name FROM access WHERE parent_id = $1`, parent) 60 | if err != nil { 61 | return list, status.Error(codes.Internal, err.Error()) 62 | } 63 | defer rows.Close() 64 | 65 | for rows.Next() { 66 | var pbAccess users.Access 67 | err = rows.Scan(&pbAccess.Id, &pbAccess.Name) 68 | if err != nil { 69 | return list, status.Error(codes.Internal, err.Error()) 70 | } 71 | 72 | /*pbAccess.Childrens, err = u.GetByParent(ctx, tx, pbAccess.GetId()) 73 | if err != nil { 74 | return list, err 75 | }*/ 76 | //pbAccess.ParentId = parent 77 | 78 | list = append(list, &pbAccess) 79 | } 80 | 81 | return list, nil 82 | } 83 | 84 | func (u *Access) List(ctx context.Context, db *sql.DB) (*users.ListAccessResponse, error) { 85 | var output users.ListAccessResponse 86 | rows, err := db.QueryContext(ctx, `SELECT id, name FROM access WHERE level = 1`) 87 | 88 | if err == sql.ErrNoRows { 89 | return &output, status.Errorf(codes.NotFound, "Query Raw: %v", err) 90 | } 91 | 92 | if err != nil { 93 | return &output, status.Errorf(codes.Internal, "Query Raw: %v", err) 94 | } 95 | 96 | defer rows.Close() 97 | 98 | for rows.Next() { 99 | var pbAccess users.AccessLevel1 100 | err = rows.Scan(&pbAccess.Id, &pbAccess.Name) 101 | if err != nil { 102 | return &output, status.Errorf(codes.Internal, "Scan: %v", err) 103 | } 104 | 105 | pbAccess.Children, err = u.ListChildren(ctx, db, pbAccess.Id) 106 | if err != nil { 107 | return nil, err 108 | } 109 | output.Access = append(output.Access, &pbAccess) 110 | } 111 | 112 | if rows.Err() != nil { 113 | return nil, status.Error(codes.Internal, rows.Err().Error()) 114 | } 115 | 116 | return &output, nil 117 | } 118 | 119 | func (u *Access) ListChildren(ctx context.Context, db *sql.DB, parentId string) ([]*users.AccessLevel2, error) { 120 | var err error 121 | var output []*users.AccessLevel2 122 | 123 | query := ` 124 | SELECT access2.id, access2.name, 125 | JSON_AGG(DISTINCT JSONB_BUILD_OBJECT( 126 | 'id', access3.id, 127 | 'name', access3.name 128 | )) as json 129 | FROM access access2 130 | LEFT JOIN access access3 ON access3.level = 3 and access3.parent_id = access2.id 131 | WHERE access2.parent_id = $1 AND access2.level = 2 132 | GROUP BY access2.id 133 | ` 134 | rows, err := db.QueryContext(ctx, query, parentId) 135 | if err != nil { 136 | return output, status.Error(codes.Internal, err.Error()) 137 | } 138 | defer rows.Close() 139 | 140 | for rows.Next() { 141 | var pbAccess users.AccessLevel2 142 | var tempJson string 143 | err = rows.Scan(&pbAccess.Id, &pbAccess.Name, &tempJson) 144 | if err != nil { 145 | return output, status.Errorf(codes.Internal, "scan data: %v", err) 146 | } 147 | err = json.Unmarshal([]byte(tempJson), &pbAccess.Children) 148 | if err != nil { 149 | return output, status.Errorf(codes.Internal, "unmarshal access: %v", err) 150 | } 151 | 152 | output = append(output, &pbAccess) 153 | } 154 | 155 | if rows.Err() != nil { 156 | return output, status.Error(codes.Internal, rows.Err().Error()) 157 | } 158 | 159 | return output, nil 160 | } 161 | -------------------------------------------------------------------------------- /internal/model/access_group.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "time" 7 | 8 | "github.com/google/uuid" 9 | "github.com/jacky-htg/erp-pkg/app" 10 | "google.golang.org/grpc/codes" 11 | "google.golang.org/grpc/status" 12 | ) 13 | 14 | // AccessGroup struct 15 | type AccessGroup struct { 16 | AccessID string 17 | GroupID string 18 | } 19 | 20 | // Get Access 21 | func (u *AccessGroup) Get(ctx context.Context, db *sql.DB) error { 22 | var isExist bool 23 | err := db.QueryRowContext(ctx, `SELECT true FROM access_groups WHERE group_id = $1 AND access_id = $2`, u.GroupID, u.AccessID).Scan(&isExist) 24 | 25 | if err == sql.ErrNoRows { 26 | return status.Errorf(codes.NotFound, "Query Raw: %v", err) 27 | } 28 | 29 | if err != nil { 30 | return status.Errorf(codes.Internal, "Query Raw: %v", err) 31 | } 32 | 33 | return nil 34 | } 35 | 36 | // Grant Access 37 | func (u *AccessGroup) Grant(ctx context.Context, db *sql.DB) error { 38 | now := time.Now().UTC() 39 | query := ` 40 | INSERT INTO access_groups (id, group_id, access_id, created_at, created_by, updated_at, updated_by) 41 | VALUES ($1, $2, $3, $4, $5, $6, $7) 42 | ` 43 | stmt, err := db.PrepareContext(ctx, query) 44 | if err != nil { 45 | return status.Errorf(codes.Internal, "Prepare grant access: %v", err) 46 | } 47 | defer stmt.Close() 48 | 49 | _, err = stmt.ExecContext(ctx, 50 | uuid.New().String(), 51 | u.GroupID, 52 | u.AccessID, 53 | now, 54 | ctx.Value(app.Ctx("userID")).(string), 55 | now, 56 | ctx.Value(app.Ctx("userID")).(string), 57 | ) 58 | if err != nil { 59 | return status.Errorf(codes.Internal, "Exec grant access: %v", err) 60 | } 61 | 62 | return nil 63 | } 64 | 65 | // Revoke Access 66 | func (u *AccessGroup) Revoke(ctx context.Context, db *sql.DB) error { 67 | stmt, err := db.PrepareContext(ctx, `DELETE FROM access_groups WHERE group_id = $1 AND access_id = $2`) 68 | if err != nil { 69 | return status.Errorf(codes.Internal, "Prepare revoke access: %v", err) 70 | } 71 | defer stmt.Close() 72 | 73 | _, err = stmt.ExecContext(ctx, u.GroupID, u.AccessID) 74 | if err != nil { 75 | return status.Errorf(codes.Internal, "Exec rebvoke access: %v", err) 76 | } 77 | 78 | return nil 79 | } 80 | -------------------------------------------------------------------------------- /internal/model/branch.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "fmt" 7 | "strings" 8 | "time" 9 | 10 | "github.com/google/uuid" 11 | "github.com/jacky-htg/erp-pkg/app" 12 | "github.com/jacky-htg/erp-proto/go/pb/users" 13 | "google.golang.org/grpc/codes" 14 | "google.golang.org/grpc/status" 15 | ) 16 | 17 | // Branch model 18 | type Branch struct { 19 | Pb users.Branch 20 | UpdateRegion bool 21 | } 22 | 23 | // Get func 24 | func (u *Branch) Get(ctx context.Context, db *sql.DB) error { 25 | query := `SELECT branches.id, branches.company_id, regions.id, branches.name, branches.code, branches.address, 26 | branches.city, branches.province, branches.npwp, branches.phone, branches.pic, branches.pic_phone 27 | FROM branches 28 | JOIN branches_regions ON branches.id = branches_regions.branch_id 29 | JOIN regions ON branches_regions.region_id = regions.id 30 | WHERE branches.id = $1` 31 | stmt, err := db.PrepareContext(ctx, query) 32 | if err != nil { 33 | return status.Errorf(codes.Internal, "Prepare statement Get branch: %v", err) 34 | } 35 | defer stmt.Close() 36 | 37 | var npwp sql.NullString 38 | err = stmt.QueryRowContext(ctx, u.Pb.GetId()).Scan( 39 | &u.Pb.Id, &u.Pb.CompanyId, &u.Pb.RegionId, &u.Pb.Name, &u.Pb.Code, &u.Pb.Address, &u.Pb.City, &u.Pb.Province, 40 | &npwp, &u.Pb.Phone, &u.Pb.Pic, &u.Pb.PicPhone, 41 | ) 42 | 43 | if err == sql.ErrNoRows { 44 | return status.Errorf(codes.NotFound, "Query Raw get branch: %v", err) 45 | } 46 | 47 | if err != nil { 48 | return status.Errorf(codes.Internal, "Query Raw get branch: %v", err) 49 | } 50 | 51 | u.Pb.Npwp = npwp.String 52 | 53 | return nil 54 | } 55 | 56 | // GetByCode func 57 | func (u *Branch) GetByCode(ctx context.Context, db *sql.DB) error { 58 | query := `SELECT branches.id, branches.company_id, regions.id, branches.name, branches.code, branches.address, 59 | branches.city, branches.province, branches.npwp, branches.phone, branches.pic, branches.pic_phone 60 | FROM branches 61 | JOIN branches_regions ON branches.id = branches_regions.branch_id 62 | JOIN regions ON branches_regions.region_id = regions.id 63 | WHERE branches.company_id = $1 AND branches.code = $2` 64 | stmt, err := db.PrepareContext(ctx, query) 65 | if err != nil { 66 | return status.Errorf(codes.Internal, "Prepare statement get branch by code: %v", err) 67 | } 68 | defer stmt.Close() 69 | 70 | var npwp sql.NullString 71 | err = stmt.QueryRowContext(ctx, ctx.Value(app.Ctx("companyID")).(string), u.Pb.GetCode()).Scan( 72 | &u.Pb.Id, &u.Pb.CompanyId, &u.Pb.RegionId, &u.Pb.Name, &u.Pb.Code, &u.Pb.Address, &u.Pb.City, &u.Pb.Province, 73 | &npwp, &u.Pb.Phone, &u.Pb.Pic, &u.Pb.PicPhone, 74 | ) 75 | 76 | if err == sql.ErrNoRows { 77 | return status.Errorf(codes.NotFound, "Query Raw get branch by code: %v", err) 78 | } 79 | 80 | if err != nil { 81 | return status.Errorf(codes.Internal, "Query Raw get branch by code: %v", err) 82 | } 83 | 84 | u.Pb.Npwp = npwp.String 85 | 86 | return nil 87 | } 88 | 89 | // Create Branch 90 | func (u *Branch) Create(ctx context.Context, db *sql.DB, tx *sql.Tx) error { 91 | u.Pb.Id = uuid.New().String() 92 | now := time.Now().UTC() 93 | u.Pb.CreatedBy = ctx.Value(app.Ctx("userID")).(string) 94 | u.Pb.UpdatedBy = ctx.Value(app.Ctx("userID")).(string) 95 | 96 | query := ` 97 | INSERT INTO branches (id, company_id, name, code, address, city, province, npwp, phone, pic, pic_phone, created_at, created_by, updated_at, updated_by) 98 | VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15) 99 | ` 100 | stmt, err := tx.PrepareContext(ctx, query) 101 | if err != nil { 102 | return status.Errorf(codes.Internal, "Prepare insert branch: %v", err) 103 | } 104 | defer stmt.Close() 105 | 106 | _, err = stmt.ExecContext(ctx, 107 | u.Pb.GetId(), 108 | u.Pb.GetCompanyId(), 109 | u.Pb.GetName(), 110 | u.Pb.GetCode(), 111 | u.Pb.GetAddress(), 112 | u.Pb.GetCity(), 113 | u.Pb.GetProvince(), 114 | u.Pb.GetNpwp(), 115 | u.Pb.GetPhone(), 116 | u.Pb.GetPic(), 117 | u.Pb.GetPicPhone(), 118 | now, 119 | u.Pb.GetCreatedBy(), 120 | now, 121 | u.Pb.GetUpdatedBy(), 122 | ) 123 | if err != nil { 124 | return status.Errorf(codes.Internal, "Exec insert branch: %v", err) 125 | } 126 | 127 | u.Pb.CreatedAt = now.String() 128 | u.Pb.UpdatedAt = u.Pb.CreatedAt 129 | 130 | branchesRegion := BranchesRegion{RegionID: u.Pb.GetRegionId(), BranchID: u.Pb.GetId()} 131 | err = branchesRegion.Create(ctx, tx) 132 | if err != nil { 133 | return err 134 | } 135 | 136 | return nil 137 | } 138 | 139 | // Update Branch 140 | func (u *Branch) Update(ctx context.Context, db *sql.DB, tx *sql.Tx) error { 141 | query := ` 142 | UPDATE branches SET 143 | name = $1, 144 | address = $2, 145 | city = $3, 146 | province = $4, 147 | npwp = $5, 148 | phone = $6, 149 | pic = $7, 150 | pic_phone = $8, 151 | updated_by = $9, 152 | updated_at = $10 153 | WHERE id = $11 154 | ` 155 | stmt, err := tx.PrepareContext(ctx, query) 156 | if err != nil { 157 | return status.Errorf(codes.Internal, "Prepare update branch: %v", err) 158 | } 159 | defer stmt.Close() 160 | 161 | _, err = stmt.ExecContext(ctx, 162 | u.Pb.GetName(), 163 | u.Pb.GetAddress(), 164 | u.Pb.GetCity(), 165 | u.Pb.GetProvince(), 166 | u.Pb.GetNpwp(), 167 | u.Pb.GetPhone(), 168 | u.Pb.GetPic(), 169 | u.Pb.GetPicPhone(), 170 | ctx.Value(app.Ctx("userID")).(string), 171 | time.Now().UTC(), 172 | u.Pb.GetId(), 173 | ) 174 | if err != nil { 175 | return status.Errorf(codes.Internal, "Exec update branch: %v", err) 176 | } 177 | 178 | if u.UpdateRegion && len(u.Pb.GetRegionId()) > 0 { 179 | // delete current branchesRegion 180 | { 181 | branchesRegion := BranchesRegion{BranchID: u.Pb.GetId()} 182 | err = branchesRegion.DeleteAll(ctx, tx) 183 | if err != nil { 184 | return err 185 | } 186 | } 187 | 188 | // create new branchesRegion 189 | { 190 | branchesRegion := BranchesRegion{RegionID: u.Pb.GetRegionId(), BranchID: u.Pb.GetId()} 191 | err = branchesRegion.Create(ctx, tx) 192 | if err != nil { 193 | return err 194 | } 195 | } 196 | } 197 | 198 | return nil 199 | } 200 | 201 | // Delete branch 202 | func (u *Branch) Delete(ctx context.Context, db *sql.DB) error { 203 | stmt, err := db.PrepareContext(ctx, `DELETE FROM branches WHERE id = $1`) 204 | if err != nil { 205 | return status.Errorf(codes.Internal, "Prepare delete branch: %v", err) 206 | } 207 | defer stmt.Close() 208 | 209 | _, err = stmt.ExecContext(ctx, u.Pb.GetId()) 210 | if err != nil { 211 | return status.Errorf(codes.Internal, "Exec delete branch: %v", err) 212 | } 213 | 214 | return nil 215 | } 216 | 217 | // ListQuery builder 218 | func (u *Branch) ListQuery(ctx context.Context, db *sql.DB, in *users.ListBranchRequest, branchID string) (string, []interface{}, *users.BranchPaginationResponse, error) { 219 | var paginationResponse users.BranchPaginationResponse 220 | query := `SELECT branches.id, branches.company_id, regions.id, regions.name region_name, branches.name, branches.code, branches.address, 221 | branches.city, branches.province, branches.npwp, branches.phone, branches.pic, branches.pic_phone 222 | FROM branches 223 | JOIN branches_regions ON branches.id = branches_regions.branch_id 224 | JOIN regions ON branches_regions.region_id = regions.id` 225 | where := []string{"branches.company_id = $1"} 226 | paramQueries := []interface{}{ctx.Value(app.Ctx("companyID")).(string)} 227 | 228 | if len(branchID) > 0 { 229 | paramQueries = append(paramQueries, branchID) 230 | where = append(where, fmt.Sprintf(`branches.id = $%d`, len(paramQueries))) 231 | } 232 | 233 | if len(in.GetRegionId()) > 0 { 234 | paramQueries = append(paramQueries, in.GetRegionId()) 235 | where = append(where, fmt.Sprintf(`regions.id = $%d`, len(paramQueries))) 236 | } 237 | 238 | if len(in.GetPagination().GetSearch()) > 0 { 239 | paramQueries = append(paramQueries, "%"+in.GetPagination().GetSearch()+"%") 240 | where = append(where, fmt.Sprintf(`(branches.name ILIKE $%d OR branches.code ILIKE $%d 241 | OR branches.address ILIKE $%d OR branches.city ILIKE $%d OR branches.province ILIKE $%d 242 | OR branches.npwp ILIKE $%d OR branches.phone ILIKE $%d OR branches.pic ILIKE $%d 243 | OR branches.pic_phone ILIKE $%d OR regions.name ILIKE $%d)`, 244 | len(paramQueries), len(paramQueries), len(paramQueries), len(paramQueries), len(paramQueries), 245 | len(paramQueries), len(paramQueries), len(paramQueries), len(paramQueries), len(paramQueries))) 246 | } 247 | 248 | { 249 | qCount := `SELECT COUNT(*) FROM branches 250 | JOIN branches_regions ON branches.id = branches_regions.branch_id 251 | JOIN regions ON branches_regions.region_id = regions.id` 252 | if len(where) > 0 { 253 | qCount += " WHERE " + strings.Join(where, " AND ") 254 | } 255 | var count int 256 | err := db.QueryRowContext(ctx, qCount, paramQueries...).Scan(&count) 257 | if err != nil && err != sql.ErrNoRows { 258 | return query, paramQueries, &paginationResponse, status.Error(codes.Internal, err.Error()) 259 | } 260 | 261 | paginationResponse.Count = uint32(count) 262 | } 263 | 264 | if len(where) > 0 { 265 | query += ` WHERE ` + strings.Join(where, " AND ") 266 | } 267 | 268 | if len(in.GetPagination().GetOrderBy()) == 0 || !(in.GetPagination().GetOrderBy() == "branches.name" || 269 | in.GetPagination().GetOrderBy() == "branches.code") { 270 | if in.GetPagination() == nil { 271 | in.Pagination = &users.Pagination{OrderBy: "branches.created_at"} 272 | } else { 273 | in.GetPagination().OrderBy = "branches.created_at" 274 | } 275 | } 276 | 277 | query += ` ORDER BY ` + in.GetPagination().GetOrderBy() + ` ` + in.GetPagination().GetSort().String() 278 | 279 | if in.GetPagination().GetLimit() > 0 { 280 | query += fmt.Sprintf(` LIMIT $%d OFFSET $%d`, (len(paramQueries) + 1), (len(paramQueries) + 2)) 281 | paramQueries = append(paramQueries, in.GetPagination().GetLimit(), in.GetPagination().GetOffset()) 282 | } 283 | 284 | return query, paramQueries, &paginationResponse, nil 285 | } 286 | -------------------------------------------------------------------------------- /internal/model/branches_region.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | 7 | "github.com/google/uuid" 8 | "github.com/jacky-htg/erp-pkg/app" 9 | "google.golang.org/grpc/codes" 10 | "google.golang.org/grpc/status" 11 | ) 12 | 13 | // BranchesRegion strcut 14 | type BranchesRegion struct { 15 | ID string 16 | BranchID string 17 | RegionID string 18 | } 19 | 20 | // Create branchesRegion 21 | func (u *BranchesRegion) Create(ctx context.Context, tx *sql.Tx) error { 22 | u.ID = uuid.New().String() 23 | 24 | query := `INSERT INTO branches_regions (id, region_id, branch_id, created_by, updated_by) 25 | VALUES ($1, $2, $3, $4, $4)` 26 | 27 | stmt, err := tx.PrepareContext(ctx, query) 28 | if err != nil { 29 | return status.Errorf(codes.Internal, "Prepare insert branches_regions: %v", err) 30 | } 31 | defer stmt.Close() 32 | 33 | _, err = stmt.ExecContext(ctx, 34 | u.ID, 35 | u.RegionID, 36 | u.BranchID, 37 | ctx.Value(app.Ctx("userID")).(string), 38 | ) 39 | if err != nil { 40 | return status.Errorf(codes.Internal, "exec insert branches_regions: %v", err) 41 | } 42 | 43 | return nil 44 | } 45 | 46 | // DeleteAll branchesRegion 47 | func (u *BranchesRegion) DeleteAll(ctx context.Context, tx *sql.Tx) error { 48 | stmt, err := tx.PrepareContext(ctx, `DELETE FROM branches_regions WHERE branch_id = $1`) 49 | if err != nil { 50 | return status.Errorf(codes.Internal, "Prepare delete all branches_regions by branch: %v", err) 51 | } 52 | defer stmt.Close() 53 | 54 | _, err = stmt.ExecContext(ctx, u.BranchID) 55 | if err != nil { 56 | return status.Errorf(codes.Internal, "exec delete all branches_regions by branch: %v", err) 57 | } 58 | 59 | return nil 60 | } 61 | -------------------------------------------------------------------------------- /internal/model/company.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "time" 7 | 8 | "github.com/google/uuid" 9 | "github.com/jacky-htg/erp-pkg/app" 10 | "github.com/jacky-htg/erp-proto/go/pb/users" 11 | "golang.org/x/crypto/bcrypt" 12 | "google.golang.org/grpc/codes" 13 | "google.golang.org/grpc/status" 14 | ) 15 | 16 | // Company struct 17 | type Company struct { 18 | Pb users.Company 19 | UpdateFeatures bool 20 | } 21 | 22 | // CompanyRegister struct 23 | type CompanyRegister struct { 24 | Pb users.CompanyRegistration 25 | Password string 26 | } 27 | 28 | // Registration Company 29 | func (u *CompanyRegister) Registration(ctx context.Context, db *sql.DB, tx *sql.Tx) error { 30 | u.Pb.GetCompany().Id = uuid.New().String() 31 | u.Pb.GetUser().Id = uuid.New().String() 32 | groupID := uuid.New().String() 33 | 34 | // Create Company 35 | { 36 | query := ` 37 | INSERT INTO companies (id, name, code, address, city, province, phone, pic, pic_phone, package_of_feature_id, updated_by, npwp, logo) 38 | VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13) 39 | ` 40 | 41 | stmt, err := tx.PrepareContext(ctx, query) 42 | if err != nil { 43 | return err 44 | } 45 | defer stmt.Close() 46 | 47 | packageFeature := FeaturePackage{} 48 | packageFeature.Pb.Name = u.Pb.GetCompany().GetPackageOfFeature() 49 | err = packageFeature.GetByName(ctx, db) 50 | if err != nil { 51 | return err 52 | } 53 | 54 | _, err = stmt.ExecContext(ctx, 55 | u.Pb.GetCompany().GetId(), 56 | u.Pb.GetCompany().GetName(), 57 | u.Pb.GetCompany().GetCode(), 58 | u.Pb.GetCompany().GetAddress(), 59 | u.Pb.GetCompany().GetCity(), 60 | u.Pb.GetCompany().GetProvince(), 61 | u.Pb.GetCompany().GetPhone(), 62 | u.Pb.GetCompany().GetPic(), 63 | u.Pb.GetCompany().GetPicPhone(), 64 | packageFeature.Pb.GetId(), 65 | u.Pb.GetUser().GetId(), 66 | u.Pb.GetCompany().GetNpwp(), 67 | u.Pb.GetCompany().GetLogo(), 68 | ) 69 | if err != nil { 70 | return err 71 | } 72 | } 73 | 74 | // Create Group 75 | { 76 | query := ` 77 | INSERT INTO groups (id, company_id, is_mutable, name, created_by, updated_by) 78 | VALUES ($1, $2, false, 'Super Admin', $3, $3) 79 | ` 80 | 81 | stmt, err := tx.PrepareContext(ctx, query) 82 | if err != nil { 83 | return status.Errorf(codes.Internal, "prepare insert group: %v", err) 84 | } 85 | defer stmt.Close() 86 | 87 | _, err = stmt.ExecContext(ctx, groupID, u.Pb.GetCompany().GetId(), u.Pb.GetUser().GetId()) 88 | if err != nil { 89 | return status.Errorf(codes.Internal, "exec insert group: %v", err) 90 | } 91 | } 92 | 93 | // Create User 94 | { 95 | query := ` 96 | INSERT INTO users (id, company_id, group_id, username, name, email, password) 97 | VALUES ($1, $2, $3, $4, $5, $6, $7) 98 | ` 99 | pass, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost) 100 | if err != nil { 101 | return status.Errorf(codes.Internal, "hash password: %v", err) 102 | } 103 | 104 | stmt, err := tx.PrepareContext(ctx, query) 105 | if err != nil { 106 | return status.Errorf(codes.Internal, "Prepare insert user: %v", err) 107 | } 108 | defer stmt.Close() 109 | 110 | _, err = stmt.ExecContext(ctx, 111 | u.Pb.GetUser().GetId(), 112 | u.Pb.GetCompany().GetId(), 113 | groupID, 114 | u.Pb.GetUser().GetUsername(), 115 | u.Pb.GetUser().GetName(), 116 | u.Pb.GetUser().GetEmail(), 117 | string(pass), 118 | ) 119 | if err != nil { 120 | return status.Errorf(codes.Internal, "Exec insert: %v", err) 121 | } 122 | } 123 | 124 | // grant access 125 | { 126 | var accessModel Access 127 | err := accessModel.GetRoot(ctx, tx, false) 128 | if err != nil { 129 | return err 130 | } 131 | 132 | query := ` 133 | INSERT INTO access_groups (id, group_id, access_id, created_by, updated_by) 134 | VALUES ($1, $2, $3, $4, $4) 135 | ` 136 | 137 | stmt, err := tx.PrepareContext(ctx, query) 138 | if err != nil { 139 | return err 140 | } 141 | defer stmt.Close() 142 | 143 | _, err = stmt.ExecContext(ctx, uuid.New().String(), groupID, accessModel.Pb.GetId(), u.Pb.GetUser().GetId()) 144 | if err != nil { 145 | return err 146 | } 147 | } 148 | 149 | if len(u.Pb.GetCompany().GetFeatures()) > 0 { 150 | modelCompany := Company{} 151 | err := modelCompany.featureSetting(ctx, tx, u.Pb.GetCompany().GetFeatures()) 152 | if err != nil { 153 | return err 154 | } 155 | } 156 | 157 | return nil 158 | } 159 | 160 | // GetByCode company 161 | func (u *Company) GetByCode(ctx context.Context, db *sql.DB) error { 162 | query := `SELECT id, name, code, address, city, province, npwp, phone, pic, pic_phone, logo, package_of_feature_id 163 | FROM companies WHERE code = $1` 164 | stmt, err := db.PrepareContext(ctx, query) 165 | if err != nil { 166 | return status.Errorf(codes.Internal, "Prepare statement: %v", err) 167 | } 168 | defer stmt.Close() 169 | 170 | var npwp, logo sql.NullString 171 | var enumPackage string 172 | err = stmt.QueryRowContext(ctx, u.Pb.GetCode()).Scan( 173 | &u.Pb.Id, &u.Pb.Name, &u.Pb.Code, 174 | &u.Pb.Address, &u.Pb.City, &u.Pb.Province, 175 | &npwp, &u.Pb.Phone, &u.Pb.Pic, &u.Pb.PicPhone, &logo, &enumPackage) 176 | 177 | if err == sql.ErrNoRows { 178 | return status.Errorf(codes.NotFound, "Query Raw: %v", err) 179 | } 180 | 181 | if err != nil { 182 | return status.Errorf(codes.Internal, "Query Raw: %v", err) 183 | } 184 | 185 | u.Pb.Npwp = npwp.String 186 | u.Pb.Logo = logo.String 187 | if value, ok := users.EnumPackageOfFeature_value[enumPackage]; ok { 188 | u.Pb.PackageOfFeature = users.EnumPackageOfFeature(value) 189 | } 190 | 191 | return nil 192 | } 193 | 194 | // Get company 195 | func (u *Company) Get(ctx context.Context, db *sql.DB) error { 196 | query := `SELECT id, name, code, address, city, province, npwp, phone, pic, pic_phone, logo, package_of_feature_id 197 | FROM companies WHERE id = $1` 198 | stmt, err := db.PrepareContext(ctx, query) 199 | if err != nil { 200 | return status.Errorf(codes.Internal, "Prepare statement: %v", err) 201 | } 202 | defer stmt.Close() 203 | 204 | var npwp, logo sql.NullString 205 | var enumPackage string 206 | err = stmt.QueryRowContext(ctx, u.Pb.GetId()).Scan( 207 | &u.Pb.Id, &u.Pb.Name, &u.Pb.Code, 208 | &u.Pb.Address, &u.Pb.City, &u.Pb.Province, 209 | &npwp, &u.Pb.Phone, &u.Pb.Pic, &u.Pb.PicPhone, &logo, &enumPackage) 210 | 211 | if err == sql.ErrNoRows { 212 | return status.Errorf(codes.NotFound, "Query Raw: %v", err) 213 | } 214 | 215 | if err != nil { 216 | return status.Errorf(codes.Internal, "Query Raw: %v", err) 217 | } 218 | 219 | u.Pb.Npwp = npwp.String 220 | u.Pb.Logo = logo.String 221 | if value, ok := users.EnumPackageOfFeature_value[enumPackage]; ok { 222 | u.Pb.PackageOfFeature = users.EnumPackageOfFeature(value) 223 | } 224 | 225 | return nil 226 | } 227 | 228 | // Update Company 229 | func (u *Company) Update(ctx context.Context, db *sql.DB, tx *sql.Tx) error { 230 | query := ` 231 | UPDATE companies SET 232 | name = $1, 233 | address = $2, 234 | city = $3, 235 | province = $4, 236 | npwp = $5, 237 | phone = $6, 238 | pic = $7, 239 | pic_phone = $8, 240 | logo = $9, 241 | package_of_feature_id = $10, 242 | updated_by = $11, 243 | updated_at = $12 244 | WHERE id = $13 245 | ` 246 | stmt, err := tx.PrepareContext(ctx, query) 247 | if err != nil { 248 | return status.Errorf(codes.Internal, "Prepare update: %v", err) 249 | } 250 | defer stmt.Close() 251 | 252 | packageFeature := FeaturePackage{} 253 | packageFeature.Pb.Name = u.Pb.GetPackageOfFeature() 254 | err = packageFeature.GetByName(ctx, db) 255 | if err != nil { 256 | return err 257 | } 258 | 259 | _, err = stmt.ExecContext(ctx, 260 | u.Pb.GetName(), 261 | u.Pb.GetAddress(), 262 | u.Pb.GetCity(), 263 | u.Pb.GetProvince(), 264 | u.Pb.GetNpwp(), 265 | u.Pb.GetPhone(), 266 | u.Pb.GetPic(), 267 | u.Pb.GetPicPhone(), 268 | u.Pb.GetLogo(), 269 | packageFeature.Pb.GetId(), 270 | ctx.Value(app.Ctx("userID")).(string), 271 | time.Now().UTC(), 272 | u.Pb.GetId(), 273 | ) 274 | if err != nil { 275 | return status.Errorf(codes.Internal, "Exec update: %v", err) 276 | } 277 | 278 | if u.UpdateFeatures && len(u.Pb.GetFeatures()) > 0 { 279 | err = u.featureSetting(ctx, tx, u.Pb.GetFeatures()) 280 | if err != nil { 281 | return err 282 | } 283 | } 284 | 285 | return nil 286 | } 287 | 288 | func (u *Company) featureSetting(ctx context.Context, tx *sql.Tx, features []*users.Feature) error { 289 | for _, feature := range features { 290 | query := `INSERT INTO companies_features (id, company_id, feature_id, created_by, updated_by) 291 | VALUES ($1, $2, $3, $4, $4)` 292 | 293 | stmt, err := tx.PrepareContext(ctx, query) 294 | if err != nil { 295 | return status.Errorf(codes.Internal, "Prepare insert companies_features: %v", err) 296 | } 297 | defer stmt.Close() 298 | 299 | _, err = stmt.ExecContext(ctx, 300 | uuid.New().String(), 301 | u.Pb.GetId(), 302 | feature.GetId(), 303 | ctx.Value(app.Ctx("userID")).(string), 304 | ) 305 | if err != nil { 306 | return status.Errorf(codes.Internal, "exec insert companies_features: %v", err) 307 | } 308 | } 309 | 310 | return nil 311 | } 312 | -------------------------------------------------------------------------------- /internal/model/employee.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "fmt" 7 | "strings" 8 | "time" 9 | 10 | "github.com/google/uuid" 11 | "github.com/jacky-htg/erp-pkg/app" 12 | "github.com/jacky-htg/erp-proto/go/pb/users" 13 | "google.golang.org/grpc/codes" 14 | "google.golang.org/grpc/status" 15 | ) 16 | 17 | // Employee struct 18 | type Employee struct { 19 | Pb users.Employee 20 | } 21 | 22 | // GetByCode func 23 | func (u *Employee) GetByCode(ctx context.Context, db *sql.DB) error { 24 | query := ` 25 | SELECT employees.id, employees.name, employees.code, employees.address, 26 | employees.city, employees.province, employees.jabatan, 27 | users.id, users.company_id, users.region_id, users.branch_id, users.name, users.email 28 | FROM employees 29 | JOIN users ON employees.user_id = users.id 30 | WHERE employees.code = $1` 31 | stmt, err := db.PrepareContext(ctx, query) 32 | if err != nil { 33 | return status.Errorf(codes.Internal, "Prepare statement get employee by code: %v", err) 34 | } 35 | defer stmt.Close() 36 | 37 | var pbUser users.User 38 | var regionID, branchID sql.NullString 39 | err = stmt.QueryRowContext(ctx, u.Pb.GetCode()).Scan( 40 | &u.Pb.Id, &u.Pb.Name, &u.Pb.Code, &u.Pb.Address, &u.Pb.City, &u.Pb.Province, &u.Pb.Jabatan, 41 | &pbUser.Id, &pbUser.CompanyId, ®ionID, &branchID, &pbUser.Name, &pbUser.Email, 42 | ) 43 | 44 | if err == sql.ErrNoRows { 45 | return status.Errorf(codes.NotFound, "Query Raw get employee by code: %v", err) 46 | } 47 | 48 | if err != nil { 49 | return status.Errorf(codes.Internal, "Query Raw get employee by code: %v", err) 50 | } 51 | 52 | pbUser.RegionId = regionID.String 53 | pbUser.BranchId = branchID.String 54 | u.Pb.User = &pbUser 55 | 56 | return nil 57 | } 58 | 59 | // Get func 60 | func (u *Employee) Get(ctx context.Context, db *sql.DB) error { 61 | query := ` 62 | SELECT employees.id, employees.name, employees.code, employees.address, 63 | employees.city, employees.province, employees.jabatan, 64 | users.id, users.company_id, users.region_id, users.branch_id, users.name, users.email 65 | FROM employees 66 | JOIN users ON employees.user_id = users.id 67 | WHERE employees.id = $1` 68 | stmt, err := db.PrepareContext(ctx, query) 69 | if err != nil { 70 | return status.Errorf(codes.Internal, "Prepare statement get employee: %v", err) 71 | } 72 | defer stmt.Close() 73 | 74 | var pbUser users.User 75 | var regionID, branchID sql.NullString 76 | err = stmt.QueryRowContext(ctx, u.Pb.GetId()).Scan( 77 | &u.Pb.Id, &u.Pb.Name, &u.Pb.Code, &u.Pb.Address, &u.Pb.City, &u.Pb.Province, &u.Pb.Jabatan, 78 | &pbUser.Id, &pbUser.CompanyId, ®ionID, &branchID, &pbUser.Name, &pbUser.Email, 79 | ) 80 | 81 | if err == sql.ErrNoRows { 82 | return status.Errorf(codes.NotFound, "Query Raw get employee: %v", err) 83 | } 84 | 85 | if err != nil { 86 | return status.Errorf(codes.Internal, "Query Raw get employee: %v", err) 87 | } 88 | 89 | pbUser.RegionId = regionID.String 90 | pbUser.BranchId = branchID.String 91 | u.Pb.User = &pbUser 92 | 93 | return nil 94 | } 95 | 96 | // Create Employee 97 | func (u *Employee) Create(ctx context.Context, db *sql.DB) error { 98 | u.Pb.Id = uuid.New().String() 99 | now := time.Now().UTC() 100 | u.Pb.CreatedBy = ctx.Value(app.Ctx("userID")).(string) 101 | u.Pb.UpdatedBy = ctx.Value(app.Ctx("userID")).(string) 102 | 103 | query := ` 104 | INSERT INTO employees (id, user_id, name, code, address, city, province, jabatan, created_at, created_by, updated_at, updated_by) 105 | VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) 106 | ` 107 | stmt, err := db.PrepareContext(ctx, query) 108 | if err != nil { 109 | return status.Errorf(codes.Internal, "Prepare insert employee: %v", err) 110 | } 111 | defer stmt.Close() 112 | 113 | _, err = stmt.ExecContext(ctx, 114 | u.Pb.GetId(), 115 | u.Pb.GetUser().GetId(), 116 | u.Pb.GetName(), 117 | u.Pb.GetCode(), 118 | u.Pb.GetAddress(), 119 | u.Pb.GetCity(), 120 | u.Pb.GetProvince(), 121 | u.Pb.GetJabatan(), 122 | now, 123 | u.Pb.GetCreatedBy(), 124 | now, 125 | u.Pb.GetUpdatedBy(), 126 | ) 127 | if err != nil { 128 | return status.Errorf(codes.Internal, "Exec insert employee: %v", err) 129 | } 130 | 131 | u.Pb.CreatedAt = now.String() 132 | u.Pb.UpdatedAt = u.Pb.CreatedAt 133 | 134 | return nil 135 | } 136 | 137 | // Update Employee 138 | func (u *Employee) Update(ctx context.Context, db *sql.DB) error { 139 | now := time.Now().UTC() 140 | u.Pb.UpdatedBy = ctx.Value(app.Ctx("userID")).(string) 141 | query := ` 142 | UPDATE employees SET 143 | user_id = $1, 144 | name = $2, 145 | address = $3, 146 | city = $4, 147 | province = $5, 148 | jabatan = $6, 149 | updated_at = $7, 150 | updated_by = $8 151 | WHERE id = $9 152 | ` 153 | stmt, err := db.PrepareContext(ctx, query) 154 | if err != nil { 155 | return status.Errorf(codes.Internal, "Prepare update employee: %v", err) 156 | } 157 | defer stmt.Close() 158 | 159 | _, err = stmt.ExecContext(ctx, 160 | u.Pb.GetUser().GetId(), 161 | u.Pb.GetName(), 162 | u.Pb.GetAddress(), 163 | u.Pb.GetCity(), 164 | u.Pb.GetProvince(), 165 | u.Pb.GetJabatan(), 166 | now, 167 | u.Pb.GetUpdatedBy(), 168 | u.Pb.GetId(), 169 | ) 170 | if err != nil { 171 | return status.Errorf(codes.Internal, "Exec update employee: %v", err) 172 | } 173 | 174 | u.Pb.UpdatedAt = now.String() 175 | 176 | return nil 177 | } 178 | 179 | // Delete employee 180 | func (u *Employee) Delete(ctx context.Context, db *sql.DB) error { 181 | stmt, err := db.PrepareContext(ctx, `DELETE FROM employees WHERE id = $1`) 182 | if err != nil { 183 | return status.Errorf(codes.Internal, "Prepare delete employee: %v", err) 184 | } 185 | defer stmt.Close() 186 | 187 | _, err = stmt.ExecContext(ctx, u.Pb.GetId()) 188 | if err != nil { 189 | return status.Errorf(codes.Internal, "Exec delete employee: %v", err) 190 | } 191 | 192 | return nil 193 | } 194 | 195 | // ListQuery builder 196 | func (u *Employee) ListQuery(ctx context.Context, db *sql.DB, in *users.ListEmployeeRequest, userLogin *users.User) (string, []interface{}, *users.EmployeePaginationResponse, error) { 197 | var paginationResponse users.EmployeePaginationResponse 198 | query := ` 199 | SELECT employees.id, employees.name, employees.code, employees.address, 200 | employees.city, employees.province, employees.jabatan, 201 | users.id, users.company_id, users.region_id, users.branch_id, users.name, users.email 202 | FROM employees 203 | JOIN users ON employees.user_id = users.id 204 | ` 205 | where := []string{"users.company_id = $1"} 206 | paramQueries := []interface{}{userLogin.GetCompanyId()} 207 | 208 | if len(userLogin.GetRegionId()) > 0 { 209 | paramQueries = append(paramQueries, userLogin.GetRegionId()) 210 | where = append(where, fmt.Sprintf(`users.region_id = $%d`, len(paramQueries))) 211 | } 212 | 213 | if len(userLogin.GetBranchId()) > 0 { 214 | paramQueries = append(paramQueries, userLogin.GetBranchId()) 215 | where = append(where, fmt.Sprintf(`users.branch_id = $%d`, len(paramQueries))) 216 | } 217 | 218 | if len(in.GetPagination().GetSearch()) > 0 { 219 | paramQueries = append(paramQueries, "%"+in.GetPagination().GetSearch()+"%") 220 | where = append(where, fmt.Sprintf(`(employees.name ILIKE $%d OR employees.code ILIKE $%d 221 | OR employees.address ILIKE $%d OR employees.city ILIKE $%d OR employees.province ILIKE $%d 222 | OR employees.jabatan ILIKE $%d OR users.name ILIKE $%d OR users.email ILIKE $%d)`, 223 | len(paramQueries), len(paramQueries), len(paramQueries), len(paramQueries), 224 | len(paramQueries), len(paramQueries), len(paramQueries), len(paramQueries))) 225 | } 226 | 227 | { 228 | qCount := `SELECT COUNT(*) FROM employees JOIN users ON employees.user_id = users.id` 229 | if len(where) > 0 { 230 | qCount += " WHERE " + strings.Join(where, " AND ") 231 | } 232 | var count int 233 | err := db.QueryRowContext(ctx, qCount, paramQueries...).Scan(&count) 234 | if err != nil && err != sql.ErrNoRows { 235 | return query, paramQueries, &paginationResponse, status.Error(codes.Internal, err.Error()) 236 | } 237 | 238 | paginationResponse.Count = uint32(count) 239 | } 240 | 241 | if len(where) > 0 { 242 | query += ` WHERE ` + strings.Join(where, " AND ") 243 | } 244 | 245 | if len(in.GetPagination().GetOrderBy()) == 0 || !(in.GetPagination().GetOrderBy() == "employees.name" || 246 | in.GetPagination().GetOrderBy() == "employees.code") { 247 | if in.GetPagination() == nil { 248 | in.Pagination = &users.Pagination{OrderBy: "employees.created_at"} 249 | } else { 250 | in.GetPagination().OrderBy = "employees.created_at" 251 | } 252 | } 253 | 254 | query += ` ORDER BY ` + in.GetPagination().GetOrderBy() + ` ` + in.GetPagination().GetSort().String() 255 | 256 | if in.GetPagination().GetLimit() > 0 { 257 | query += fmt.Sprintf(` LIMIT $%d OFFSET $%d`, (len(paramQueries) + 1), (len(paramQueries) + 2)) 258 | paramQueries = append(paramQueries, in.GetPagination().GetLimit(), in.GetPagination().GetOffset()) 259 | } 260 | 261 | return query, paramQueries, &paginationResponse, nil 262 | } 263 | -------------------------------------------------------------------------------- /internal/model/feature.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | 7 | "github.com/jacky-htg/erp-proto/go/pb/users" 8 | "google.golang.org/grpc/codes" 9 | "google.golang.org/grpc/status" 10 | ) 11 | 12 | // Feature struct 13 | type Feature struct { 14 | Pb users.Feature 15 | PackageID string 16 | } 17 | 18 | // FeaturePackage struct 19 | type FeaturePackage struct { 20 | Pb users.PackageOfFeature 21 | } 22 | 23 | // GetByName func 24 | func (u *FeaturePackage) GetByName(ctx context.Context, db *sql.DB) error { 25 | query := `SELECT id, name FROM package_features WHERE name = $1` 26 | stmt, err := db.PrepareContext(ctx, query) 27 | if err != nil { 28 | return status.Errorf(codes.Internal, "Prepare statement: %v", err) 29 | } 30 | defer stmt.Close() 31 | var name string 32 | err = stmt.QueryRowContext(ctx, u.Pb.GetName().String()).Scan(&u.Pb.Id, &name) 33 | 34 | if err == sql.ErrNoRows { 35 | return status.Errorf(codes.NotFound, "Query Raw: %v", err) 36 | } 37 | 38 | if err != nil { 39 | return status.Errorf(codes.Internal, "Query Raw: %v", err) 40 | } 41 | 42 | feature := Feature{PackageID: u.Pb.GetId()} 43 | u.Pb.Features, err = feature.GetByPackage(ctx, db) 44 | if err != nil { 45 | return err 46 | } 47 | 48 | return nil 49 | } 50 | 51 | // Get func 52 | func (u *FeaturePackage) Get(ctx context.Context, db *sql.DB) error { 53 | query := `SELECT id, name FROM package_features WHERE id = $1` 54 | stmt, err := db.PrepareContext(ctx, query) 55 | if err != nil { 56 | return status.Errorf(codes.Internal, "Prepare statement get package feature: %v", err) 57 | } 58 | defer stmt.Close() 59 | var name string 60 | err = stmt.QueryRowContext(ctx, u.Pb.GetId()).Scan(&u.Pb.Id, &name) 61 | 62 | if err == sql.ErrNoRows { 63 | return status.Errorf(codes.NotFound, "Query Raw get package feature: %v", err) 64 | } 65 | 66 | if err != nil { 67 | return status.Errorf(codes.Internal, "Query Raw get package feature: %v", err) 68 | } 69 | 70 | if value, ok := users.EnumPackageOfFeature_value[name]; ok { 71 | u.Pb.Name = users.EnumPackageOfFeature(value) 72 | } 73 | 74 | if u.Pb.Name == users.EnumPackageOfFeature(0) { 75 | feature := Feature{} 76 | u.Pb.Features, err = feature.GetAll(ctx, db) 77 | if err != nil { 78 | return err 79 | } 80 | } else { 81 | feature := Feature{PackageID: u.Pb.GetId()} 82 | u.Pb.Features, err = feature.GetByPackage(ctx, db) 83 | if err != nil { 84 | return err 85 | } 86 | } 87 | 88 | return nil 89 | } 90 | 91 | // GetByPackage feature 92 | func (u *Feature) GetByPackage(ctx context.Context, db *sql.DB) ([]*users.Feature, error) { 93 | var list []*users.Feature 94 | query := ` 95 | SELECT features.id, features.name 96 | FROM features 97 | JOIN features_package_features ON features.id = features_package_features.feature_id 98 | WHERE features_package_features.package_feature_id = $1 99 | ` 100 | rows, err := db.QueryContext(ctx, query, u.PackageID) 101 | if err != nil { 102 | return list, status.Error(codes.Internal, err.Error()) 103 | } 104 | defer rows.Close() 105 | 106 | for rows.Next() { 107 | var pbFeature users.Feature 108 | err = rows.Scan(&pbFeature.Id, &pbFeature.Name) 109 | if err != nil { 110 | return list, status.Error(codes.Internal, err.Error()) 111 | } 112 | 113 | list = append(list, &pbFeature) 114 | } 115 | 116 | return list, nil 117 | } 118 | 119 | // Get Feature 120 | func (u *Feature) Get(ctx context.Context, db *sql.DB) error { 121 | query := `SELECT id, name FROM features WHERE id = $1` 122 | stmt, err := db.PrepareContext(ctx, query) 123 | if err != nil { 124 | return status.Errorf(codes.Internal, "Prepare statement: %v", err) 125 | } 126 | defer stmt.Close() 127 | 128 | err = stmt.QueryRowContext(ctx, u.Pb.GetName()).Scan(&u.Pb.Id, &u.Pb.Name) 129 | 130 | if err == sql.ErrNoRows { 131 | return status.Errorf(codes.NotFound, "Query Raw: %v", err) 132 | } 133 | 134 | if err != nil { 135 | return status.Errorf(codes.Internal, "Query Raw: %v", err) 136 | } 137 | 138 | return nil 139 | } 140 | 141 | // GetAll Features 142 | func (u *Feature) GetAll(ctx context.Context, db *sql.DB) ([]*users.Feature, error) { 143 | var list []*users.Feature 144 | rows, err := db.QueryContext(ctx, `SELECT id, name from features`) 145 | if err != nil { 146 | return list, status.Error(codes.Internal, err.Error()) 147 | } 148 | defer rows.Close() 149 | 150 | for rows.Next() { 151 | var pbFeature users.Feature 152 | err = rows.Scan(&pbFeature.Id, &pbFeature.Name) 153 | if err != nil { 154 | return list, err 155 | } 156 | 157 | list = append(list, &pbFeature) 158 | 159 | } 160 | 161 | return list, nil 162 | } 163 | -------------------------------------------------------------------------------- /internal/model/group.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "encoding/json" 7 | "fmt" 8 | "strings" 9 | "time" 10 | 11 | "github.com/google/uuid" 12 | "github.com/jacky-htg/erp-pkg/app" 13 | "github.com/jacky-htg/erp-proto/go/pb/users" 14 | "google.golang.org/grpc/codes" 15 | "google.golang.org/grpc/status" 16 | ) 17 | 18 | // Group model 19 | type Group struct { 20 | Pb users.Group 21 | WithAccess bool 22 | } 23 | 24 | // Get func 25 | func (u *Group) Get(ctx context.Context, db *sql.DB) error { 26 | query := ` 27 | SELECT groups.id, groups.company_id, groups.name, groups.is_mutable, 28 | json_agg(DISTINCT jsonb_build_object( 29 | 'id', access.id, 30 | 'name', access.name 31 | )) as access 32 | FROM groups 33 | LEFT JOIN access_groups ON groups.id = access_groups.group_id 34 | LEFT JOIN access ON access_groups.access_id = access.id 35 | WHERE groups.id = $1 36 | GROUP BY groups.id, groups.company_id, groups.name, groups.is_mutable` 37 | stmt, err := db.PrepareContext(ctx, query) 38 | if err != nil { 39 | return status.Errorf(codes.Internal, "Prepare statement: %v", err) 40 | } 41 | defer stmt.Close() 42 | 43 | var tmpAccess string 44 | err = stmt.QueryRowContext(ctx, u.Pb.GetId()).Scan(&u.Pb.Id, &u.Pb.CompanyId, &u.Pb.Name, &u.Pb.IsMutable, &tmpAccess) 45 | 46 | if err == sql.ErrNoRows { 47 | return status.Errorf(codes.NotFound, "Query Raw: %v", err) 48 | } 49 | 50 | if err != nil { 51 | return status.Errorf(codes.Internal, "Query Raw: %v", err) 52 | } 53 | 54 | err = json.Unmarshal([]byte(tmpAccess), &u.Pb.Access) 55 | if err != nil { 56 | return status.Errorf(codes.Internal, "unmarshal access: %v", err) 57 | } 58 | 59 | return nil 60 | } 61 | 62 | // Create Group 63 | func (u *Group) Create(ctx context.Context, db *sql.DB) error { 64 | var err error 65 | u.Pb.Id = uuid.New().String() 66 | now := time.Now().UTC() 67 | u.Pb.CreatedBy = ctx.Value(app.Ctx("userID")).(string) 68 | u.Pb.UpdatedBy = ctx.Value(app.Ctx("userID")).(string) 69 | 70 | u.Pb.CreatedAt = now.String() 71 | u.Pb.UpdatedAt = u.Pb.CreatedAt 72 | 73 | query := ` 74 | INSERT INTO groups (id, company_id, name, created_at, created_by, updated_at, updated_by) 75 | VALUES ($1, $2, $3, $4, $5, $6, $7) 76 | ` 77 | stmt, err := db.PrepareContext(ctx, query) 78 | if err != nil { 79 | return status.Errorf(codes.Internal, "Prepare insert group: %v", err) 80 | } 81 | defer stmt.Close() 82 | 83 | _, err = stmt.ExecContext(ctx, 84 | u.Pb.GetId(), 85 | u.Pb.GetCompanyId(), 86 | u.Pb.GetName(), 87 | now, 88 | u.Pb.GetCreatedBy(), 89 | now, 90 | u.Pb.GetUpdatedBy(), 91 | ) 92 | if err != nil { 93 | return status.Errorf(codes.Internal, "Exec insert group: %v", err) 94 | } 95 | 96 | return nil 97 | } 98 | 99 | // Update Group 100 | func (u *Group) Update(ctx context.Context, db *sql.DB) error { 101 | var err error 102 | now := time.Now().UTC() 103 | u.Pb.UpdatedBy = ctx.Value(app.Ctx("userID")).(string) 104 | u.Pb.UpdatedAt = now.String() 105 | 106 | query := ` 107 | UPDATE groups SET 108 | name = $1, 109 | updated_at = $2, 110 | updated_by = $3 111 | WHERE id = $4 112 | ` 113 | stmt, err := db.PrepareContext(ctx, query) 114 | if err != nil { 115 | return status.Errorf(codes.Internal, "Prepare update group: %v", err) 116 | } 117 | defer stmt.Close() 118 | 119 | _, err = stmt.ExecContext(ctx, 120 | u.Pb.GetName(), 121 | now, 122 | u.Pb.GetUpdatedBy(), 123 | u.Pb.GetId(), 124 | ) 125 | if err != nil { 126 | return status.Errorf(codes.Internal, "Exec update group: %v", err) 127 | } 128 | 129 | return nil 130 | } 131 | 132 | // Delete group 133 | func (u *Group) Delete(ctx context.Context, db *sql.DB) error { 134 | stmt, err := db.PrepareContext(ctx, `DELETE FROM groups WHERE id = $1`) 135 | if err != nil { 136 | return status.Errorf(codes.Internal, "Prepare delete group: %v", err) 137 | } 138 | defer stmt.Close() 139 | 140 | _, err = stmt.ExecContext(ctx, u.Pb.GetId()) 141 | if err != nil { 142 | return status.Errorf(codes.Internal, "Exec delete group: %v", err) 143 | } 144 | 145 | return nil 146 | } 147 | 148 | // ListQuery builder 149 | func (u *Group) ListQuery(ctx context.Context, db *sql.DB, in *users.ListGroupRequest) (string, []interface{}, *users.GroupPaginationResponse, error) { 150 | var paginationResponse users.GroupPaginationResponse 151 | query := ` 152 | SELECT groups.id, groups.company_id, groups.name, groups.is_mutable, 153 | json_agg(DISTINCT jsonb_build_object( 154 | 'id', access.id, 155 | 'name', access.name 156 | )) as access 157 | FROM groups 158 | LEFT JOIN access_groups ON groups.id = access_groups.group_id 159 | LEFT JOIN access ON access_groups.access_id = access.id 160 | ` 161 | where := []string{"groups.company_id = $1"} 162 | paramQueries := []interface{}{ctx.Value(app.Ctx("companyID")).(string)} 163 | 164 | if len(in.GetPagination().GetSearch()) > 0 { 165 | paramQueries = append(paramQueries, "%"+in.GetPagination().GetSearch()+"%") 166 | where = append(where, fmt.Sprintf(`(groups.name ILIKE $%d)`, len(paramQueries))) 167 | } 168 | 169 | { 170 | qCount := ` 171 | SELECT COUNT(*) 172 | FROM groups 173 | LEFT JOIN access_groups ON groups.id = access_groups.group_id 174 | LEFT JOIN access ON access_groups.access_id = access.id 175 | ` 176 | if len(where) > 0 { 177 | qCount += " WHERE " + strings.Join(where, " AND ") 178 | } 179 | var count int 180 | err := db.QueryRowContext(ctx, qCount, paramQueries...).Scan(&count) 181 | if err != nil && err != sql.ErrNoRows { 182 | return query, paramQueries, &paginationResponse, status.Error(codes.Internal, err.Error()) 183 | } 184 | 185 | paginationResponse.Count = uint32(count) 186 | } 187 | 188 | if len(where) > 0 { 189 | query += ` WHERE ` + strings.Join(where, " AND ") 190 | } 191 | 192 | query += ` GROUP BY groups.id, groups.company_id, groups.name, groups.is_mutable` 193 | 194 | if len(in.GetPagination().GetOrderBy()) == 0 || !(in.GetPagination().GetOrderBy() == "groups.name") { 195 | if in.GetPagination() == nil { 196 | in.Pagination = &users.Pagination{OrderBy: "groups.created_at"} 197 | } else { 198 | in.GetPagination().OrderBy = "groups.created_at" 199 | } 200 | } 201 | 202 | query += ` ORDER BY ` + in.GetPagination().GetOrderBy() + ` ` + in.GetPagination().GetSort().String() 203 | 204 | if in.GetPagination().GetLimit() > 0 { 205 | query += fmt.Sprintf(` LIMIT $%d OFFSET $%d`, (len(paramQueries) + 1), (len(paramQueries) + 2)) 206 | paramQueries = append(paramQueries, in.GetPagination().GetLimit(), in.GetPagination().GetOffset()) 207 | } 208 | 209 | return query, paramQueries, &paginationResponse, nil 210 | } 211 | -------------------------------------------------------------------------------- /internal/model/region.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "fmt" 7 | "strings" 8 | "time" 9 | 10 | "github.com/google/uuid" 11 | "github.com/jacky-htg/erp-pkg/app" 12 | "github.com/jacky-htg/erp-proto/go/pb/users" 13 | "google.golang.org/grpc/codes" 14 | "google.golang.org/grpc/status" 15 | ) 16 | 17 | // Region model 18 | type Region struct { 19 | Pb users.Region 20 | UpdateBranches bool 21 | } 22 | 23 | // Get func 24 | func (u *Region) Get(ctx context.Context, db *sql.DB) error { 25 | query := `SELECT id, company_id, name, code FROM regions WHERE id = $1` 26 | stmt, err := db.PrepareContext(ctx, query) 27 | if err != nil { 28 | return status.Errorf(codes.Internal, "Prepare statement get region: %v", err) 29 | } 30 | defer stmt.Close() 31 | 32 | err = stmt.QueryRowContext(ctx, u.Pb.GetId()).Scan(&u.Pb.Id, &u.Pb.CompanyId, &u.Pb.Name, &u.Pb.Code) 33 | 34 | if err == sql.ErrNoRows { 35 | return status.Errorf(codes.NotFound, "Query Raw get region: %v", err) 36 | } 37 | 38 | if err != nil { 39 | return status.Errorf(codes.Internal, "Query Raw get region: %v", err) 40 | } 41 | 42 | return nil 43 | } 44 | 45 | // GetByCode Region 46 | func (u *Region) GetByCode(ctx context.Context, db *sql.DB) error { 47 | query := `SELECT id, company_id, name, code FROM regions WHERE company_id = $1 AND code = $2` 48 | stmt, err := db.PrepareContext(ctx, query) 49 | if err != nil { 50 | return status.Errorf(codes.Internal, "Prepare statement get region by code: %v", err) 51 | } 52 | defer stmt.Close() 53 | 54 | err = stmt.QueryRowContext(ctx, ctx.Value(app.Ctx("companyID")).(string), u.Pb.GetCode()).Scan(&u.Pb.Id, &u.Pb.CompanyId, &u.Pb.Name, &u.Pb.Code) 55 | 56 | if err == sql.ErrNoRows { 57 | return status.Errorf(codes.NotFound, "Query Raw get region by code: %v", err) 58 | } 59 | 60 | if err != nil { 61 | return status.Errorf(codes.Internal, "Query Raw get region by code: %v", err) 62 | } 63 | 64 | return nil 65 | } 66 | 67 | // Create Region 68 | func (u *Region) Create(ctx context.Context, db *sql.DB, tx *sql.Tx) error { 69 | u.Pb.Id = uuid.New().String() 70 | query := ` 71 | INSERT INTO regions (id, company_id, name, code, created_by, updated_by) 72 | VALUES ($1, $2, $3, $4, $5, $5) 73 | ` 74 | stmt, err := tx.PrepareContext(ctx, query) 75 | if err != nil { 76 | return status.Errorf(codes.Internal, "Prepare insert region: %v", err) 77 | } 78 | defer stmt.Close() 79 | 80 | _, err = stmt.ExecContext(ctx, 81 | u.Pb.GetId(), 82 | u.Pb.GetCompanyId(), 83 | u.Pb.GetName(), 84 | u.Pb.GetCode(), 85 | ctx.Value(app.Ctx("userID")).(string), 86 | ) 87 | if err != nil { 88 | return status.Errorf(codes.Internal, "Exec insert region: %v", err) 89 | } 90 | 91 | if len(u.Pb.GetBranches()) > 0 { 92 | err = u.regionBranches(ctx, tx, u.Pb.GetBranches()) 93 | if err != nil { 94 | return err 95 | } 96 | } 97 | 98 | return nil 99 | } 100 | 101 | // Update Region 102 | func (u *Region) Update(ctx context.Context, db *sql.DB, tx *sql.Tx) error { 103 | query := ` 104 | UPDATE regions SET 105 | name = $1, 106 | updated_by = $2, 107 | updated_at = $3 108 | WHERE id = $4 109 | ` 110 | stmt, err := tx.PrepareContext(ctx, query) 111 | if err != nil { 112 | return status.Errorf(codes.Internal, "Prepare update region: %v", err) 113 | } 114 | defer stmt.Close() 115 | 116 | _, err = stmt.ExecContext(ctx, 117 | u.Pb.GetName(), 118 | ctx.Value(app.Ctx("userID")).(string), 119 | time.Now().UTC(), 120 | u.Pb.GetId(), 121 | ) 122 | if err != nil { 123 | return status.Errorf(codes.Internal, "Exec update region: %v", err) 124 | } 125 | 126 | if u.UpdateBranches && len(u.Pb.GetBranches()) > 0 { 127 | err = u.regionBranches(ctx, tx, u.Pb.GetBranches()) 128 | if err != nil { 129 | return err 130 | } 131 | } 132 | 133 | return nil 134 | } 135 | 136 | // Delete region 137 | func (u *Region) Delete(ctx context.Context, db *sql.DB) error { 138 | stmt, err := db.PrepareContext(ctx, `DELETE FROM regions WHERE id = $1`) 139 | if err != nil { 140 | return status.Errorf(codes.Internal, "Prepare delete region: %v", err) 141 | } 142 | defer stmt.Close() 143 | 144 | _, err = stmt.ExecContext(ctx, u.Pb.GetId()) 145 | if err != nil { 146 | return status.Errorf(codes.Internal, "Exec delete region: %v", err) 147 | } 148 | 149 | return nil 150 | } 151 | 152 | // ListQuery builder 153 | func (u *Region) ListQuery(ctx context.Context, db *sql.DB, in *users.ListRegionRequest, regionID string) (string, []interface{}, *users.RegionPaginationResponse, error) { 154 | var paginationResponse users.RegionPaginationResponse 155 | query := `SELECT id, company_id, name, code FROM regions` 156 | where := []string{"company_id = $1"} 157 | paramQueries := []interface{}{ctx.Value(app.Ctx("companyID")).(string)} 158 | 159 | if len(regionID) > 0 { 160 | paramQueries = append(paramQueries, regionID) 161 | where = append(where, fmt.Sprintf(`id = $%d`, len(paramQueries))) 162 | } 163 | 164 | if len(in.GetPagination().GetSearch()) > 0 { 165 | paramQueries = append(paramQueries, "%"+in.GetPagination().GetSearch()+"%") 166 | where = append(where, fmt.Sprintf(`(name ILIKE $%d OR code ILIKE $%d)`, 167 | len(paramQueries), len(paramQueries))) 168 | } 169 | 170 | { 171 | qCount := `SELECT COUNT(*) FROM regions` 172 | if len(where) > 0 { 173 | qCount += " WHERE " + strings.Join(where, " AND ") 174 | } 175 | var count int 176 | err := db.QueryRowContext(ctx, qCount, paramQueries...).Scan(&count) 177 | if err != nil && err != sql.ErrNoRows { 178 | return query, paramQueries, &paginationResponse, status.Error(codes.Internal, err.Error()) 179 | } 180 | 181 | paginationResponse.Count = uint32(count) 182 | } 183 | 184 | if len(where) > 0 { 185 | query += ` WHERE ` + strings.Join(where, " AND ") 186 | } 187 | 188 | if len(in.GetPagination().GetOrderBy()) == 0 || !(in.GetPagination().GetOrderBy() == "name" || 189 | in.GetPagination().GetOrderBy() == "code") { 190 | if in.GetPagination() == nil { 191 | in.Pagination = &users.Pagination{OrderBy: "created_at"} 192 | } else { 193 | in.GetPagination().OrderBy = "created_at" 194 | } 195 | } 196 | 197 | query += ` ORDER BY ` + in.GetPagination().GetOrderBy() + ` ` + in.GetPagination().GetSort().String() 198 | 199 | if in.GetPagination().GetLimit() > 0 { 200 | query += fmt.Sprintf(` LIMIT $%d OFFSET $%d`, (len(paramQueries) + 1), (len(paramQueries) + 2)) 201 | paramQueries = append(paramQueries, in.GetPagination().GetLimit(), in.GetPagination().GetOffset()) 202 | } 203 | 204 | return query, paramQueries, &paginationResponse, nil 205 | } 206 | 207 | func (u *Region) regionBranches(ctx context.Context, tx *sql.Tx, branches []*users.Branch) error { 208 | for _, branch := range branches { 209 | branchesRegion := BranchesRegion{RegionID: u.Pb.GetId(), BranchID: branch.GetId()} 210 | err := branchesRegion.Create(ctx, tx) 211 | if err != nil { 212 | return err 213 | } 214 | } 215 | 216 | return nil 217 | } 218 | -------------------------------------------------------------------------------- /internal/model/request_password.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "time" 7 | 8 | "github.com/google/uuid" 9 | "github.com/jacky-htg/erp-proto/go/pb/users" 10 | "google.golang.org/grpc/codes" 11 | "google.golang.org/grpc/status" 12 | ) 13 | 14 | // RequestPassword model 15 | type RequestPassword struct { 16 | Pb users.RequestPassword 17 | } 18 | 19 | // Create func 20 | func (u *RequestPassword) Create(ctx context.Context, db *sql.DB) error { 21 | u.Pb.Id = uuid.New().String() 22 | stmt, err := db.PrepareContext(ctx, `INSERT INTO request_passwords (id, user_id, created_at) VALUES ($1, $2, $3)`) 23 | if err != nil { 24 | return status.Errorf(codes.Internal, "prepare insert: %v", err) 25 | } 26 | defer stmt.Close() 27 | 28 | _, err = stmt.ExecContext(ctx, u.Pb.GetId(), u.Pb.GetUserId(), time.Now().UTC()) 29 | if err != nil { 30 | return status.Errorf(codes.Internal, "exec insert: %v", err) 31 | } 32 | 33 | return nil 34 | } 35 | 36 | // Get func 37 | func (u *RequestPassword) Get(ctx context.Context, db *sql.DB) error { 38 | query := `SELECT id, user_id, is_used, created_at FROM request_passwords WHERE id = $1` 39 | stmt, err := db.PrepareContext(ctx, query) 40 | if err != nil { 41 | return status.Errorf(codes.Internal, "Prepare statement: %v", err) 42 | } 43 | defer stmt.Close() 44 | 45 | var createdAt time.Time 46 | err = stmt.QueryRowContext(ctx, u.Pb.GetId()).Scan(&u.Pb.Id, &u.Pb.UserId, &u.Pb.IsUsed, &createdAt) 47 | 48 | if err == sql.ErrNoRows { 49 | return status.Errorf(codes.NotFound, "Query Raw: %v", err) 50 | } 51 | 52 | if err != nil { 53 | return status.Errorf(codes.Internal, "Query Raw: %v", err) 54 | } 55 | 56 | u.Pb.CreatedAt = createdAt.String() 57 | 58 | return nil 59 | } 60 | 61 | // UpdateIsUsed func 62 | func (u *RequestPassword) UpdateIsUsed(ctx context.Context, tx *sql.Tx) error { 63 | 64 | stmt, err := tx.PrepareContext(ctx, `UPDATE request_passwords SET is_used = $1 WHERE id = $2`) 65 | defer stmt.Close() 66 | if err != nil { 67 | return status.Errorf(codes.Internal, "prepare update: %v", err) 68 | } 69 | 70 | _, err = stmt.ExecContext(ctx, true, u.Pb.GetId()) 71 | if err != nil { 72 | return status.Errorf(codes.Internal, "exec update: %v", err) 73 | } 74 | return nil 75 | } 76 | -------------------------------------------------------------------------------- /internal/model/user.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "encoding/json" 7 | "fmt" 8 | "strings" 9 | "time" 10 | 11 | "github.com/google/uuid" 12 | "github.com/jacky-htg/erp-pkg/app" 13 | "github.com/jacky-htg/erp-proto/go/pb/users" 14 | "golang.org/x/crypto/bcrypt" 15 | "google.golang.org/grpc/codes" 16 | "google.golang.org/grpc/status" 17 | ) 18 | 19 | // User struct 20 | type User struct { 21 | Pb users.User 22 | Password string 23 | } 24 | 25 | // GetByUserNamePassword func 26 | func (u *User) GetByUserNamePassword(ctx context.Context, db *sql.DB) error { 27 | var strPassword, tmpAccess string 28 | var regionID, branchID sql.NullString 29 | query := ` 30 | SELECT users.id, users.company_id, users.region_id, users.branch_id, users.name, users.email, users.password, 31 | groups.id groups_id, groups.name groups_name, 32 | json_agg(DISTINCT jsonb_build_object( 33 | 'id', access.id, 34 | 'name', access.name 35 | )) as access 36 | FROM users 37 | JOIN groups ON users.group_id = groups.id 38 | LEFT JOIN access_groups ON groups.id = access_groups.group_id 39 | LEFT JOIN access ON access_groups.access_id = access.id 40 | WHERE users.username = $1 41 | GROUP BY users.id, users.company_id, users.region_id, users.branch_id, users.name, users.email, users.password, groups_id, groups_name 42 | ` 43 | stmt, err := db.PrepareContext(ctx, query) 44 | if err != nil { 45 | return status.Errorf(codes.Internal, "Prepare statement: %v", err) 46 | } 47 | defer stmt.Close() 48 | 49 | group := users.Group{} 50 | err = stmt.QueryRowContext(ctx, u.Pb.GetUsername()).Scan( 51 | &u.Pb.Id, &u.Pb.CompanyId, ®ionID, &branchID, &u.Pb.Name, &u.Pb.Email, &strPassword, 52 | &group.Id, &group.Name, &tmpAccess) 53 | 54 | if err == sql.ErrNoRows { 55 | return status.Errorf(codes.NotFound, "Query Raw: %v", err) 56 | } 57 | 58 | if err != nil { 59 | return status.Errorf(codes.Internal, "Query Raw: %v", err) 60 | } 61 | 62 | u.Pb.RegionId = regionID.String 63 | u.Pb.BranchId = branchID.String 64 | 65 | err = bcrypt.CompareHashAndPassword([]byte(strPassword), []byte(u.Password)) 66 | if err != nil { 67 | return status.Errorf(codes.NotFound, "Invalid Password: %v", err) 68 | } 69 | 70 | err = json.Unmarshal([]byte(tmpAccess), &group.Access) 71 | if err != nil { 72 | return status.Errorf(codes.Internal, "unmarshal access: %v", err) 73 | } 74 | 75 | u.Pb.Group = &group 76 | 77 | return nil 78 | } 79 | 80 | // Get func 81 | func (u *User) Get(ctx context.Context, db *sql.DB) error { 82 | var regionID, branchID sql.NullString 83 | query := ` 84 | SELECT users.id, users.company_id, users.region_id, users.branch_id, users.name, users.email, 85 | groups.id groups_id, groups.name groups_name 86 | FROM users 87 | JOIN groups ON users.group_id = groups.id 88 | WHERE users.id = $1 89 | ` 90 | stmt, err := db.PrepareContext(ctx, query) 91 | if err != nil { 92 | return status.Errorf(codes.Internal, "Prepare statement: %v", err) 93 | } 94 | defer stmt.Close() 95 | 96 | group := users.Group{} 97 | err = stmt.QueryRowContext(ctx, u.Pb.GetId()).Scan( 98 | &u.Pb.Id, &u.Pb.CompanyId, ®ionID, &branchID, &u.Pb.Name, &u.Pb.Email, &group.Id, &group.Name) 99 | 100 | if err == sql.ErrNoRows { 101 | return status.Errorf(codes.NotFound, "Query Raw: %v", err) 102 | } 103 | 104 | if err != nil { 105 | return status.Errorf(codes.Internal, "Query Raw: %v", err) 106 | } 107 | 108 | u.Pb.RegionId = regionID.String 109 | u.Pb.BranchId = branchID.String 110 | u.Pb.Group = &group 111 | 112 | return nil 113 | } 114 | 115 | // GetByPassword func 116 | func (u *User) GetByPassword(ctx context.Context, db *sql.DB) error { 117 | var regionID, branchID sql.NullString 118 | var strPassword string 119 | query := ` 120 | SELECT users.id, users.company_id, users.region_id, users.branch_id, users.name, users.email, users.password, 121 | groups.id groups_id, groups.name groups_name 122 | FROM users 123 | JOIN groups ON users.group_id = groups.id 124 | WHERE users.id = $1 125 | ` 126 | stmt, err := db.PrepareContext(ctx, query) 127 | if err != nil { 128 | return status.Errorf(codes.Internal, "Prepare statement: %v", err) 129 | } 130 | defer stmt.Close() 131 | 132 | group := users.Group{} 133 | err = stmt.QueryRowContext(ctx, u.Pb.GetId()).Scan( 134 | &u.Pb.Id, &u.Pb.CompanyId, ®ionID, &branchID, &u.Pb.Name, &u.Pb.Email, &strPassword, &group.Id, &group.Name) 135 | 136 | if err == sql.ErrNoRows { 137 | return status.Errorf(codes.NotFound, "Query Raw: %v", err) 138 | } 139 | 140 | if err != nil { 141 | return status.Errorf(codes.Internal, "Query Raw: %v", err) 142 | } 143 | 144 | err = bcrypt.CompareHashAndPassword([]byte(strPassword), []byte(u.Password)) 145 | if err != nil { 146 | return status.Errorf(codes.NotFound, "Invalid Password: %v", err) 147 | } 148 | 149 | u.Pb.RegionId = regionID.String 150 | u.Pb.BranchId = branchID.String 151 | u.Pb.Group = &group 152 | 153 | return nil 154 | } 155 | 156 | // GetByEmail func 157 | func (u *User) GetByEmail(ctx context.Context, db *sql.DB) error { 158 | var regionID, branchID sql.NullString 159 | query := ` 160 | SELECT users.id, users.company_id, users.region_id, users.branch_id, users.name, users.email, 161 | groups.id groups_id, groups.name groups_name 162 | FROM users 163 | JOIN groups ON users.group_id = groups.id 164 | WHERE users.email = $1 165 | ` 166 | stmt, err := db.PrepareContext(ctx, query) 167 | if err != nil { 168 | return status.Errorf(codes.Internal, "Prepare statement: %v", err) 169 | } 170 | defer stmt.Close() 171 | 172 | group := users.Group{} 173 | err = stmt.QueryRowContext(ctx, u.Pb.GetEmail()).Scan( 174 | &u.Pb.Id, &u.Pb.CompanyId, ®ionID, &branchID, &u.Pb.Name, &u.Pb.Email, &group.Id, &group.Name) 175 | 176 | if err == sql.ErrNoRows { 177 | return status.Errorf(codes.NotFound, "Query Raw: %v", err) 178 | } 179 | 180 | if err != nil { 181 | return status.Errorf(codes.Internal, "Query Raw: %v", err) 182 | } 183 | 184 | u.Pb.RegionId = regionID.String 185 | u.Pb.BranchId = branchID.String 186 | u.Pb.Group = &group 187 | 188 | return nil 189 | } 190 | 191 | // GetByUsername func 192 | func (u *User) GetByUsername(ctx context.Context, db *sql.DB) error { 193 | var regionID, branchID sql.NullString 194 | query := ` 195 | SELECT users.id, users.company_id, users.region_id, users.branch_id, users.name, users.email, 196 | groups.id groups_id, groups.name groups_name 197 | FROM users 198 | JOIN groups ON users.group_id = groups.id 199 | WHERE users.username = $1 200 | ` 201 | stmt, err := db.PrepareContext(ctx, query) 202 | if err != nil { 203 | return status.Errorf(codes.Internal, "Prepare statement: %v", err) 204 | } 205 | defer stmt.Close() 206 | 207 | group := users.Group{} 208 | err = stmt.QueryRowContext(ctx, u.Pb.GetUsername()).Scan( 209 | &u.Pb.Id, &u.Pb.CompanyId, ®ionID, &branchID, &u.Pb.Name, &u.Pb.Email, &group.Id, &group.Name) 210 | 211 | if err == sql.ErrNoRows { 212 | return status.Errorf(codes.NotFound, "Query Raw: %v", err) 213 | } 214 | 215 | if err != nil { 216 | return status.Errorf(codes.Internal, "Query Raw: %v", err) 217 | } 218 | 219 | u.Pb.RegionId = regionID.String 220 | u.Pb.BranchId = branchID.String 221 | u.Pb.Group = &group 222 | 223 | return nil 224 | } 225 | 226 | // ChangePassword func 227 | func (u *User) ChangePassword(ctx context.Context, tx *sql.Tx) error { 228 | 229 | stmt, err := tx.PrepareContext(ctx, `UPDATE users SET password = $1 WHERE id = $2`) 230 | defer stmt.Close() 231 | if err != nil { 232 | return status.Errorf(codes.Internal, "prepare update: %v", err) 233 | } 234 | 235 | pass, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost) 236 | if err != nil { 237 | return status.Errorf(codes.Internal, "hash password: %v", err) 238 | } 239 | _, err = stmt.ExecContext(ctx, string(pass), u.Pb.GetId()) 240 | if err != nil { 241 | return status.Errorf(codes.Internal, "exec update: %v", err) 242 | } 243 | 244 | return nil 245 | } 246 | 247 | // IsAuth func 248 | func (u *User) IsAuth(ctx context.Context, db *sql.DB, access string) error { 249 | var parent *string 250 | strArr := strings.Split(access, "::") 251 | if len(strArr) > 1 { 252 | parent = &strArr[0] 253 | } 254 | query := ` 255 | SELECT 1 256 | FROM users 257 | JOIN groups ON users.group_id = groups.id 258 | JOIN access_groups ON groups.id = access_groups.group_id 259 | JOIN access ON access_groups.access_id = access.id 260 | WHERE users.id = $1 AND (access.name = 'root' OR access.name = $2 OR access.name = $3) 261 | ` 262 | stmt, err := db.PrepareContext(ctx, query) 263 | if err != nil { 264 | return status.Errorf(codes.Internal, "Prepare statement: %v", err) 265 | } 266 | defer stmt.Close() 267 | 268 | var result bool 269 | err = stmt.QueryRowContext(ctx, u.Pb.GetId(), parent, access).Scan(&result) 270 | 271 | if err == sql.ErrNoRows { 272 | return status.Errorf(codes.NotFound, "Query Raw: %v", err) 273 | } 274 | 275 | if err != nil { 276 | return status.Errorf(codes.Internal, "Query Raw: %v", err) 277 | } 278 | 279 | return nil 280 | } 281 | 282 | // Create new user 283 | func (u *User) Create(ctx context.Context, db *sql.DB) error { 284 | query := ` 285 | INSERT INTO users (id, company_id, region_id, branch_id, group_id, username, name, email, password, updated_by) 286 | VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) 287 | ` 288 | var regionID, branchID *string 289 | if len(u.Pb.GetRegionId()) > 0 { 290 | regionID = &u.Pb.RegionId 291 | } 292 | 293 | if len(u.Pb.GetBranchId()) > 0 { 294 | branchID = &u.Pb.BranchId 295 | } 296 | 297 | u.Pb.Id = uuid.New().String() 298 | 299 | pass, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost) 300 | if err != nil { 301 | return status.Errorf(codes.Internal, "hash password: %v", err) 302 | } 303 | 304 | stmt, err := db.PrepareContext(ctx, query) 305 | if err != nil { 306 | return status.Errorf(codes.Internal, "Prepare insert: %v", err) 307 | } 308 | defer stmt.Close() 309 | 310 | _, err = stmt.ExecContext(ctx, 311 | u.Pb.GetId(), 312 | u.Pb.GetCompanyId(), 313 | regionID, 314 | branchID, 315 | u.Pb.GetGroup().GetId(), 316 | u.Pb.GetUsername(), 317 | u.Pb.GetName(), 318 | u.Pb.GetEmail(), 319 | string(pass), 320 | ctx.Value(app.Ctx("userID")).(string), 321 | ) 322 | if err != nil { 323 | return status.Errorf(codes.Internal, "Exec insert: %v", err) 324 | } 325 | 326 | return nil 327 | } 328 | 329 | // Update user 330 | func (u *User) Update(ctx context.Context, db *sql.DB) error { 331 | query := ` 332 | UPDATE users SET 333 | region_id = $1, 334 | branch_id = $2, 335 | group_id = $3, 336 | name = $4, 337 | updated_by = $5, 338 | updated_at = $6 339 | WHERE id = $7 340 | ` 341 | var regionID, branchID *string 342 | if len(u.Pb.GetRegionId()) > 0 { 343 | regionID = &u.Pb.RegionId 344 | } 345 | 346 | if len(u.Pb.GetBranchId()) > 0 { 347 | branchID = &u.Pb.BranchId 348 | } 349 | 350 | stmt, err := db.PrepareContext(ctx, query) 351 | if err != nil { 352 | return status.Errorf(codes.Internal, "Prepare update: %v", err) 353 | } 354 | defer stmt.Close() 355 | 356 | _, err = stmt.ExecContext(ctx, 357 | regionID, 358 | branchID, 359 | u.Pb.GetGroup().GetId(), 360 | u.Pb.GetName(), 361 | ctx.Value(app.Ctx("userID")).(string), 362 | time.Now().UTC(), 363 | u.Pb.GetId(), 364 | ) 365 | if err != nil { 366 | return status.Errorf(codes.Internal, "Exec update: %v", err) 367 | } 368 | 369 | return nil 370 | } 371 | 372 | // Delete user 373 | func (u *User) Delete(ctx context.Context, db *sql.DB) error { 374 | stmt, err := db.PrepareContext(ctx, `DELETE FROM users WHERE id = $1`) 375 | if err != nil { 376 | return status.Errorf(codes.Internal, "Prepare delete: %v", err) 377 | } 378 | defer stmt.Close() 379 | 380 | _, err = stmt.ExecContext(ctx, u.Pb.GetId()) 381 | if err != nil { 382 | return status.Errorf(codes.Internal, "Exec delete: %v", err) 383 | } 384 | 385 | return nil 386 | } 387 | 388 | // ListQuery builder 389 | func (u *User) ListQuery(ctx context.Context, db *sql.DB, in *users.ListUserRequest) (string, []interface{}, *users.UserPaginationResponse, error) { 390 | var paginationResponse users.UserPaginationResponse 391 | query := ` 392 | SELECT users.id, users.company_id, users.region_id, users.branch_id, users.name, users.email, 393 | groups.id groups_id, groups.name groups_name 394 | FROM users 395 | JOIN groups ON users.group_id = groups.id 396 | ` 397 | where := []string{} 398 | paramQueries := []interface{}{} 399 | 400 | if len(in.GetBranchId()) > 0 { 401 | paramQueries = append(paramQueries, in.GetBranchId()) 402 | where = append(where, fmt.Sprintf(`users.branch_id = $%d`, len(paramQueries))) 403 | } 404 | 405 | if len(in.GetCompanyId()) > 0 { 406 | paramQueries = append(paramQueries, in.GetCompanyId()) 407 | where = append(where, fmt.Sprintf(`users.company_id = $%d`, len(paramQueries))) 408 | } 409 | 410 | if len(in.GetPagination().GetSearch()) > 0 { 411 | paramQueries = append(paramQueries, "%"+in.GetPagination().GetSearch()+"%") 412 | where = append(where, fmt.Sprintf(`(users.name ILIKE $%d OR users.email ILIKE $%d OR groups.name ILIKE $%d)`, 413 | len(paramQueries), len(paramQueries), len(paramQueries))) 414 | } 415 | 416 | { 417 | qCount := `SELECT COUNT(*) FROM users JOIN groups ON users.group_id = groups.id` 418 | if len(where) > 0 { 419 | qCount += " WHERE " + strings.Join(where, " AND ") 420 | } 421 | var count int 422 | err := db.QueryRowContext(ctx, qCount, paramQueries...).Scan(&count) 423 | if err != nil && err != sql.ErrNoRows { 424 | return query, paramQueries, &paginationResponse, status.Error(codes.Internal, err.Error()) 425 | } 426 | 427 | paginationResponse.Count = uint32(count) 428 | } 429 | 430 | if len(where) > 0 { 431 | query += `WHERE ` + strings.Join(where, " AND ") 432 | } 433 | 434 | if len(in.GetPagination().GetOrderBy()) == 0 || !(in.GetPagination().GetOrderBy() == "users.name" || 435 | in.GetPagination().GetOrderBy() == "users.email" || 436 | in.GetPagination().GetOrderBy() == "groups.name") { 437 | if in.GetPagination() == nil { 438 | in.Pagination = &users.Pagination{OrderBy: "users.created_at"} 439 | } else { 440 | in.GetPagination().OrderBy = "users.created_at" 441 | } 442 | } 443 | 444 | query += ` ORDER BY ` + in.GetPagination().GetOrderBy() + ` ` + in.GetPagination().GetSort().String() 445 | 446 | if in.GetPagination().GetLimit() > 0 { 447 | query += fmt.Sprintf(` LIMIT $%d OFFSET $%d`, (len(paramQueries) + 1), (len(paramQueries) + 2)) 448 | paramQueries = append(paramQueries, in.GetPagination().GetLimit(), in.GetPagination().GetOffset()) 449 | } 450 | 451 | return query, paramQueries, &paginationResponse, nil 452 | } 453 | -------------------------------------------------------------------------------- /internal/route/route.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | import ( 4 | "database/sql" 5 | "log" 6 | 7 | "github.com/jacky-htg/erp-pkg/db/redis" 8 | "github.com/jacky-htg/erp-proto/go/pb/users" 9 | "github.com/jacky-htg/user-service/internal/service" 10 | "google.golang.org/grpc" 11 | ) 12 | 13 | // GrpcRoute func 14 | func GrpcRoute(grpcServer *grpc.Server, db *sql.DB, log *log.Logger, cache *redis.Cache) { 15 | authServer := service.Auth{Db: db, Cache: cache} 16 | users.RegisterAuthServiceServer(grpcServer, &authServer) 17 | 18 | userServer := service.User{Db: db, Cache: cache} 19 | users.RegisterUserServiceServer(grpcServer, &userServer) 20 | 21 | companyServer := service.Company{Db: db, Cache: cache} 22 | users.RegisterCompanyServiceServer(grpcServer, &companyServer) 23 | 24 | regionServer := service.Region{Db: db, Cache: cache} 25 | users.RegisterRegionServiceServer(grpcServer, ®ionServer) 26 | 27 | branchServer := service.Branch{Db: db, Cache: cache} 28 | users.RegisterBranchServiceServer(grpcServer, &branchServer) 29 | 30 | employeeServer := service.Employee{Db: db, Cache: cache} 31 | users.RegisterEmployeeServiceServer(grpcServer, &employeeServer) 32 | 33 | featureServer := service.Feature{Db: db, Cache: cache} 34 | users.RegisterFeatureServiceServer(grpcServer, &featureServer) 35 | 36 | packageFeatureServer := service.PackageFeature{Db: db, Cache: cache} 37 | users.RegisterPackageFeatureServiceServer(grpcServer, &packageFeatureServer) 38 | 39 | accessServer := service.Access{Db: db, Cache: cache} 40 | users.RegisterAccessServiceServer(grpcServer, &accessServer) 41 | 42 | groupServer := service.Group{Db: db, Cache: cache} 43 | users.RegisterGroupServiceServer(grpcServer, &groupServer) 44 | } 45 | -------------------------------------------------------------------------------- /internal/schema/migrate.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "database/sql" 5 | 6 | "github.com/GuiaBolso/darwin" 7 | ) 8 | 9 | var migrations = []darwin.Migration{ 10 | { 11 | Version: 1, 12 | Description: "Create companies Table", 13 | Script: ` 14 | CREATE TABLE companies ( 15 | id char(36) NOT NULL PRIMARY KEY, 16 | name varchar(100) NOT NULL, 17 | code char(4) NOT NULL UNIQUE, 18 | address varchar NOT NULL, 19 | city varchar(100) NOT NULL, 20 | province varchar(100) NOT NULL, 21 | npwp char(20) NULL, 22 | phone varchar(20) NOT NULL, 23 | pic varchar(100) NOT NULL, 24 | pic_phone varchar(20) NOT NULL, 25 | logo varchar NULL, 26 | package_of_feature_id char(36) NOT NULL, 27 | created_at timestamp NOT NULL DEFAULT NOW(), 28 | updated_at timestamp NOT NULL DEFAULT NOW(), 29 | updated_by char(36) NULL 30 | ); 31 | `, 32 | }, 33 | { 34 | Version: 2, 35 | Description: "Create regions Table", 36 | Script: ` 37 | CREATE TABLE regions ( 38 | id char(36) NOT NULL PRIMARY KEY, 39 | company_id char(36) NOT NULL, 40 | name varchar(100) NOT NULL, 41 | code char(4) NOT NULL, 42 | created_at timestamp NOT NULL DEFAULT NOW(), 43 | created_by char(36) NOT NULL, 44 | updated_at timestamp NOT NULL DEFAULT NOW(), 45 | updated_by char(36) NOT NULL, 46 | UNIQUE(company_id, code), 47 | CONSTRAINT fk_regions_to_companies FOREIGN KEY(company_id) REFERENCES companies(id) 48 | ); 49 | `, 50 | }, 51 | { 52 | Version: 3, 53 | Description: "Create branches Table", 54 | Script: ` 55 | CREATE TABLE branches ( 56 | id char(36) NOT NULL PRIMARY KEY, 57 | company_id char(36) NOT NULL, 58 | name varchar(100) NOT NULL, 59 | code char(4) NOT NULL, 60 | address varchar NOT NULL, 61 | city varchar(100) NOT NULL, 62 | province varchar(100) NOT NULL, 63 | npwp char(20) NULL, 64 | phone varchar(20) NOT NULL, 65 | pic varchar(100) NOT NULL, 66 | pic_phone varchar(20) NOT NULL, 67 | created_at timestamp NOT NULL DEFAULT NOW(), 68 | created_by char(36) NOT NULL, 69 | updated_at timestamp NOT NULL DEFAULT NOW(), 70 | updated_by char(36) NOT NULL, 71 | UNIQUE(company_id, code), 72 | CONSTRAINT fk_branches_to_companies FOREIGN KEY(company_id) REFERENCES companies(id) 73 | ); 74 | `, 75 | }, 76 | { 77 | Version: 4, 78 | Description: "Create branches_regions Table", 79 | Script: ` 80 | CREATE TABLE branches_regions ( 81 | id char(36) NOT NULL PRIMARY KEY, 82 | region_id char(36) NOT NULL, 83 | branch_id char(36) NOT NULL, 84 | created_at timestamp NOT NULL DEFAULT NOW(), 85 | created_by char(36) NOT NULL, 86 | updated_at timestamp NOT NULL DEFAULT NOW(), 87 | updated_by char(36) NOT NULL, 88 | UNIQUE(region_id, branch_id), 89 | CONSTRAINT fk_branches_regions_to_regions FOREIGN KEY(region_id) REFERENCES regions(id) ON DELETE CASCADE, 90 | CONSTRAINT fk_branches_regions_to_branches FOREIGN KEY(branch_id) REFERENCES branches(id) ON DELETE CASCADE 91 | ); 92 | `, 93 | }, 94 | { 95 | Version: 5, 96 | Description: "Create groups Table", 97 | Script: ` 98 | CREATE TABLE groups ( 99 | id char(36) NOT NULL PRIMARY KEY, 100 | company_id char(36) NOT NULL, 101 | name varchar(100) NOT NULL, 102 | is_mutable bool NOT NULL DEFAULT true, 103 | created_at timestamp NOT NULL DEFAULT NOW(), 104 | created_by char(36) NOT NULL, 105 | updated_at timestamp NOT NULL DEFAULT NOW(), 106 | updated_by char(36) NOT NULL, 107 | CONSTRAINT fk_groups_to_companies FOREIGN KEY(company_id) REFERENCES companies(id) 108 | ); 109 | `, 110 | }, 111 | { 112 | Version: 6, 113 | Description: "Create users Table", 114 | Script: ` 115 | CREATE TABLE users ( 116 | id char(36) NOT NULL PRIMARY KEY, 117 | company_id char(36) NOT NULL, 118 | region_id char(36) NULL, 119 | branch_id char(36) NULL, 120 | group_id char(36) NOT NULL, 121 | username varchar(20) NOT NULL UNIQUE, 122 | password varchar NOT NULL, 123 | name varchar(100) NOT NULL, 124 | email varchar(100) NOT NULL UNIQUE, 125 | created_at timestamp NOT NULL DEFAULT NOW(), 126 | updated_at timestamp NOT NULL DEFAULT NOW(), 127 | updated_by char(36) NULL, 128 | CONSTRAINT fk_users_to_companies FOREIGN KEY(company_id) REFERENCES companies(id), 129 | CONSTRAINT fk_users_to_regions FOREIGN KEY(region_id) REFERENCES regions(id), 130 | CONSTRAINT fk_users_to_branches FOREIGN KEY(branch_id) REFERENCES branches(id), 131 | CONSTRAINT fk_users_to_groups FOREIGN KEY(group_id) REFERENCES groups(id) 132 | ); 133 | `, 134 | }, 135 | { 136 | Version: 7, 137 | Description: "Create access Table", 138 | Script: ` 139 | CREATE TABLE access ( 140 | id char(36) NOT NULL PRIMARY KEY, 141 | parent_id char(36) NULL, 142 | name varchar(100) NOT NULL UNIQUE, 143 | route varchar(100) NOT NULL UNIQUE, 144 | level smallint NOT NULL, 145 | created_at timestamp NOT NULL DEFAULT NOW(), 146 | created_by char(36) NOT NULL, 147 | updated_at timestamp NOT NULL DEFAULT NOW(), 148 | updated_by char(36) NOT NULL, 149 | CONSTRAINT fk_access_to_parents FOREIGN KEY(parent_id) REFERENCES access(id) 150 | ); 151 | `, 152 | }, 153 | { 154 | Version: 8, 155 | Description: "Create access_groups Table", 156 | Script: ` 157 | CREATE TABLE access_groups ( 158 | id char(36) NOT NULL PRIMARY KEY, 159 | group_id char(36) NOT NULL, 160 | access_id char(36) NOT NULL, 161 | created_at timestamp NOT NULL DEFAULT NOW(), 162 | created_by char(36) NOT NULL, 163 | updated_at timestamp NOT NULL DEFAULT NOW(), 164 | updated_by char(36) NOT NULL, 165 | UNIQUE(group_id, access_id), 166 | CONSTRAINT fk_access_groups_to_groups FOREIGN KEY(group_id) REFERENCES groups(id) ON DELETE CASCADE, 167 | CONSTRAINT fk_access_groups_to_access FOREIGN KEY(access_id) REFERENCES access(id) ON DELETE CASCADE 168 | ); 169 | `, 170 | }, 171 | { 172 | Version: 9, 173 | Description: "Create employees Table", 174 | Script: ` 175 | CREATE TABLE employees ( 176 | id char(36) NOT NULL PRIMARY KEY, 177 | user_id char(36) NOT NULL UNIQUE, 178 | name varchar(100) NOT NULL, 179 | code char(20) NOT NULL UNIQUE, 180 | address varchar NOT NULL, 181 | city varchar(100) NOT NULL, 182 | province varchar(100) NOT NULL, 183 | jabatan varchar(100) NOT NULL, 184 | created_at timestamp NOT NULL DEFAULT NOW(), 185 | created_by char(36) NOT NULL, 186 | updated_at timestamp NOT NULL DEFAULT NOW(), 187 | updated_by char(36) NOT NULL, 188 | CONSTRAINT fk_employees_to_users FOREIGN KEY(user_id) REFERENCES users(id) 189 | ); 190 | `, 191 | }, 192 | { 193 | Version: 10, 194 | Description: "Create package_features Table", 195 | Script: ` 196 | CREATE TABLE package_features ( 197 | id char(36) NOT NULL PRIMARY KEY, 198 | name varchar(100) NOT NULL UNIQUE, 199 | created_at timestamp NOT NULL DEFAULT NOW(), 200 | updated_at timestamp NOT NULL DEFAULT NOW() 201 | ); 202 | `, 203 | }, 204 | { 205 | Version: 11, 206 | Description: "Create features Table", 207 | Script: ` 208 | CREATE TABLE features ( 209 | id char(36) NOT NULL PRIMARY KEY, 210 | name varchar(100) NOT NULL UNIQUE, 211 | created_at timestamp NOT NULL DEFAULT NOW(), 212 | created_by char(36) NOT NULL, 213 | updated_at timestamp NOT NULL DEFAULT NOW(), 214 | updated_by char(36) NOT NULL 215 | ); 216 | `, 217 | }, 218 | { 219 | Version: 12, 220 | Description: "Create features_package_features Table", 221 | Script: ` 222 | CREATE TABLE features_package_features ( 223 | id char(36) NOT NULL PRIMARY KEY, 224 | package_feature_id char(36) NOT NULL, 225 | feature_id char(36) NOT NULL, 226 | created_at timestamp NOT NULL DEFAULT NOW(), 227 | created_by char(36) NOT NULL, 228 | updated_at timestamp NOT NULL DEFAULT NOW(), 229 | updated_by char(36) NOT NULL, 230 | UNIQUE(package_feature_id, feature_id), 231 | CONSTRAINT fk_features_package_features_to_package_features FOREIGN KEY(package_feature_id) REFERENCES package_features(id) ON DELETE CASCADE, 232 | CONSTRAINT fk_features_package_features_to_features FOREIGN KEY(feature_id) REFERENCES features(id) ON DELETE CASCADE 233 | ); 234 | `, 235 | }, 236 | { 237 | Version: 13, 238 | Description: "Create companies_features Table", 239 | Script: ` 240 | CREATE TABLE companies_features ( 241 | id char(36) NOT NULL PRIMARY KEY, 242 | company_id char(36) NOT NULL, 243 | feature_id char(36) NOT NULL, 244 | created_at timestamp NOT NULL DEFAULT NOW(), 245 | created_by char(36) NOT NULL, 246 | updated_at timestamp NOT NULL DEFAULT NOW(), 247 | updated_by char(36) NOT NULL, 248 | UNIQUE(company_id, feature_id), 249 | CONSTRAINT fk_companies_features_to_companies FOREIGN KEY(company_id) REFERENCES companies(id) ON DELETE CASCADE, 250 | CONSTRAINT fk_companies_features_to_features FOREIGN KEY(feature_id) REFERENCES features(id) ON DELETE CASCADE 251 | ); 252 | `, 253 | }, 254 | { 255 | Version: 14, 256 | Description: "Create request_passwords Table", 257 | Script: ` 258 | CREATE TABLE request_passwords ( 259 | id char(36) NOT NULL PRIMARY KEY, 260 | user_id char(36) NOT NULL, 261 | is_used boolean NOT NULL DEFAULT false, 262 | created_at timestamp NOT NULL DEFAULT NOW(), 263 | CONSTRAINT fk_request_passwords_to_users FOREIGN KEY(user_id) REFERENCES users(id) 264 | ); 265 | `, 266 | }, 267 | } 268 | 269 | // Migrate attempts to bring the schema for db up to date with the migrations 270 | // defined in this package. 271 | func Migrate(db *sql.DB) error { 272 | driver := darwin.NewGenericDriver(db, darwin.PostgresDialect{}) 273 | 274 | d := darwin.New(driver, migrations, nil) 275 | 276 | return d.Migrate() 277 | } 278 | -------------------------------------------------------------------------------- /internal/schema/seed.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "fmt" 7 | 8 | "github.com/google/uuid" 9 | "golang.org/x/crypto/bcrypt" 10 | ) 11 | 12 | func initSeed(ctx context.Context, tx *sql.Tx) error { 13 | packageFeatureID := uuid.New().String() 14 | companyID := uuid.New().String() 15 | userID := uuid.New().String() 16 | groupID := uuid.New().String() 17 | accessID := uuid.New().String() 18 | 19 | // seed package features 20 | stmt, err := tx.PrepareContext(ctx, 21 | `INSERT INTO package_features (id, name) VALUES ($1, 'ALL'), ($2, 'SIMPLE'), ($3, 'CUSTOME')`) 22 | if err != nil { 23 | return err 24 | } 25 | defer stmt.Close() 26 | 27 | _, err = stmt.ExecContext(ctx, packageFeatureID, uuid.New().String(), uuid.New().String()) 28 | if err != nil { 29 | return err 30 | } 31 | 32 | // seed company 33 | query := ` 34 | INSERT INTO companies (id, name, code, address, city, province, phone, pic, pic_phone, package_of_feature_id, updated_by) 35 | VALUES ($1, 'Wiradata Sistem', 'WIRA', 'Pondok Aren', 'Tangerang Selatan', 'Banten', '08122222222', 'Jacky', '08133333333', $2, $3) 36 | ` 37 | 38 | stmt, err = tx.PrepareContext(ctx, query) 39 | if err != nil { 40 | return err 41 | } 42 | defer stmt.Close() 43 | 44 | _, err = stmt.ExecContext(ctx, companyID, packageFeatureID, userID) 45 | if err != nil { 46 | return err 47 | } 48 | 49 | // seed group 50 | query = ` 51 | INSERT INTO groups (id, company_id, is_mutable, name, created_by, updated_by) 52 | VALUES ($1, $2, false, 'Super Admin', $3, $3) 53 | ` 54 | 55 | stmt, err = tx.PrepareContext(ctx, query) 56 | if err != nil { 57 | return err 58 | } 59 | defer stmt.Close() 60 | 61 | _, err = stmt.ExecContext(ctx, groupID, companyID, userID) 62 | if err != nil { 63 | return err 64 | } 65 | 66 | // seed user 67 | password, err := bcrypt.GenerateFromPassword([]byte("1234"), bcrypt.DefaultCost) 68 | if err != nil { 69 | return err 70 | } 71 | 72 | query = ` 73 | INSERT INTO users (id, company_id, group_id, username, name, email, password) 74 | VALUES ($1, $2, $3, 'wira-admin', 'Administrator', 'rijal.asep.nugroho@gmail.com', $4) 75 | ` 76 | 77 | stmt, err = tx.PrepareContext(ctx, query) 78 | if err != nil { 79 | return err 80 | } 81 | defer stmt.Close() 82 | 83 | _, err = stmt.ExecContext(ctx, userID, companyID, groupID, password) 84 | if err != nil { 85 | return err 86 | } 87 | 88 | // seed access 89 | query = ` 90 | INSERT INTO access (id, name, created_by, updated_by) 91 | VALUES ($1, 'root', $2, $2) 92 | ` 93 | 94 | stmt, err = tx.PrepareContext(ctx, query) 95 | if err != nil { 96 | return err 97 | } 98 | defer stmt.Close() 99 | 100 | _, err = stmt.ExecContext(ctx, accessID, userID) 101 | if err != nil { 102 | return err 103 | } 104 | 105 | // seed grant access 106 | query = ` 107 | INSERT INTO access_groups (id, group_id, access_id, created_by, updated_by) 108 | VALUES ($1, $2, $3, $4, $4) 109 | ` 110 | 111 | stmt, err = tx.PrepareContext(ctx, query) 112 | if err != nil { 113 | return err 114 | } 115 | defer stmt.Close() 116 | 117 | _, err = stmt.ExecContext(ctx, uuid.New().String(), groupID, accessID, userID) 118 | if err != nil { 119 | return err 120 | } 121 | 122 | return nil 123 | } 124 | 125 | // seeds is a string constant containing all of the queries needed to get the 126 | // db seeded to a useful state for development. 127 | // 128 | // Using a constant in a .go file is an easy way to ensure the queries are part 129 | // of the compiled executable and avoids pathing issues with the working 130 | // directory. It has the downside that it lacks syntax highlighting and may be 131 | // harder to read for some cases compared to using .sql files. You may also 132 | // consider a combined approach using a tool like packr or go-bindata. 133 | // 134 | // Note that database servers besides PostgreSQL may not support running 135 | // multiple queries as part of the same execution so this single large constant 136 | // may need to be broken up. 137 | 138 | // Seed runs the set of seed-data queries against db. The queries are ran in a 139 | // transaction and rolled back if any fail. 140 | func Seed(db *sql.DB) error { 141 | seeds := []string{} 142 | ctx := context.Background() 143 | 144 | tx, err := db.Begin() 145 | if err != nil { 146 | return err 147 | } 148 | 149 | err = initSeed(ctx, tx) 150 | if err != nil { 151 | tx.Rollback() 152 | fmt.Println("error execute initSeed") 153 | return err 154 | } 155 | 156 | for _, seed := range seeds { 157 | _, err = tx.ExecContext(ctx, seed) 158 | if err != nil { 159 | tx.Rollback() 160 | fmt.Println("error execute seed") 161 | return err 162 | } 163 | } 164 | 165 | return tx.Commit() 166 | } 167 | -------------------------------------------------------------------------------- /internal/service/access.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | 7 | "github.com/jacky-htg/erp-pkg/db/redis" 8 | "github.com/jacky-htg/erp-proto/go/pb/users" 9 | "github.com/jacky-htg/user-service/internal/model" 10 | ) 11 | 12 | // Access struct 13 | type Access struct { 14 | Db *sql.DB 15 | Cache *redis.Cache 16 | users.UnimplementedAccessServiceServer 17 | } 18 | 19 | // List access 20 | func (u *Access) List(ctx context.Context, in *users.MyEmpty) (*users.ListAccessResponse, error) { 21 | var accessModel model.Access 22 | return accessModel.List(ctx, u.Db) 23 | } 24 | -------------------------------------------------------------------------------- /internal/service/auth.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "os" 7 | "time" 8 | 9 | "github.com/jacky-htg/erp-pkg/app" 10 | "github.com/jacky-htg/erp-pkg/db/redis" 11 | "github.com/jacky-htg/erp-pkg/email" 12 | "github.com/jacky-htg/erp-pkg/token" 13 | "github.com/jacky-htg/erp-proto/go/pb/users" 14 | "github.com/jacky-htg/user-service/internal/model" 15 | "github.com/sendgrid/sendgrid-go/helpers/mail" 16 | "google.golang.org/grpc/codes" 17 | "google.golang.org/grpc/status" 18 | ) 19 | 20 | // Auth struct 21 | type Auth struct { 22 | Db *sql.DB 23 | Cache *redis.Cache 24 | users.UnimplementedAuthServiceServer 25 | } 26 | 27 | // Login service 28 | func (u *Auth) Login(ctx context.Context, in *users.LoginRequest) (*users.LoginResponse, error) { 29 | var output users.LoginResponse 30 | if len(in.GetUsername()) == 0 { 31 | return &output, status.Error(codes.InvalidArgument, "Please supply valid username") 32 | } 33 | 34 | if len(in.GetPassword()) == 0 { 35 | return &output, status.Error(codes.InvalidArgument, "Please supply valid password") 36 | } 37 | 38 | var userModel model.User 39 | userModel.Pb.Username = in.GetUsername() 40 | userModel.Password = in.GetPassword() 41 | err := userModel.GetByUserNamePassword(ctx, u.Db) 42 | if err != nil { 43 | return &output, err 44 | } 45 | 46 | output.User = &userModel.Pb 47 | output.Token, err = token.ClaimToken(output.User.GetEmail()) 48 | if err != nil { 49 | return &output, status.Errorf(codes.Internal, "claim token: %v", err) 50 | } 51 | 52 | return &output, nil 53 | } 54 | 55 | // ForgotPassword service 56 | func (u *Auth) ForgotPassword(ctx context.Context, in *users.ForgotPasswordRequest) (*users.Message, error) { 57 | var output users.Message 58 | output.Message = "Failed" 59 | if len(in.GetEmail()) == 0 { 60 | return &output, status.Error(codes.InvalidArgument, "Please supply valid email") 61 | } 62 | 63 | var userModel model.User 64 | userModel.Pb.Email = in.GetEmail() 65 | err := userModel.GetByEmail(ctx, u.Db) 66 | if err != nil { 67 | return &output, err 68 | } 69 | 70 | var requestPasswordModel model.RequestPassword 71 | requestPasswordModel.Pb.UserId = userModel.Pb.GetId() 72 | err = requestPasswordModel.Create(ctx, u.Db) 73 | if err != nil { 74 | return &output, err 75 | } 76 | 77 | // send email link forgot password 78 | from := mail.NewEmail(os.Getenv("SENDGRID_FROM_NAME"), os.Getenv("SENDGRID_FROM_EMAIL")) 79 | p := mail.NewPersonalization() 80 | tos := []*mail.Email{ 81 | mail.NewEmail(userModel.Pb.GetName(), userModel.Pb.GetEmail()), 82 | } 83 | p.AddTos(tos...) 84 | 85 | p.SetDynamicTemplateData("name", userModel.Pb.GetName()) 86 | p.SetDynamicTemplateData("url", os.Getenv("APP_HOST")+"/change-password/"+requestPasswordModel.Pb.GetId()) 87 | p.SetDynamicTemplateData("app_name", os.Getenv("APP_NAME")) 88 | p.SetDynamicTemplateData("cs_email", os.Getenv("CUSTOMERSERVICE_EMAIL")) 89 | p.SetDynamicTemplateData("cs_phone", os.Getenv("CUSTOMERSERVICE_PHONE")) 90 | 91 | err = email.SendMailV3(from, p, os.Getenv("SENDGRID_TEMPLATE_FORGOT_PASSWORD")) 92 | if err != nil { 93 | return &output, status.Errorf(codes.Internal, "send link forgot password: %v", err) 94 | } 95 | 96 | output.Message = "Success" 97 | return &output, nil 98 | } 99 | 100 | // ResetPassword service 101 | func (u *Auth) ResetPassword(ctx context.Context, in *users.ResetPasswordRequest) (*users.Message, error) { 102 | output := users.Message{Message: "Failed"} 103 | 104 | if len(in.GetToken()) == 0 { 105 | return &output, status.Error(codes.InvalidArgument, "Please supply valid token") 106 | } 107 | 108 | if len(in.GetNewPassword()) == 0 { 109 | return &output, status.Error(codes.InvalidArgument, "Please supply valid new password") 110 | } 111 | 112 | if len(in.GetRePassword()) == 0 { 113 | return &output, status.Error(codes.InvalidArgument, "Please supply valid re password") 114 | } 115 | 116 | if in.GetNewPassword() != in.GetRePassword() { 117 | return &output, status.Error(codes.InvalidArgument, "new password not match with re password") 118 | } 119 | 120 | err := checkStrongPassword(in.GetNewPassword()) 121 | if err != nil { 122 | return &output, err 123 | } 124 | 125 | var requestPasswordModel model.RequestPassword 126 | requestPasswordModel.Pb.Id = in.GetToken() 127 | err = requestPasswordModel.Get(ctx, u.Db) 128 | if err != nil { 129 | return &output, err 130 | } 131 | 132 | if requestPasswordModel.Pb.GetIsUsed() { 133 | return &output, status.Error(codes.PermissionDenied, "token has been used") 134 | } 135 | 136 | createdAt, err := time.Parse("2006-01-02T15:04:05.000Z", requestPasswordModel.Pb.GetCreatedAt()) 137 | if err != nil { 138 | return &output, status.Errorf(codes.Internal, "parse timestamp: %v", err) 139 | } 140 | 141 | if time.Now().UTC().After(createdAt.Add(time.Hour * 2 * 24)) { 142 | return &output, status.Error(codes.PermissionDenied, "token has been expired") 143 | } 144 | 145 | var userModel model.User 146 | userModel.Pb.Id = requestPasswordModel.Pb.GetUserId() 147 | userModel.Password = in.GetNewPassword() 148 | tx, err := u.Db.BeginTx(ctx, nil) 149 | if err != nil { 150 | return &output, status.Errorf(codes.Internal, "begin tx: %v", err) 151 | } 152 | 153 | err = userModel.ChangePassword(ctx, tx) 154 | if err != nil { 155 | tx.Rollback() 156 | return &output, err 157 | } 158 | 159 | err = requestPasswordModel.UpdateIsUsed(ctx, tx) 160 | if err != nil { 161 | tx.Rollback() 162 | return &output, err 163 | } 164 | 165 | tx.Commit() 166 | 167 | output.Message = "Success" 168 | 169 | return &output, nil 170 | } 171 | 172 | // ChangePassword service 173 | func (u *Auth) ChangePassword(ctx context.Context, in *users.ChangePasswordRequest) (*users.Message, error) { 174 | var output users.Message 175 | output.Message = "Failed" 176 | 177 | ctx, err := getMetadata(ctx) 178 | if err != nil { 179 | return &output, err 180 | } 181 | 182 | if len(in.GetOldPassword()) == 0 { 183 | return &output, status.Error(codes.InvalidArgument, "Please supply valid current password") 184 | } 185 | 186 | if len(in.GetNewPassword()) == 0 { 187 | return &output, status.Error(codes.InvalidArgument, "Please supply valid new password") 188 | } 189 | 190 | if len(in.GetRePassword()) == 0 { 191 | return &output, status.Error(codes.InvalidArgument, "Please supply valid re password") 192 | } 193 | 194 | if in.GetNewPassword() != in.GetRePassword() { 195 | return &output, status.Error(codes.InvalidArgument, "new password not match with re password") 196 | } 197 | 198 | err = checkStrongPassword(in.GetNewPassword()) 199 | if err != nil { 200 | return &output, err 201 | } 202 | 203 | var userModel model.User 204 | userModel.Pb.Id = ctx.Value(app.Ctx("userID")).(string) 205 | userModel.Password = in.GetOldPassword() 206 | err = userModel.GetByPassword(ctx, u.Db) 207 | if err != nil { 208 | return &output, err 209 | } 210 | 211 | userModel.Password = in.GetNewPassword() 212 | tx, err := u.Db.BeginTx(ctx, nil) 213 | if err != nil { 214 | return &output, status.Errorf(codes.Internal, "begin tx: %v", err) 215 | } 216 | 217 | err = userModel.ChangePassword(ctx, tx) 218 | if err != nil { 219 | tx.Rollback() 220 | return &output, err 221 | } 222 | 223 | tx.Commit() 224 | output.Message = "success" 225 | 226 | return &output, nil 227 | } 228 | 229 | // IsAuth service 230 | func (u *Auth) IsAuth(ctx context.Context, in *users.MyString) (*users.MyBoolean, error) { 231 | output := users.MyBoolean{Boolean: false} 232 | 233 | ctx, err := getMetadata(ctx) 234 | if err != nil { 235 | return &output, err 236 | } 237 | 238 | if len(in.GetString_()) == 0 { 239 | return &output, status.Error(codes.InvalidArgument, "Please supply valid access") 240 | } 241 | 242 | var userModel model.User 243 | userModel.Pb.Id = ctx.Value(app.Ctx("userID")).(string) 244 | err = userModel.IsAuth(ctx, u.Db, in.GetString_()) 245 | if err != nil { 246 | return &output, err 247 | } 248 | 249 | output.Boolean = true 250 | return &output, nil 251 | } 252 | -------------------------------------------------------------------------------- /internal/service/branch.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | 7 | "github.com/jacky-htg/erp-pkg/app" 8 | "github.com/jacky-htg/erp-pkg/db/redis" 9 | "github.com/jacky-htg/erp-proto/go/pb/users" 10 | "github.com/jacky-htg/user-service/internal/model" 11 | "google.golang.org/grpc/codes" 12 | "google.golang.org/grpc/status" 13 | ) 14 | 15 | // Branch struct 16 | type Branch struct { 17 | Db *sql.DB 18 | Cache *redis.Cache 19 | users.UnimplementedBranchServiceServer 20 | } 21 | 22 | // Create new branch 23 | func (u *Branch) Create(ctx context.Context, in *users.Branch) (*users.Branch, error) { 24 | var output users.Branch 25 | var err error 26 | var branchModel model.Branch 27 | 28 | // basic validation 29 | { 30 | if len(in.GetName()) == 0 { 31 | return &output, status.Error(codes.InvalidArgument, "Please supply valid name") 32 | } 33 | 34 | if len(in.GetAddress()) == 0 { 35 | return &output, status.Error(codes.InvalidArgument, "Please supply valid company address") 36 | } 37 | 38 | if len(in.GetCity()) == 0 { 39 | return &output, status.Error(codes.InvalidArgument, "Please supply valid company city") 40 | } 41 | 42 | if len(in.GetProvince()) == 0 { 43 | return &output, status.Error(codes.InvalidArgument, "Please supply valid company province") 44 | } 45 | 46 | if len(in.GetPhone()) == 0 { 47 | return &output, status.Error(codes.InvalidArgument, "Please supply valid company phone") 48 | } 49 | 50 | if len(in.GetPic()) == 0 { 51 | return &output, status.Error(codes.InvalidArgument, "Please supply valid company pic") 52 | } 53 | 54 | if len(in.GetPicPhone()) == 0 { 55 | return &output, status.Error(codes.InvalidArgument, "Please supply valid company pic phone") 56 | } 57 | } 58 | 59 | ctx, err = getMetadata(ctx) 60 | if err != nil { 61 | return &output, err 62 | } 63 | 64 | // code validation 65 | { 66 | if len(in.GetCode()) == 0 { 67 | return &output, status.Error(codes.InvalidArgument, "Please supply valid code") 68 | } 69 | 70 | branchModel = model.Branch{} 71 | branchModel.Pb.Code = in.GetCode() 72 | err = branchModel.GetByCode(ctx, u.Db) 73 | if err != nil { 74 | if st, ok := status.FromError(err); ok && st.Code() != codes.NotFound { 75 | return &output, err 76 | } 77 | } 78 | 79 | if len(branchModel.Pb.GetId()) > 0 { 80 | return &output, status.Error(codes.AlreadyExists, "code must be unique") 81 | } 82 | } 83 | 84 | // company validation 85 | { 86 | if len(in.GetCompanyId()) > 0 && in.GetCompanyId() != ctx.Value(app.Ctx("companyID")).(string) { 87 | return &output, status.Error(codes.PermissionDenied, "Please supply valid company id") 88 | } 89 | in.CompanyId = ctx.Value(app.Ctx("companyID")).(string) 90 | } 91 | 92 | // get user login 93 | var userLogin model.User 94 | userLogin.Pb.Id = ctx.Value(app.Ctx("userID")).(string) 95 | err = userLogin.Get(ctx, u.Db) 96 | if err != nil { 97 | return &output, err 98 | } 99 | 100 | if len(userLogin.Pb.GetBranchId()) > 0 { 101 | return &output, status.Error(codes.PermissionDenied, "Need user region/company to add new branch") 102 | } 103 | 104 | // region validation 105 | if len(userLogin.Pb.GetRegionId()) == 0 { 106 | if len(in.GetRegionId()) == 0 { 107 | return &output, status.Error(codes.InvalidArgument, "Please supply valid region") 108 | } 109 | 110 | regionModel := model.Region{} 111 | regionModel.Pb.Id = in.GetRegionId() 112 | err = regionModel.Get(ctx, u.Db) 113 | if err != nil { 114 | return &output, err 115 | } 116 | 117 | if regionModel.Pb.GetCompanyId() != userLogin.Pb.GetCompanyId() { 118 | return &output, status.Error(codes.PermissionDenied, "its not your region") 119 | } 120 | } else { 121 | if len(in.GetRegionId()) > 0 && in.GetRegionId() != userLogin.Pb.GetRegionId() { 122 | return &output, status.Error(codes.PermissionDenied, "its not your region") 123 | } 124 | 125 | if (len(in.GetRegionId())) == 0 { 126 | in.RegionId = userLogin.Pb.GetRegionId() 127 | } 128 | } 129 | 130 | branchModel = model.Branch{} 131 | branchModel.Pb = users.Branch{ 132 | Code: in.GetCode(), 133 | CompanyId: in.GetCompanyId(), 134 | Name: in.GetName(), 135 | Address: in.GetAddress(), 136 | City: in.GetCity(), 137 | Npwp: in.GetNpwp(), 138 | Phone: in.GetPhone(), 139 | Pic: in.GetPic(), 140 | PicPhone: in.GetPicPhone(), 141 | Province: in.GetProvince(), 142 | RegionId: in.GetRegionId(), 143 | } 144 | tx, err := u.Db.BeginTx(ctx, nil) 145 | if err != nil { 146 | return &output, status.Errorf(codes.Internal, "begin tx: %v", err) 147 | } 148 | 149 | err = branchModel.Create(ctx, u.Db, tx) 150 | if err != nil { 151 | tx.Rollback() 152 | return &output, err 153 | } 154 | 155 | tx.Commit() 156 | 157 | return &branchModel.Pb, nil 158 | } 159 | 160 | // Update branch 161 | func (u *Branch) Update(ctx context.Context, in *users.Branch) (*users.Branch, error) { 162 | var output users.Branch 163 | var err error 164 | var branchModel model.Branch 165 | 166 | // basic validation 167 | { 168 | if len(in.GetId()) == 0 { 169 | return &output, status.Error(codes.InvalidArgument, "Please supply valid id") 170 | } 171 | branchModel.Pb.Id = in.GetId() 172 | } 173 | 174 | ctx, err = getMetadata(ctx) 175 | if err != nil { 176 | return &output, err 177 | } 178 | 179 | // get user login 180 | var userLogin model.User 181 | userLogin.Pb.Id = ctx.Value(app.Ctx("userID")).(string) 182 | err = userLogin.Get(ctx, u.Db) 183 | if err != nil { 184 | return &output, err 185 | } 186 | 187 | if len(userLogin.Pb.GetBranchId()) > 0 && userLogin.Pb.GetBranchId() != in.GetId() { 188 | return &output, status.Error(codes.Unauthenticated, "its not your branch") 189 | } 190 | 191 | err = branchModel.Get(ctx, u.Db) 192 | if err != nil { 193 | return &output, err 194 | } 195 | 196 | if userLogin.Pb.GetCompanyId() != branchModel.Pb.GetCompanyId() { 197 | return &output, status.Error(codes.Unauthenticated, "its not your company") 198 | } 199 | 200 | if len(userLogin.Pb.GetRegionId()) > 0 && userLogin.Pb.GetRegionId() != branchModel.Pb.GetRegionId() { 201 | return &output, status.Error(codes.Unauthenticated, "its not your region") 202 | } 203 | 204 | if len(userLogin.Pb.GetRegionId()) == 0 && 205 | len(in.GetRegionId()) > 0 && 206 | branchModel.Pb.GetRegionId() != in.GetRegionId() { 207 | 208 | regionModel := model.Region{} 209 | regionModel.Pb.Id = in.GetRegionId() 210 | err = regionModel.Get(ctx, u.Db) 211 | if err != nil { 212 | return &output, err 213 | } 214 | branchModel.UpdateRegion = true 215 | branchModel.Pb.RegionId = in.GetRegionId() 216 | } 217 | 218 | if len(in.GetName()) > 0 { 219 | branchModel.Pb.Name = in.GetName() 220 | } 221 | 222 | if len(in.GetAddress()) == 0 { 223 | branchModel.Pb.Address = in.GetAddress() 224 | } 225 | 226 | if len(in.GetCity()) == 0 { 227 | branchModel.Pb.City = in.GetCity() 228 | } 229 | 230 | if len(in.GetProvince()) == 0 { 231 | branchModel.Pb.Province = in.GetProvince() 232 | } 233 | 234 | if len(in.GetPhone()) == 0 { 235 | branchModel.Pb.Phone = in.GetPhone() 236 | } 237 | 238 | if len(in.GetPic()) == 0 { 239 | branchModel.Pb.Pic = in.GetPic() 240 | } 241 | 242 | if len(in.GetPicPhone()) == 0 { 243 | branchModel.Pb.PicPhone = in.GetPicPhone() 244 | } 245 | 246 | tx, err := u.Db.BeginTx(ctx, nil) 247 | if err != nil { 248 | return &output, status.Errorf(codes.Internal, "begin tx: %v", err) 249 | } 250 | 251 | err = branchModel.Update(ctx, u.Db, tx) 252 | if err != nil { 253 | tx.Rollback() 254 | return &output, err 255 | } 256 | 257 | tx.Commit() 258 | 259 | return &branchModel.Pb, nil 260 | } 261 | 262 | // View branch 263 | func (u *Branch) View(ctx context.Context, in *users.Id) (*users.Branch, error) { 264 | var output users.Branch 265 | var err error 266 | var branchModel model.Branch 267 | 268 | // basic validation 269 | { 270 | if len(in.GetId()) == 0 { 271 | return &output, status.Error(codes.InvalidArgument, "Please supply valid id") 272 | } 273 | branchModel.Pb.Id = in.GetId() 274 | } 275 | 276 | ctx, err = getMetadata(ctx) 277 | if err != nil { 278 | return &output, err 279 | } 280 | 281 | // get user login 282 | var userLogin model.User 283 | userLogin.Pb.Id = ctx.Value(app.Ctx("userID")).(string) 284 | err = userLogin.Get(ctx, u.Db) 285 | if err != nil { 286 | return &output, err 287 | } 288 | 289 | err = branchModel.Get(ctx, u.Db) 290 | if err != nil { 291 | return &output, err 292 | } 293 | 294 | if userLogin.Pb.GetCompanyId() != branchModel.Pb.GetCompanyId() { 295 | return &output, status.Error(codes.Unauthenticated, "its not your company") 296 | } 297 | 298 | if len(userLogin.Pb.GetRegionId()) > 0 && userLogin.Pb.GetRegionId() != branchModel.Pb.GetRegionId() { 299 | return &output, status.Error(codes.Unauthenticated, "its not your region") 300 | } 301 | 302 | if len(userLogin.Pb.GetBranchId()) > 0 && userLogin.Pb.GetBranchId() != branchModel.Pb.GetId() { 303 | return &output, status.Error(codes.Unauthenticated, "its not your branch") 304 | } 305 | 306 | return &branchModel.Pb, err 307 | } 308 | 309 | // Delete branch 310 | func (u *Branch) Delete(ctx context.Context, in *users.Id) (*users.MyBoolean, error) { 311 | var output users.MyBoolean 312 | output.Boolean = false 313 | var err error 314 | var branchModel model.Branch 315 | 316 | // basic validation 317 | { 318 | if len(in.GetId()) == 0 { 319 | return &output, status.Error(codes.InvalidArgument, "Please supply valid id") 320 | } 321 | branchModel.Pb.Id = in.GetId() 322 | } 323 | 324 | ctx, err = getMetadata(ctx) 325 | if err != nil { 326 | return &output, err 327 | } 328 | 329 | // get user login 330 | var userLogin model.User 331 | userLogin.Pb.Id = ctx.Value(app.Ctx("userID")).(string) 332 | err = userLogin.Get(ctx, u.Db) 333 | if err != nil { 334 | return &output, err 335 | } 336 | 337 | err = branchModel.Get(ctx, u.Db) 338 | if err != nil { 339 | return &output, err 340 | } 341 | 342 | if userLogin.Pb.GetCompanyId() != branchModel.Pb.GetCompanyId() { 343 | return &output, status.Error(codes.Unauthenticated, "its not your company") 344 | } 345 | 346 | if len(userLogin.Pb.GetRegionId()) > 0 && userLogin.Pb.GetRegionId() != branchModel.Pb.GetRegionId() { 347 | return &output, status.Error(codes.Unauthenticated, "its not your region") 348 | } 349 | 350 | err = branchModel.Delete(ctx, u.Db) 351 | if err != nil { 352 | return &output, err 353 | } 354 | 355 | output.Boolean = true 356 | return &output, err 357 | } 358 | 359 | // List branches 360 | func (u *Branch) List(in *users.ListBranchRequest, stream users.BranchService_ListServer) error { 361 | ctx := stream.Context() 362 | ctx, err := getMetadata(ctx) 363 | if err != nil { 364 | return err 365 | } 366 | 367 | // get user login 368 | var userLogin model.User 369 | userLogin.Pb.Id = ctx.Value(app.Ctx("userID")).(string) 370 | err = userLogin.Get(ctx, u.Db) 371 | if err != nil { 372 | return err 373 | } 374 | 375 | if len(in.GetRegionId()) > 0 { 376 | regionModel := model.Region{} 377 | regionModel.Pb.Id = in.GetRegionId() 378 | err = regionModel.Get(ctx, u.Db) 379 | if err != nil { 380 | return err 381 | } 382 | 383 | if regionModel.Pb.GetCompanyId() != ctx.Value(app.Ctx("companyID")).(string) { 384 | return status.Error(codes.InvalidArgument, "its not your company") 385 | } 386 | } else { 387 | if len(userLogin.Pb.GetRegionId()) > 0 { 388 | in.RegionId = userLogin.Pb.GetRegionId() 389 | } 390 | } 391 | 392 | var branchModel model.Branch 393 | query, paramQueries, paginationResponse, err := branchModel.ListQuery(ctx, u.Db, in, userLogin.Pb.GetBranchId()) 394 | 395 | rows, err := u.Db.QueryContext(ctx, query, paramQueries...) 396 | if err != nil { 397 | return status.Error(codes.Internal, err.Error()) 398 | } 399 | defer rows.Close() 400 | paginationResponse.RegionId = in.GetRegionId() 401 | paginationResponse.Pagination = in.GetPagination() 402 | 403 | for rows.Next() { 404 | err := contextError(ctx) 405 | if err != nil { 406 | return err 407 | } 408 | 409 | var pbBranch users.Branch 410 | var npwp sql.NullString 411 | err = rows.Scan( 412 | &pbBranch.Id, &pbBranch.CompanyId, &pbBranch.RegionId, &pbBranch.RegionName, &pbBranch.Name, &pbBranch.Code, &pbBranch.Address, 413 | &pbBranch.City, &pbBranch.Province, &npwp, &pbBranch.Phone, &pbBranch.Pic, &pbBranch.PicPhone, 414 | ) 415 | if err != nil { 416 | return err 417 | } 418 | 419 | pbBranch.Npwp = npwp.String 420 | 421 | res := &users.ListBranchResponse{ 422 | Pagination: paginationResponse, 423 | Branch: &pbBranch, 424 | } 425 | 426 | err = stream.Send(res) 427 | if err != nil { 428 | return status.Errorf(codes.Unknown, "cannot send stream response: %v", err) 429 | } 430 | } 431 | 432 | return nil 433 | } 434 | -------------------------------------------------------------------------------- /internal/service/company.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "os" 7 | "regexp" 8 | 9 | "github.com/jacky-htg/erp-pkg/app" 10 | "github.com/jacky-htg/erp-pkg/db/redis" 11 | "github.com/jacky-htg/erp-pkg/email" 12 | "github.com/jacky-htg/erp-proto/go/pb/users" 13 | "github.com/jacky-htg/user-service/internal/model" 14 | "github.com/sendgrid/sendgrid-go/helpers/mail" 15 | "google.golang.org/grpc/codes" 16 | "google.golang.org/grpc/status" 17 | ) 18 | 19 | // Company struct 20 | type Company struct { 21 | Db *sql.DB 22 | Cache *redis.Cache 23 | users.UnimplementedCompanyServiceServer 24 | } 25 | 26 | // Registration new company 27 | func (u *Company) Registration(ctx context.Context, in *users.CompanyRegistration) (*users.CompanyRegistration, error) { 28 | var output users.CompanyRegistration 29 | var err error 30 | 31 | // validate company request 32 | { 33 | if len(in.GetCompany().GetName()) == 0 { 34 | return &output, status.Error(codes.InvalidArgument, "Please supply valid company name") 35 | } 36 | 37 | if len(in.GetCompany().GetCode()) == 0 { 38 | return &output, status.Error(codes.InvalidArgument, "Please supply valid company code") 39 | } 40 | 41 | if len(in.GetCompany().GetAddress()) == 0 { 42 | return &output, status.Error(codes.InvalidArgument, "Please supply valid company address") 43 | } 44 | 45 | if len(in.GetCompany().GetCity()) == 0 { 46 | return &output, status.Error(codes.InvalidArgument, "Please supply valid company city") 47 | } 48 | 49 | if len(in.GetCompany().GetProvince()) == 0 { 50 | return &output, status.Error(codes.InvalidArgument, "Please supply valid company province") 51 | } 52 | 53 | if len(in.GetCompany().GetPhone()) == 0 { 54 | return &output, status.Error(codes.InvalidArgument, "Please supply valid company phone") 55 | } 56 | 57 | if len(in.GetCompany().GetPic()) == 0 { 58 | return &output, status.Error(codes.InvalidArgument, "Please supply valid company pic") 59 | } 60 | 61 | if len(in.GetCompany().GetPicPhone()) == 0 { 62 | return &output, status.Error(codes.InvalidArgument, "Please supply valid company pic phone") 63 | } 64 | 65 | // company code validation 66 | { 67 | companyModel := model.Company{} 68 | companyModel.Pb.Code = in.GetCompany().GetCode() 69 | err = companyModel.GetByCode(ctx, u.Db) 70 | if err != nil { 71 | if st, ok := status.FromError(err); ok && st.Code() != codes.NotFound { 72 | return &output, err 73 | } 74 | } 75 | 76 | if len(companyModel.Pb.GetId()) > 0 { 77 | return &output, status.Error(codes.AlreadyExists, "code must be unique") 78 | } 79 | } 80 | 81 | switch in.GetCompany().GetPackageOfFeature().String() { 82 | case "ALL": 83 | var featurePackageModel model.FeaturePackage 84 | featurePackageModel.Pb.Name = in.GetCompany().GetPackageOfFeature() 85 | err = featurePackageModel.GetByName(ctx, u.Db) 86 | if err != nil { 87 | return &output, err 88 | } 89 | 90 | in.GetCompany().Features = featurePackageModel.Pb.Features 91 | case "SIMPLE": 92 | var featurePackageModel model.FeaturePackage 93 | featurePackageModel.Pb.Name = in.GetCompany().GetPackageOfFeature() 94 | err = featurePackageModel.GetByName(ctx, u.Db) 95 | if err != nil { 96 | return &output, err 97 | } 98 | 99 | in.GetCompany().Features = featurePackageModel.Pb.Features 100 | case "CUSTOME": 101 | if len(in.GetCompany().GetFeatures()) == 0 { 102 | return &output, status.Error(codes.InvalidArgument, "Please supply valid company features") 103 | } 104 | 105 | for _, feature := range in.GetCompany().GetFeatures() { 106 | var featureModel model.Feature 107 | featureModel.Pb.Id = feature.GetId() 108 | err = featureModel.Get(ctx, u.Db) 109 | if err != nil { 110 | return &output, err 111 | } 112 | } 113 | } 114 | } 115 | 116 | // user validation 117 | { 118 | 119 | if len(in.GetUser().GetEmail()) == 0 { 120 | return &output, status.Error(codes.InvalidArgument, "Please supply valid email") 121 | } 122 | 123 | if len(in.GetUser().GetName()) == 0 { 124 | return &output, status.Error(codes.InvalidArgument, "Please supply valid name") 125 | } 126 | 127 | // username validation 128 | { 129 | if len(in.GetUser().GetUsername()) == 0 { 130 | return &output, status.Error(codes.InvalidArgument, "Please supply valid username") 131 | } 132 | 133 | if len(in.GetUser().GetUsername()) < 8 { 134 | return &output, status.Error(codes.InvalidArgument, "username min 8 character") 135 | } 136 | 137 | userModel := model.User{} 138 | userModel.Pb.Username = in.GetUser().GetUsername() 139 | err = userModel.GetByUsername(ctx, u.Db) 140 | if err != nil { 141 | if st, ok := status.FromError(err); ok && st.Code() != codes.NotFound { 142 | return &output, err 143 | } 144 | } 145 | 146 | if len(userModel.Pb.GetId()) > 0 { 147 | return &output, status.Error(codes.AlreadyExists, "username must be unique") 148 | } 149 | } 150 | 151 | // email validation 152 | { 153 | var emailRegex = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$") 154 | if valid := func(e string) bool { 155 | if len(e) < 3 && len(e) > 254 { 156 | return false 157 | } 158 | return emailRegex.MatchString(e) 159 | }(in.GetUser().GetEmail()); !valid { 160 | return &output, status.Error(codes.InvalidArgument, "Please supply valid email") 161 | } 162 | 163 | userModel := model.User{} 164 | userModel.Pb.Email = in.GetUser().GetEmail() 165 | err = userModel.GetByEmail(ctx, u.Db) 166 | if err != nil { 167 | if st, ok := status.FromError(err); ok && st.Code() != codes.NotFound { 168 | return &output, err 169 | } 170 | } 171 | 172 | if len(userModel.Pb.GetId()) > 0 { 173 | return &output, status.Error(codes.AlreadyExists, "email must be unique") 174 | } 175 | } 176 | } 177 | 178 | companyRegisterModel := model.CompanyRegister{} 179 | companyRegisterModel.Pb = users.CompanyRegistration{ 180 | Company: in.GetCompany(), 181 | User: in.GetUser(), 182 | } 183 | companyRegisterModel.Password = generateRandomPassword() 184 | tx, err := u.Db.BeginTx(ctx, nil) 185 | if err != nil { 186 | return &output, status.Errorf(codes.Internal, "begin tx: %v", err) 187 | } 188 | 189 | err = companyRegisterModel.Registration(ctx, u.Db, tx) 190 | if err != nil { 191 | tx.Rollback() 192 | return &output, err 193 | } 194 | 195 | tx.Commit() 196 | 197 | // TODO : currenly we accept logo url, next we should accept logo file and process to upload log 198 | 199 | // send email registration info 200 | from := mail.NewEmail(os.Getenv("SENDGRID_FROM_NAME"), os.Getenv("SENDGRID_FROM_EMAIL")) 201 | p := mail.NewPersonalization() 202 | tos := []*mail.Email{ 203 | mail.NewEmail(companyRegisterModel.Pb.GetUser().GetName(), companyRegisterModel.Pb.GetUser().GetEmail()), 204 | } 205 | p.AddTos(tos...) 206 | 207 | p.SetDynamicTemplateData("name", companyRegisterModel.Pb.GetUser().GetName()) 208 | p.SetDynamicTemplateData("username", companyRegisterModel.Pb.GetUser().GetUsername()) 209 | p.SetDynamicTemplateData("password", companyRegisterModel.Password) 210 | p.SetDynamicTemplateData("app_name", os.Getenv("APP_NAME")) 211 | p.SetDynamicTemplateData("cs_email", os.Getenv("CUSTOMERSERVICE_EMAIL")) 212 | p.SetDynamicTemplateData("cs_phone", os.Getenv("CUSTOMERSERVICE_PHONE")) 213 | 214 | err = email.SendMailV3(from, p, os.Getenv("SENDGRID_TEMPLATE_REGISTRATION")) 215 | if err != nil { 216 | return &output, status.Errorf(codes.Internal, "send registration email: %v", err) 217 | } 218 | 219 | return &companyRegisterModel.Pb, err 220 | } 221 | 222 | // Update Company 223 | func (u *Company) Update(ctx context.Context, in *users.Company) (*users.Company, error) { 224 | var output users.Company 225 | var err error 226 | var companyModel model.Company 227 | 228 | // basic validation 229 | { 230 | if len(in.GetId()) == 0 { 231 | return &output, status.Error(codes.InvalidArgument, "Please supply valid id") 232 | } 233 | companyModel.Pb.Id = in.GetId() 234 | } 235 | 236 | ctx, err = getMetadata(ctx) 237 | if err != nil { 238 | return &output, err 239 | } 240 | 241 | // get user login 242 | var userLogin model.User 243 | userLogin.Pb.Id = ctx.Value(app.Ctx("userID")).(string) 244 | err = userLogin.Get(ctx, u.Db) 245 | if err != nil { 246 | return &output, err 247 | } 248 | 249 | if userLogin.Pb.GetCompanyId() != in.GetId() { 250 | return &output, status.Error(codes.Unauthenticated, "its not your company") 251 | } 252 | 253 | if len(userLogin.Pb.GetBranchId()) > 0 || len(userLogin.Pb.GetRegionId()) > 0 { 254 | return &output, status.Error(codes.Unauthenticated, "only user company can update the company") 255 | } 256 | 257 | err = companyModel.Get(ctx, u.Db) 258 | if err != nil { 259 | return &output, err 260 | } 261 | 262 | if len(in.GetName()) > 0 { 263 | companyModel.Pb.Name = in.GetName() 264 | } 265 | 266 | if len(in.GetAddress()) > 0 { 267 | companyModel.Pb.Address = in.GetAddress() 268 | } 269 | 270 | if len(in.GetCity()) > 0 { 271 | companyModel.Pb.City = in.GetCity() 272 | } 273 | 274 | if len(in.GetProvince()) > 0 { 275 | companyModel.Pb.Province = in.GetProvince() 276 | } 277 | 278 | if len(in.GetPhone()) > 0 { 279 | companyModel.Pb.Phone = in.GetPhone() 280 | } 281 | 282 | if len(in.GetPic()) > 0 { 283 | companyModel.Pb.Pic = in.GetPic() 284 | } 285 | 286 | if len(in.GetPicPhone()) > 0 { 287 | companyModel.Pb.PicPhone = in.GetPicPhone() 288 | } 289 | 290 | if in.GetPackageOfFeature() != companyModel.Pb.GetPackageOfFeature() { 291 | companyModel.UpdateFeatures = true 292 | switch in.GetPackageOfFeature().String() { 293 | case "ALL": 294 | var featurePackageModel model.FeaturePackage 295 | featurePackageModel.Pb.Name = in.GetPackageOfFeature() 296 | err = featurePackageModel.GetByName(ctx, u.Db) 297 | if err != nil { 298 | return &output, err 299 | } 300 | 301 | companyModel.Pb.Features = featurePackageModel.Pb.Features 302 | case "SIMPLE": 303 | var featurePackageModel model.FeaturePackage 304 | featurePackageModel.Pb.Name = in.GetPackageOfFeature() 305 | err = featurePackageModel.GetByName(ctx, u.Db) 306 | if err != nil { 307 | return &output, err 308 | } 309 | 310 | companyModel.Pb.Features = featurePackageModel.Pb.Features 311 | case "CUSTOME": 312 | if len(in.GetFeatures()) == 0 { 313 | return &output, status.Error(codes.InvalidArgument, "Please supply valid company features") 314 | } 315 | 316 | for _, feature := range in.GetFeatures() { 317 | var featureModel model.Feature 318 | featureModel.Pb.Id = feature.GetId() 319 | err = featureModel.Get(ctx, u.Db) 320 | if err != nil { 321 | return &output, err 322 | } 323 | } 324 | 325 | companyModel.Pb.Features = in.GetFeatures() 326 | } 327 | companyModel.Pb.PackageOfFeature = in.GetPackageOfFeature() 328 | } else { 329 | if in.GetPackageOfFeature() == companyModel.Pb.GetPackageOfFeature() && companyModel.Pb.GetPackageOfFeature().String() == "CUSTOME" { 330 | if len(in.GetFeatures()) > 0 { 331 | companyModel.UpdateFeatures = true 332 | for _, feature := range in.GetFeatures() { 333 | var featureModel model.Feature 334 | featureModel.Pb.Id = feature.GetId() 335 | err = featureModel.Get(ctx, u.Db) 336 | if err != nil { 337 | return &output, err 338 | } 339 | } 340 | 341 | companyModel.Pb.Features = in.GetFeatures() 342 | } 343 | } 344 | } 345 | 346 | tx, err := u.Db.BeginTx(ctx, nil) 347 | if err != nil { 348 | return &output, status.Errorf(codes.Internal, "begin tx: %v", err) 349 | } 350 | 351 | err = companyModel.Update(ctx, u.Db, tx) 352 | if err != nil { 353 | tx.Rollback() 354 | return &output, err 355 | } 356 | 357 | tx.Commit() 358 | 359 | return &companyModel.Pb, nil 360 | } 361 | 362 | // View Company 363 | func (u *Company) View(ctx context.Context, in *users.Id) (*users.Company, error) { 364 | var output users.Company 365 | var err error 366 | var companyModel model.Company 367 | 368 | // basic validation 369 | { 370 | if len(in.GetId()) == 0 { 371 | return &output, status.Error(codes.InvalidArgument, "Please supply valid id") 372 | } 373 | companyModel.Pb.Id = in.GetId() 374 | } 375 | 376 | ctx, err = getMetadata(ctx) 377 | if err != nil { 378 | return &output, err 379 | } 380 | 381 | // get user login 382 | var userLogin model.User 383 | userLogin.Pb.Id = ctx.Value(app.Ctx("userID")).(string) 384 | err = userLogin.Get(ctx, u.Db) 385 | if err != nil { 386 | return &output, err 387 | } 388 | 389 | if userLogin.Pb.GetCompanyId() != in.GetId() { 390 | return &output, status.Error(codes.Unauthenticated, "its not your company") 391 | } 392 | 393 | err = companyModel.Get(ctx, u.Db) 394 | if err != nil { 395 | return &output, err 396 | } 397 | 398 | return &companyModel.Pb, err 399 | } 400 | -------------------------------------------------------------------------------- /internal/service/employee.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | 7 | "github.com/jacky-htg/erp-pkg/app" 8 | "github.com/jacky-htg/erp-pkg/db/redis" 9 | "github.com/jacky-htg/erp-proto/go/pb/users" 10 | "github.com/jacky-htg/user-service/internal/model" 11 | "google.golang.org/grpc/codes" 12 | "google.golang.org/grpc/status" 13 | ) 14 | 15 | // Employee struct 16 | type Employee struct { 17 | Db *sql.DB 18 | Cache *redis.Cache 19 | users.UnimplementedEmployeeServiceServer 20 | } 21 | 22 | // Create Employee 23 | func (u *Employee) Create(ctx context.Context, in *users.Employee) (*users.Employee, error) { 24 | var output users.Employee 25 | var err error 26 | var employeeModel model.Employee 27 | 28 | // basic validation 29 | { 30 | if len(in.GetName()) == 0 { 31 | return &output, status.Error(codes.InvalidArgument, "Please supply valid name") 32 | } 33 | 34 | if len(in.GetAddress()) == 0 { 35 | return &output, status.Error(codes.InvalidArgument, "Please supply valid address") 36 | } 37 | 38 | if len(in.GetCity()) == 0 { 39 | return &output, status.Error(codes.InvalidArgument, "Please supply valid city") 40 | } 41 | 42 | if len(in.GetProvince()) == 0 { 43 | return &output, status.Error(codes.InvalidArgument, "Please supply valid province") 44 | } 45 | 46 | if len(in.GetJabatan()) == 0 { 47 | return &output, status.Error(codes.InvalidArgument, "Please supply valid jabatan") 48 | } 49 | } 50 | 51 | // code validation 52 | { 53 | if len(in.GetCode()) == 0 { 54 | return &output, status.Error(codes.InvalidArgument, "Please supply valid code") 55 | } 56 | 57 | employeeModel = model.Employee{} 58 | employeeModel.Pb.Code = in.GetCode() 59 | err = employeeModel.GetByCode(ctx, u.Db) 60 | if err != nil { 61 | if st, ok := status.FromError(err); ok && st.Code() != codes.NotFound { 62 | return &output, err 63 | } 64 | } 65 | 66 | if len(employeeModel.Pb.GetId()) > 0 { 67 | return &output, status.Error(codes.AlreadyExists, "code must be unique") 68 | } 69 | } 70 | 71 | ctx, err = getMetadata(ctx) 72 | if err != nil { 73 | return &output, err 74 | } 75 | 76 | // get user login 77 | var userLogin model.User 78 | userLogin.Pb.Id = ctx.Value(app.Ctx("userID")).(string) 79 | err = userLogin.Get(ctx, u.Db) 80 | if err != nil { 81 | return &output, err 82 | } 83 | 84 | // user validation 85 | { 86 | if len(in.GetUser().GetId()) == 0 { 87 | return &output, status.Error(codes.InvalidArgument, "Please supply valid user_id") 88 | } 89 | 90 | userModel := model.User{} 91 | userModel.Pb.Id = in.GetUser().GetId() 92 | err = userModel.Get(ctx, u.Db) 93 | if err != nil { 94 | return &output, err 95 | } 96 | 97 | if userLogin.Pb.GetCompanyId() != userModel.Pb.GetCompanyId() { 98 | return &output, status.Error(codes.PermissionDenied, "its not your company") 99 | } 100 | 101 | if len(userLogin.Pb.GetRegionId()) > 0 && userLogin.Pb.GetRegionId() != userModel.Pb.GetRegionId() { 102 | return &output, status.Error(codes.PermissionDenied, "its not your company") 103 | } 104 | 105 | if len(userLogin.Pb.GetBranchId()) > 0 && userLogin.Pb.GetBranchId() != userModel.Pb.GetBranchId() { 106 | return &output, status.Error(codes.PermissionDenied, "its not your branch") 107 | } 108 | 109 | in.User = &userModel.Pb 110 | } 111 | 112 | employeeModel = model.Employee{} 113 | employeeModel.Pb = users.Employee{ 114 | Code: in.GetCode(), 115 | Name: in.GetName(), 116 | Address: in.GetAddress(), 117 | City: in.GetCity(), 118 | Province: in.GetProvince(), 119 | Jabatan: in.GetJabatan(), 120 | User: in.GetUser(), 121 | } 122 | err = employeeModel.Create(ctx, u.Db) 123 | if err != nil { 124 | return &output, err 125 | } 126 | return &employeeModel.Pb, nil 127 | } 128 | 129 | // Update Employee 130 | func (u *Employee) Update(ctx context.Context, in *users.Employee) (*users.Employee, error) { 131 | var output users.Employee 132 | var err error 133 | var employeeModel model.Employee 134 | 135 | // basic validation 136 | { 137 | if len(in.GetId()) == 0 { 138 | return &output, status.Error(codes.InvalidArgument, "Please supply valid id") 139 | } 140 | employeeModel.Pb.Id = in.GetId() 141 | } 142 | 143 | employeeModel.Pb.Id = in.GetId() 144 | err = employeeModel.Get(ctx, u.Db) 145 | if err != nil { 146 | return &output, err 147 | } 148 | 149 | ctx, err = getMetadata(ctx) 150 | if err != nil { 151 | return &output, err 152 | } 153 | 154 | // get user login 155 | var userLogin model.User 156 | userLogin.Pb.Id = ctx.Value(app.Ctx("userID")).(string) 157 | err = userLogin.Get(ctx, u.Db) 158 | if err != nil { 159 | return &output, err 160 | } 161 | 162 | if len(userLogin.Pb.GetBranchId()) > 0 && userLogin.Pb.GetBranchId() != employeeModel.Pb.GetUser().GetBranchId() { 163 | return &output, status.Error(codes.Unauthenticated, "its not your branch") 164 | } 165 | 166 | if userLogin.Pb.GetCompanyId() != employeeModel.Pb.GetUser().GetCompanyId() { 167 | return &output, status.Error(codes.Unauthenticated, "its not your company") 168 | } 169 | 170 | if len(userLogin.Pb.GetRegionId()) > 0 && userLogin.Pb.GetRegionId() != employeeModel.Pb.GetUser().GetRegionId() { 171 | return &output, status.Error(codes.Unauthenticated, "its not your region") 172 | } 173 | 174 | if len(in.GetUser().GetId()) > 0 && in.GetUser().GetId() != employeeModel.Pb.GetUser().GetId() { 175 | var userInput model.User 176 | userInput.Pb.Id = in.GetUser().GetId() 177 | err = userInput.Get(ctx, u.Db) 178 | if err != nil { 179 | return &output, err 180 | } 181 | if userLogin.Pb.GetCompanyId() != userInput.Pb.GetCompanyId() { 182 | return &output, status.Error(codes.PermissionDenied, "its not your company") 183 | } 184 | 185 | if len(userLogin.Pb.GetRegionId()) > 0 && userLogin.Pb.GetRegionId() != userInput.Pb.GetRegionId() { 186 | return &output, status.Error(codes.PermissionDenied, "its not your company") 187 | } 188 | 189 | if len(userLogin.Pb.GetBranchId()) > 0 && userLogin.Pb.GetBranchId() != userInput.Pb.GetBranchId() { 190 | return &output, status.Error(codes.PermissionDenied, "its not your branch") 191 | } 192 | 193 | employeeModel.Pb.User = &userInput.Pb 194 | } 195 | 196 | if len(in.GetName()) > 0 { 197 | employeeModel.Pb.Name = in.GetName() 198 | } 199 | 200 | if len(in.GetAddress()) > 0 { 201 | employeeModel.Pb.Address = in.GetAddress() 202 | } 203 | 204 | if len(in.GetCity()) > 0 { 205 | employeeModel.Pb.City = in.GetCity() 206 | } 207 | 208 | if len(in.GetProvince()) > 0 { 209 | employeeModel.Pb.Province = in.GetProvince() 210 | } 211 | 212 | if len(in.GetJabatan()) > 0 { 213 | employeeModel.Pb.Jabatan = in.GetJabatan() 214 | } 215 | 216 | err = employeeModel.Update(ctx, u.Db) 217 | if err != nil { 218 | return &output, err 219 | } 220 | 221 | return &employeeModel.Pb, nil 222 | } 223 | 224 | // View Employee 225 | func (u *Employee) View(ctx context.Context, in *users.Id) (*users.Employee, error) { 226 | var output users.Employee 227 | var err error 228 | var employeeModel model.Employee 229 | 230 | // basic validation 231 | { 232 | if len(in.GetId()) == 0 { 233 | return &output, status.Error(codes.InvalidArgument, "Please supply valid id") 234 | } 235 | employeeModel.Pb.Id = in.GetId() 236 | } 237 | 238 | ctx, err = getMetadata(ctx) 239 | if err != nil { 240 | return &output, err 241 | } 242 | 243 | // get user login 244 | var userLogin model.User 245 | userLogin.Pb.Id = ctx.Value(app.Ctx("userID")).(string) 246 | err = userLogin.Get(ctx, u.Db) 247 | if err != nil { 248 | return &output, err 249 | } 250 | 251 | err = employeeModel.Get(ctx, u.Db) 252 | if err != nil { 253 | return &output, err 254 | } 255 | 256 | if userLogin.Pb.GetCompanyId() != employeeModel.Pb.GetUser().GetCompanyId() { 257 | return &output, status.Error(codes.Unauthenticated, "its not your company") 258 | } 259 | 260 | if len(userLogin.Pb.GetRegionId()) > 0 && userLogin.Pb.GetRegionId() != employeeModel.Pb.GetUser().GetRegionId() { 261 | return &output, status.Error(codes.Unauthenticated, "its not your region") 262 | } 263 | 264 | if len(userLogin.Pb.GetBranchId()) > 0 && userLogin.Pb.GetBranchId() != employeeModel.Pb.GetUser().GetBranchId() { 265 | return &output, status.Error(codes.Unauthenticated, "its not your branch") 266 | } 267 | 268 | return &employeeModel.Pb, nil 269 | } 270 | 271 | // Delete Employee 272 | func (u *Employee) Delete(ctx context.Context, in *users.Id) (*users.MyBoolean, error) { 273 | var output users.MyBoolean 274 | output.Boolean = false 275 | var err error 276 | var employeeModel model.Employee 277 | 278 | // basic validation 279 | { 280 | if len(in.GetId()) == 0 { 281 | return &output, status.Error(codes.InvalidArgument, "Please supply valid id") 282 | } 283 | employeeModel.Pb.Id = in.GetId() 284 | } 285 | 286 | ctx, err = getMetadata(ctx) 287 | if err != nil { 288 | return &output, err 289 | } 290 | 291 | // get user login 292 | var userLogin model.User 293 | userLogin.Pb.Id = ctx.Value(app.Ctx("userID")).(string) 294 | err = userLogin.Get(ctx, u.Db) 295 | if err != nil { 296 | return &output, err 297 | } 298 | 299 | err = employeeModel.Get(ctx, u.Db) 300 | if err != nil { 301 | return &output, err 302 | } 303 | 304 | if userLogin.Pb.GetCompanyId() != employeeModel.Pb.GetUser().GetCompanyId() { 305 | return &output, status.Error(codes.Unauthenticated, "its not your company") 306 | } 307 | 308 | if len(userLogin.Pb.GetRegionId()) > 0 && userLogin.Pb.GetRegionId() != employeeModel.Pb.GetUser().GetRegionId() { 309 | return &output, status.Error(codes.Unauthenticated, "its not your region") 310 | } 311 | 312 | if len(userLogin.Pb.GetBranchId()) > 0 && userLogin.Pb.GetBranchId() != employeeModel.Pb.GetUser().GetBranchId() { 313 | return &output, status.Error(codes.Unauthenticated, "its not your branch") 314 | } 315 | 316 | err = employeeModel.Delete(ctx, u.Db) 317 | if err != nil { 318 | return &output, err 319 | } 320 | 321 | output.Boolean = true 322 | return &output, nil 323 | } 324 | 325 | // List Employee 326 | func (u *Employee) List(in *users.ListEmployeeRequest, stream users.EmployeeService_ListServer) error { 327 | ctx := stream.Context() 328 | ctx, err := getMetadata(ctx) 329 | if err != nil { 330 | return err 331 | } 332 | 333 | // get user login 334 | var userLogin model.User 335 | userLogin.Pb.Id = ctx.Value(app.Ctx("userID")).(string) 336 | err = userLogin.Get(ctx, u.Db) 337 | if err != nil { 338 | return err 339 | } 340 | 341 | if len(in.GetRegionId()) > 0 { 342 | regionModel := model.Region{} 343 | regionModel.Pb.Id = in.GetRegionId() 344 | err = regionModel.Get(ctx, u.Db) 345 | if err != nil { 346 | return err 347 | } 348 | 349 | if regionModel.Pb.GetCompanyId() != ctx.Value(app.Ctx("companyID")).(string) { 350 | return status.Error(codes.InvalidArgument, "its not your company") 351 | } 352 | } else { 353 | if len(userLogin.Pb.GetRegionId()) > 0 { 354 | in.RegionId = userLogin.Pb.GetRegionId() 355 | } 356 | } 357 | 358 | if len(in.GetBranchId()) > 0 { 359 | branchModel := model.Branch{} 360 | branchModel.Pb.Id = in.GetBranchId() 361 | err = branchModel.Get(ctx, u.Db) 362 | if err != nil { 363 | return err 364 | } 365 | 366 | if branchModel.Pb.GetCompanyId() != ctx.Value(app.Ctx("companyID")).(string) { 367 | return status.Error(codes.InvalidArgument, "its not your company") 368 | } 369 | 370 | if branchModel.Pb.GetRegionId() != userLogin.Pb.GetRegionId() { 371 | return status.Error(codes.InvalidArgument, "its not your region") 372 | } 373 | } else { 374 | if len(userLogin.Pb.GetBranchId()) > 0 { 375 | in.BranchId = userLogin.Pb.GetBranchId() 376 | } 377 | } 378 | 379 | var employeeModel model.Employee 380 | query, paramQueries, paginationResponse, err := employeeModel.ListQuery(ctx, u.Db, in, &userLogin.Pb) 381 | 382 | rows, err := u.Db.QueryContext(ctx, query, paramQueries...) 383 | if err != nil { 384 | return status.Error(codes.Internal, err.Error()) 385 | } 386 | defer rows.Close() 387 | paginationResponse.RegionId = in.GetRegionId() 388 | paginationResponse.BranchId = in.GetBranchId() 389 | paginationResponse.Pagination = in.GetPagination() 390 | 391 | for rows.Next() { 392 | err := contextError(ctx) 393 | if err != nil { 394 | return err 395 | } 396 | 397 | var pbEmployee users.Employee 398 | var pbUser users.User 399 | var regionID, branchID sql.NullString 400 | err = rows.Scan( 401 | &pbEmployee.Id, &pbEmployee.Name, &pbEmployee.Code, &pbEmployee.Address, 402 | &pbEmployee.City, &pbEmployee.Province, &pbEmployee.Jabatan, 403 | &pbUser.Id, &pbUser.CompanyId, ®ionID, &branchID, &pbUser.Name, &pbUser.Email, 404 | ) 405 | if err != nil { 406 | return err 407 | } 408 | 409 | pbUser.RegionId = regionID.String 410 | pbUser.BranchId = branchID.String 411 | pbEmployee.User = &pbUser 412 | 413 | res := &users.ListEmployeeResponse{ 414 | Pagination: paginationResponse, 415 | Employee: &pbEmployee, 416 | } 417 | 418 | err = stream.Send(res) 419 | if err != nil { 420 | return status.Errorf(codes.Unknown, "cannot send stream response: %v", err) 421 | } 422 | } 423 | 424 | return nil 425 | } 426 | -------------------------------------------------------------------------------- /internal/service/feature.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "database/sql" 5 | 6 | "github.com/jacky-htg/erp-pkg/db/redis" 7 | "github.com/jacky-htg/erp-proto/go/pb/users" 8 | "google.golang.org/grpc/codes" 9 | "google.golang.org/grpc/status" 10 | ) 11 | 12 | // Feature struct 13 | type Feature struct { 14 | Db *sql.DB 15 | Cache *redis.Cache 16 | users.UnimplementedFeatureServiceServer 17 | } 18 | 19 | // List feature 20 | func (u *Feature) List(in *users.MyEmpty, stream users.FeatureService_ListServer) error { 21 | ctx := stream.Context() 22 | rows, err := u.Db.QueryContext(ctx, `SELECT id, name from features`) 23 | if err != nil { 24 | return status.Error(codes.Internal, err.Error()) 25 | } 26 | defer rows.Close() 27 | 28 | for rows.Next() { 29 | err := contextError(ctx) 30 | if err != nil { 31 | return err 32 | } 33 | 34 | var pbFeature users.Feature 35 | err = rows.Scan(&pbFeature.Id, &pbFeature.Name) 36 | if err != nil { 37 | return err 38 | } 39 | 40 | err = stream.Send(&users.ListFeatureResponse{Feature: &pbFeature}) 41 | if err != nil { 42 | return status.Errorf(codes.Unknown, "cannot send stream response: %v", err) 43 | } 44 | } 45 | return nil 46 | } 47 | -------------------------------------------------------------------------------- /internal/service/group.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "encoding/json" 7 | 8 | "github.com/jacky-htg/erp-pkg/app" 9 | "github.com/jacky-htg/erp-pkg/db/redis" 10 | "github.com/jacky-htg/erp-proto/go/pb/users" 11 | "github.com/jacky-htg/user-service/internal/model" 12 | "google.golang.org/grpc/codes" 13 | "google.golang.org/grpc/status" 14 | ) 15 | 16 | // Group struct 17 | type Group struct { 18 | Db *sql.DB 19 | Cache *redis.Cache 20 | users.UnimplementedGroupServiceServer 21 | } 22 | 23 | // Create Group 24 | func (u *Group) Create(ctx context.Context, in *users.Group) (*users.Group, error) { 25 | var groupModel model.Group 26 | var err error 27 | 28 | // basic validation 29 | { 30 | if len(in.GetName()) == 0 { 31 | return &groupModel.Pb, status.Error(codes.InvalidArgument, "Please supply valid name") 32 | } 33 | } 34 | 35 | ctx, err = getMetadata(ctx) 36 | if err != nil { 37 | return &groupModel.Pb, err 38 | } 39 | 40 | // company validation 41 | { 42 | if len(in.GetCompanyId()) > 0 && in.GetCompanyId() != ctx.Value(app.Ctx("companyID")).(string) { 43 | return &groupModel.Pb, status.Error(codes.PermissionDenied, "Please supply valid company id") 44 | } 45 | in.CompanyId = ctx.Value(app.Ctx("companyID")).(string) 46 | } 47 | 48 | groupModel.Pb = users.Group{ 49 | CompanyId: in.GetCompanyId(), 50 | Name: in.GetName(), 51 | } 52 | err = groupModel.Create(ctx, u.Db) 53 | if err != nil { 54 | return &groupModel.Pb, err 55 | } 56 | 57 | return &groupModel.Pb, nil 58 | } 59 | 60 | // Update Group 61 | func (u *Group) Update(ctx context.Context, in *users.Group) (*users.Group, error) { 62 | var groupModel model.Group 63 | var err error 64 | 65 | // basic validation 66 | { 67 | if len(in.GetId()) == 0 { 68 | return &groupModel.Pb, status.Error(codes.InvalidArgument, "Please supply valid id") 69 | } 70 | groupModel.Pb.Id = in.GetId() 71 | } 72 | 73 | ctx, err = getMetadata(ctx) 74 | if err != nil { 75 | return &groupModel.Pb, err 76 | } 77 | 78 | // get user login 79 | var userLogin model.User 80 | userLogin.Pb.Id = ctx.Value(app.Ctx("userID")).(string) 81 | err = userLogin.Get(ctx, u.Db) 82 | if err != nil { 83 | return &groupModel.Pb, err 84 | } 85 | 86 | if len(in.GetCompanyId()) > 0 && userLogin.Pb.GetCompanyId() != in.GetCompanyId() { 87 | return &groupModel.Pb, status.Error(codes.Unauthenticated, "its not your company") 88 | } 89 | 90 | err = groupModel.Get(ctx, u.Db) 91 | if err != nil { 92 | return &groupModel.Pb, err 93 | } 94 | 95 | if !groupModel.Pb.IsMutable { 96 | return &groupModel.Pb, status.Error(codes.FailedPrecondition, "data is not imutable") 97 | } 98 | 99 | if len(in.GetName()) > 0 { 100 | groupModel.Pb.Name = in.GetName() 101 | } 102 | 103 | err = groupModel.Update(ctx, u.Db) 104 | if err != nil { 105 | return &groupModel.Pb, err 106 | } 107 | 108 | return &groupModel.Pb, nil 109 | } 110 | 111 | // View Group 112 | func (u *Group) View(ctx context.Context, in *users.Id) (*users.Group, error) { 113 | var groupModel model.Group 114 | var err error 115 | 116 | // basic validation 117 | { 118 | if len(in.GetId()) == 0 { 119 | return &groupModel.Pb, status.Error(codes.InvalidArgument, "Please supply valid id") 120 | } 121 | groupModel.Pb.Id = in.GetId() 122 | } 123 | 124 | ctx, err = getMetadata(ctx) 125 | if err != nil { 126 | return &groupModel.Pb, err 127 | } 128 | 129 | // get user login 130 | var userLogin model.User 131 | userLogin.Pb.Id = ctx.Value(app.Ctx("userID")).(string) 132 | err = userLogin.Get(ctx, u.Db) 133 | if err != nil { 134 | return &groupModel.Pb, err 135 | } 136 | 137 | err = groupModel.Get(ctx, u.Db) 138 | if err != nil { 139 | return &groupModel.Pb, err 140 | } 141 | 142 | if userLogin.Pb.GetCompanyId() != groupModel.Pb.GetCompanyId() { 143 | return &groupModel.Pb, status.Error(codes.Unauthenticated, "its not your company") 144 | } 145 | 146 | return &groupModel.Pb, nil 147 | } 148 | 149 | // Delete Group 150 | func (u *Group) Delete(ctx context.Context, in *users.Id) (*users.MyBoolean, error) { 151 | var output users.MyBoolean 152 | output.Boolean = false 153 | 154 | var groupModel model.Group 155 | var err error 156 | 157 | // basic validation 158 | { 159 | if len(in.GetId()) == 0 { 160 | return &output, status.Error(codes.InvalidArgument, "Please supply valid id") 161 | } 162 | groupModel.Pb.Id = in.GetId() 163 | } 164 | 165 | ctx, err = getMetadata(ctx) 166 | if err != nil { 167 | return &output, err 168 | } 169 | 170 | // get user login 171 | var userLogin model.User 172 | userLogin.Pb.Id = ctx.Value(app.Ctx("userID")).(string) 173 | err = userLogin.Get(ctx, u.Db) 174 | if err != nil { 175 | return &output, err 176 | } 177 | 178 | err = groupModel.Get(ctx, u.Db) 179 | if err != nil { 180 | return &output, err 181 | } 182 | 183 | if !groupModel.Pb.IsMutable { 184 | return &output, status.Error(codes.FailedPrecondition, "data is not imutable") 185 | } 186 | 187 | if userLogin.Pb.GetCompanyId() != groupModel.Pb.GetCompanyId() { 188 | return &output, status.Error(codes.Unauthenticated, "its not your company") 189 | } 190 | 191 | err = groupModel.Delete(ctx, u.Db) 192 | if err != nil { 193 | return &output, err 194 | } 195 | 196 | output.Boolean = true 197 | return &output, nil 198 | } 199 | 200 | // List Group 201 | func (u *Group) List(in *users.ListGroupRequest, stream users.GroupService_ListServer) error { 202 | ctx := stream.Context() 203 | ctx, err := getMetadata(ctx) 204 | if err != nil { 205 | return err 206 | } 207 | 208 | var groupModel model.Group 209 | query, paramQueries, paginationResponse, err := groupModel.ListQuery(ctx, u.Db, in) 210 | 211 | rows, err := u.Db.QueryContext(ctx, query, paramQueries...) 212 | if err != nil { 213 | return status.Error(codes.Internal, err.Error()) 214 | } 215 | defer rows.Close() 216 | paginationResponse.Pagination = in.GetPagination() 217 | 218 | for rows.Next() { 219 | err := contextError(ctx) 220 | if err != nil { 221 | return err 222 | } 223 | 224 | var tmpAccess string 225 | var pbGroup users.Group 226 | err = rows.Scan(&pbGroup.Id, &pbGroup.CompanyId, &pbGroup.Name, &pbGroup.IsMutable, &tmpAccess) 227 | if err != nil { 228 | return err 229 | } 230 | 231 | err = json.Unmarshal([]byte(tmpAccess), &pbGroup.Access) 232 | if err != nil { 233 | return status.Errorf(codes.Internal, "unmarshal access: %v", err) 234 | } 235 | 236 | res := &users.ListGroupResponse{ 237 | Pagination: paginationResponse, 238 | Group: &pbGroup, 239 | } 240 | 241 | err = stream.Send(res) 242 | if err != nil { 243 | return status.Errorf(codes.Unknown, "cannot send stream response: %v", err) 244 | } 245 | } 246 | return nil 247 | } 248 | 249 | // GrantAccess Group 250 | func (u *Group) GrantAccess(ctx context.Context, in *users.GrantAccessRequest) (*users.Message, error) { 251 | var output users.Message 252 | output.Message = "Failed" 253 | 254 | var err error 255 | 256 | // basic validation 257 | { 258 | if len(in.GetGroupId()) == 0 { 259 | return &output, status.Error(codes.InvalidArgument, "Please supply valid group") 260 | } 261 | 262 | if len(in.GetAccessId()) == 0 { 263 | return &output, status.Error(codes.InvalidArgument, "Please supply valid access") 264 | } 265 | } 266 | 267 | ctx, err = getMetadata(ctx) 268 | if err != nil { 269 | return &output, err 270 | } 271 | 272 | // group validation 273 | { 274 | groupModel := model.Group{} 275 | groupModel.Pb.Id = in.GetGroupId() 276 | err = groupModel.Get(ctx, u.Db) 277 | if err != nil { 278 | return &output, err 279 | } 280 | 281 | if groupModel.Pb.GetCompanyId() != ctx.Value(app.Ctx("companyID")).(string) { 282 | return &output, status.Error(codes.PermissionDenied, "its not your company") 283 | } 284 | 285 | if !groupModel.Pb.IsMutable { 286 | return &output, status.Error(codes.FailedPrecondition, "data is not imutable") 287 | } 288 | } 289 | 290 | // access validation 291 | { 292 | accessModel := model.Access{} 293 | accessModel.Pb.Id = in.GetAccessId() 294 | err = accessModel.Get(ctx, u.Db) 295 | if err != nil { 296 | return &output, err 297 | } 298 | } 299 | 300 | accessGroup := model.AccessGroup{} 301 | accessGroup.AccessID = in.GetAccessId() 302 | accessGroup.GroupID = in.GetGroupId() 303 | err = accessGroup.Get(ctx, u.Db) 304 | if err != nil { 305 | if st, ok := status.FromError(err); ok && st.Code() != codes.NotFound { 306 | return &output, err 307 | } 308 | } else { 309 | return &output, status.Error(codes.AlreadyExists, "Already grant access") 310 | } 311 | 312 | err = accessGroup.Grant(ctx, u.Db) 313 | if err != nil { 314 | return &output, err 315 | } 316 | 317 | output.Message = "Success" 318 | return &output, nil 319 | } 320 | 321 | // RevokeAccess Group 322 | func (u *Group) RevokeAccess(ctx context.Context, in *users.GrantAccessRequest) (*users.Message, error) { 323 | var output users.Message 324 | output.Message = "Failed" 325 | 326 | var err error 327 | 328 | // basic validation 329 | { 330 | if len(in.GetGroupId()) == 0 { 331 | return &output, status.Error(codes.InvalidArgument, "Please supply valid group") 332 | } 333 | 334 | if len(in.GetAccessId()) == 0 { 335 | return &output, status.Error(codes.InvalidArgument, "Please supply valid access") 336 | } 337 | } 338 | 339 | ctx, err = getMetadata(ctx) 340 | if err != nil { 341 | return &output, err 342 | } 343 | 344 | // group validation 345 | { 346 | groupModel := model.Group{} 347 | groupModel.Pb.Id = in.GetGroupId() 348 | err = groupModel.Get(ctx, u.Db) 349 | if err != nil { 350 | return &output, err 351 | } 352 | 353 | if groupModel.Pb.GetCompanyId() != ctx.Value(app.Ctx("companyID")).(string) { 354 | return &output, status.Error(codes.PermissionDenied, "its not your company") 355 | } 356 | 357 | if !groupModel.Pb.IsMutable { 358 | return &output, status.Error(codes.FailedPrecondition, "data is not imutable") 359 | } 360 | } 361 | 362 | // access validation 363 | { 364 | accessModel := model.Access{} 365 | accessModel.Pb.Id = in.GetAccessId() 366 | err = accessModel.Get(ctx, u.Db) 367 | if err != nil { 368 | return &output, err 369 | } 370 | } 371 | 372 | accessGroup := model.AccessGroup{} 373 | accessGroup.AccessID = in.GetAccessId() 374 | accessGroup.GroupID = in.GetGroupId() 375 | err = accessGroup.Get(ctx, u.Db) 376 | if err != nil { 377 | return &output, err 378 | } 379 | 380 | err = accessGroup.Revoke(ctx, u.Db) 381 | if err != nil { 382 | return &output, err 383 | } 384 | 385 | output.Message = "Success" 386 | return &output, nil 387 | } 388 | -------------------------------------------------------------------------------- /internal/service/package_feature.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | 7 | "github.com/jacky-htg/erp-pkg/db/redis" 8 | "github.com/jacky-htg/erp-proto/go/pb/users" 9 | "github.com/jacky-htg/user-service/internal/model" 10 | "google.golang.org/grpc/codes" 11 | "google.golang.org/grpc/status" 12 | ) 13 | 14 | // PackageFeature struct 15 | type PackageFeature struct { 16 | Db *sql.DB 17 | Cache *redis.Cache 18 | users.UnimplementedPackageFeatureServiceServer 19 | } 20 | 21 | // View Package Feature 22 | func (u *PackageFeature) View(ctx context.Context, in *users.Id) (*users.PackageOfFeature, error) { 23 | var output users.PackageOfFeature 24 | var packageFeatureModel model.FeaturePackage 25 | 26 | // basic validation 27 | { 28 | if len(in.GetId()) == 0 { 29 | return &output, status.Error(codes.InvalidArgument, "Please supply valid id") 30 | } 31 | packageFeatureModel.Pb.Id = in.GetId() 32 | } 33 | 34 | err := packageFeatureModel.Get(ctx, u.Db) 35 | if err != nil { 36 | return &output, err 37 | } 38 | 39 | return &packageFeatureModel.Pb, nil 40 | } 41 | 42 | // List PackageFeature 43 | func (u *PackageFeature) List(in *users.MyEmpty, stream users.PackageFeatureService_ListServer) error { 44 | ctx := stream.Context() 45 | rows, err := u.Db.QueryContext(ctx, `SELECT id, name from package_features`) 46 | if err != nil { 47 | return status.Error(codes.Internal, err.Error()) 48 | } 49 | defer rows.Close() 50 | 51 | for rows.Next() { 52 | err := contextError(ctx) 53 | if err != nil { 54 | return err 55 | } 56 | 57 | var pbPackageFeature users.PackageOfFeature 58 | var name string 59 | err = rows.Scan(&pbPackageFeature.Id, &name) 60 | if err != nil { 61 | return err 62 | } 63 | 64 | if value, ok := users.EnumPackageOfFeature_value[name]; ok { 65 | pbPackageFeature.Name = users.EnumPackageOfFeature(value) 66 | } 67 | 68 | err = stream.Send(&users.ListPackageFeatureResponse{PackageOfFeature: &pbPackageFeature}) 69 | if err != nil { 70 | return status.Errorf(codes.Unknown, "cannot send stream response: %v", err) 71 | } 72 | } 73 | return nil 74 | } 75 | -------------------------------------------------------------------------------- /internal/service/region.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | 7 | "github.com/jacky-htg/erp-pkg/app" 8 | "github.com/jacky-htg/erp-pkg/db/redis" 9 | "github.com/jacky-htg/erp-proto/go/pb/users" 10 | "github.com/jacky-htg/user-service/internal/model" 11 | "google.golang.org/grpc/codes" 12 | "google.golang.org/grpc/status" 13 | ) 14 | 15 | // Region struct 16 | type Region struct { 17 | Db *sql.DB 18 | Cache *redis.Cache 19 | users.UnimplementedRegionServiceServer 20 | } 21 | 22 | // Create new region 23 | func (u *Region) Create(ctx context.Context, in *users.Region) (*users.Region, error) { 24 | var output users.Region 25 | var err error 26 | var regionModel model.Region 27 | 28 | // basic validation 29 | { 30 | if len(in.GetName()) == 0 { 31 | return &output, status.Error(codes.InvalidArgument, "Please supply valid name") 32 | } 33 | } 34 | 35 | ctx, err = getMetadata(ctx) 36 | if err != nil { 37 | return &output, err 38 | } 39 | 40 | // code validation 41 | { 42 | if len(in.GetCode()) == 0 { 43 | return &output, status.Error(codes.InvalidArgument, "Please supply valid code") 44 | } 45 | regionModel = model.Region{} 46 | regionModel.Pb.Code = in.GetCode() 47 | err = regionModel.GetByCode(ctx, u.Db) 48 | if err != nil { 49 | if st, ok := status.FromError(err); ok && st.Code() != codes.NotFound { 50 | return &output, err 51 | } 52 | } 53 | 54 | if len(regionModel.Pb.GetId()) > 0 { 55 | return &output, status.Error(codes.AlreadyExists, "code must be unique") 56 | } 57 | } 58 | 59 | // company validation 60 | { 61 | if len(in.GetCompanyId()) > 0 && in.GetCompanyId() != ctx.Value(app.Ctx("companyID")).(string) { 62 | return &output, status.Error(codes.PermissionDenied, "Please supply valid company id") 63 | } 64 | in.CompanyId = ctx.Value(app.Ctx("companyID")).(string) 65 | } 66 | 67 | // get user login 68 | var userLogin model.User 69 | userLogin.Pb.Id = ctx.Value(app.Ctx("userID")).(string) 70 | err = userLogin.Get(ctx, u.Db) 71 | if err != nil { 72 | return &output, err 73 | } 74 | 75 | if len(userLogin.Pb.GetRegionId()) > 0 || len(userLogin.Pb.GetBranchId()) > 0 { 76 | return &output, status.Error(codes.PermissionDenied, "Need user company to add new region") 77 | } 78 | 79 | if len(in.GetBranches()) > 0 { 80 | for _, branch := range in.GetBranches() { 81 | branchModel := model.Branch{} 82 | branchModel.Pb.Id = branch.GetId() 83 | err = branchModel.Get(ctx, u.Db) 84 | if err != nil { 85 | return &output, err 86 | } 87 | } 88 | } 89 | 90 | regionModel = model.Region{} 91 | regionModel.Pb = users.Region{ 92 | Branches: in.GetBranches(), 93 | Code: in.GetCode(), 94 | CompanyId: in.GetCompanyId(), 95 | Name: in.GetName(), 96 | } 97 | tx, err := u.Db.BeginTx(ctx, nil) 98 | if err != nil { 99 | return &output, status.Errorf(codes.Internal, "begin tx: %v", err) 100 | } 101 | 102 | err = regionModel.Create(ctx, u.Db, tx) 103 | if err != nil { 104 | tx.Rollback() 105 | return &output, err 106 | } 107 | 108 | tx.Commit() 109 | 110 | return ®ionModel.Pb, nil 111 | } 112 | 113 | // Update region 114 | func (u *Region) Update(ctx context.Context, in *users.Region) (*users.Region, error) { 115 | var output users.Region 116 | var err error 117 | var regionModel model.Region 118 | 119 | // basic validation 120 | { 121 | if len(in.GetId()) == 0 { 122 | return &output, status.Error(codes.InvalidArgument, "Please supply valid id") 123 | } 124 | regionModel.Pb.Id = in.GetId() 125 | } 126 | 127 | ctx, err = getMetadata(ctx) 128 | if err != nil { 129 | return &output, err 130 | } 131 | 132 | // get user login 133 | var userLogin model.User 134 | userLogin.Pb.Id = ctx.Value(app.Ctx("userID")).(string) 135 | err = userLogin.Get(ctx, u.Db) 136 | if err != nil { 137 | return &output, err 138 | } 139 | 140 | if len(userLogin.Pb.GetBranchId()) > 0 { 141 | return &output, status.Error(codes.Unauthenticated, "only user company/region can update the region") 142 | } 143 | 144 | if len(userLogin.Pb.GetRegionId()) > 0 && userLogin.Pb.GetRegionId() != in.GetId() { 145 | return &output, status.Error(codes.Unauthenticated, "its not your region") 146 | } 147 | 148 | err = regionModel.Get(ctx, u.Db) 149 | if err != nil { 150 | return &output, err 151 | } 152 | 153 | if userLogin.Pb.GetCompanyId() != regionModel.Pb.GetCompanyId() { 154 | return &output, status.Error(codes.Unauthenticated, "its not your company") 155 | } 156 | 157 | if len(in.GetName()) > 0 { 158 | regionModel.Pb.Name = in.GetName() 159 | } 160 | 161 | if len(in.GetBranches()) > 0 { 162 | regionModel.UpdateBranches = true 163 | for _, branch := range in.GetBranches() { 164 | var branchModel model.Branch 165 | branchModel.Pb.Id = branch.GetId() 166 | err = branchModel.Get(ctx, u.Db) 167 | if err != nil { 168 | return &output, err 169 | } 170 | } 171 | 172 | regionModel.Pb.Branches = in.GetBranches() 173 | } 174 | 175 | tx, err := u.Db.BeginTx(ctx, nil) 176 | if err != nil { 177 | return &output, status.Errorf(codes.Internal, "begin tx: %v", err) 178 | } 179 | 180 | err = regionModel.Update(ctx, u.Db, tx) 181 | if err != nil { 182 | tx.Rollback() 183 | return &output, err 184 | } 185 | 186 | tx.Commit() 187 | 188 | return ®ionModel.Pb, nil 189 | } 190 | 191 | // View Region 192 | func (u *Region) View(ctx context.Context, in *users.Id) (*users.Region, error) { 193 | var output users.Region 194 | var err error 195 | var regionModel model.Region 196 | 197 | // basic validation 198 | { 199 | if len(in.GetId()) == 0 { 200 | return &output, status.Error(codes.InvalidArgument, "Please supply valid id") 201 | } 202 | regionModel.Pb.Id = in.GetId() 203 | } 204 | 205 | ctx, err = getMetadata(ctx) 206 | if err != nil { 207 | return &output, err 208 | } 209 | 210 | // get user login 211 | var userLogin model.User 212 | userLogin.Pb.Id = ctx.Value(app.Ctx("userID")).(string) 213 | err = userLogin.Get(ctx, u.Db) 214 | if err != nil { 215 | return &output, err 216 | } 217 | 218 | err = regionModel.Get(ctx, u.Db) 219 | if err != nil { 220 | return &output, err 221 | } 222 | 223 | if userLogin.Pb.GetCompanyId() != regionModel.Pb.GetCompanyId() { 224 | return &output, status.Error(codes.Unauthenticated, "its not your company") 225 | } 226 | 227 | if len(userLogin.Pb.GetRegionId()) > 0 && userLogin.Pb.GetRegionId() != regionModel.Pb.GetId() { 228 | return &output, status.Error(codes.Unauthenticated, "its not your region") 229 | } 230 | 231 | return ®ionModel.Pb, nil 232 | } 233 | 234 | // Delete Region 235 | func (u *Region) Delete(ctx context.Context, in *users.Id) (*users.MyBoolean, error) { 236 | var output users.MyBoolean 237 | output.Boolean = false 238 | 239 | var err error 240 | var regionModel model.Region 241 | 242 | // basic validation 243 | { 244 | if len(in.GetId()) == 0 { 245 | return &output, status.Error(codes.InvalidArgument, "Please supply valid id") 246 | } 247 | regionModel.Pb.Id = in.GetId() 248 | } 249 | 250 | ctx, err = getMetadata(ctx) 251 | if err != nil { 252 | return &output, err 253 | } 254 | 255 | // get user login 256 | var userLogin model.User 257 | userLogin.Pb.Id = ctx.Value(app.Ctx("userID")).(string) 258 | err = userLogin.Get(ctx, u.Db) 259 | if err != nil { 260 | return &output, err 261 | } 262 | 263 | if len(userLogin.Pb.GetRegionId()) > 0 || len(userLogin.Pb.GetBranchId()) > 0 { 264 | return &output, status.Error(codes.Unauthenticated, "only user company can delete region") 265 | } 266 | 267 | err = regionModel.Get(ctx, u.Db) 268 | if err != nil { 269 | return &output, err 270 | } 271 | 272 | if userLogin.Pb.GetCompanyId() != regionModel.Pb.GetCompanyId() { 273 | return &output, status.Error(codes.Unauthenticated, "its not your company") 274 | } 275 | 276 | err = regionModel.Delete(ctx, u.Db) 277 | if err != nil { 278 | return &output, err 279 | } 280 | 281 | output.Boolean = true 282 | return &output, nil 283 | } 284 | 285 | // List Region 286 | func (u *Region) List(in *users.ListRegionRequest, stream users.RegionService_ListServer) error { 287 | ctx := stream.Context() 288 | ctx, err := getMetadata(ctx) 289 | if err != nil { 290 | return err 291 | } 292 | 293 | if len(in.GetCompanyId()) > 0 && in.GetCompanyId() != ctx.Value(app.Ctx("companyID")).(string) { 294 | return status.Error(codes.InvalidArgument, "its not your company") 295 | } 296 | 297 | // get user login 298 | var userLogin model.User 299 | userLogin.Pb.Id = ctx.Value(app.Ctx("userID")).(string) 300 | err = userLogin.Get(ctx, u.Db) 301 | if err != nil { 302 | return err 303 | } 304 | 305 | var regionModel model.Region 306 | query, paramQueries, paginationResponse, err := regionModel.ListQuery(ctx, u.Db, in, userLogin.Pb.GetRegionId()) 307 | 308 | rows, err := u.Db.QueryContext(ctx, query, paramQueries...) 309 | if err != nil { 310 | return status.Error(codes.Internal, err.Error()) 311 | } 312 | defer rows.Close() 313 | paginationResponse.CompanyId = in.GetCompanyId() 314 | paginationResponse.Pagination = in.GetPagination() 315 | 316 | for rows.Next() { 317 | err := contextError(ctx) 318 | if err != nil { 319 | return err 320 | } 321 | 322 | var pbRegion users.Region 323 | err = rows.Scan(&pbRegion.Id, &pbRegion.CompanyId, &pbRegion.Name, &pbRegion.Code) 324 | if err != nil { 325 | return err 326 | } 327 | 328 | res := &users.ListRegionResponse{ 329 | Pagination: paginationResponse, 330 | Region: &pbRegion, 331 | } 332 | 333 | err = stream.Send(res) 334 | if err != nil { 335 | return status.Errorf(codes.Unknown, "cannot send stream response: %v", err) 336 | } 337 | } 338 | 339 | return nil 340 | } 341 | -------------------------------------------------------------------------------- /internal/service/user.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "os" 7 | "regexp" 8 | 9 | "github.com/jacky-htg/erp-pkg/app" 10 | "github.com/jacky-htg/erp-pkg/db/redis" 11 | "github.com/jacky-htg/erp-pkg/email" 12 | "github.com/jacky-htg/erp-pkg/token" 13 | "github.com/jacky-htg/erp-proto/go/pb/users" 14 | "github.com/jacky-htg/user-service/internal/model" 15 | "github.com/sendgrid/sendgrid-go/helpers/mail" 16 | "google.golang.org/grpc/codes" 17 | "google.golang.org/grpc/status" 18 | ) 19 | 20 | // User struct 21 | type User struct { 22 | Db *sql.DB 23 | Cache *redis.Cache 24 | users.UnimplementedUserServiceServer 25 | } 26 | 27 | // Create func 28 | func (u *User) Create(ctx context.Context, in *users.User) (*users.User, error) { 29 | var output users.User 30 | var err error 31 | var userModel model.User 32 | 33 | // basic validation 34 | { 35 | if in == nil { 36 | return &output, status.Error(codes.InvalidArgument, "Please supply valid argument") 37 | } 38 | 39 | if len(in.GetEmail()) == 0 { 40 | return &output, status.Error(codes.InvalidArgument, "Please supply valid email") 41 | } 42 | 43 | if len(in.GetGroup().GetId()) == 0 { 44 | return &output, status.Error(codes.InvalidArgument, "Please supply valid group_id") 45 | } 46 | 47 | if len(in.GetName()) == 0 { 48 | return &output, status.Error(codes.InvalidArgument, "Please supply valid name") 49 | } 50 | } 51 | 52 | // username validation 53 | { 54 | if len(in.GetUsername()) == 0 { 55 | return &output, status.Error(codes.InvalidArgument, "Please supply valid username") 56 | } 57 | 58 | if len(in.GetUsername()) < 8 { 59 | return &output, status.Error(codes.InvalidArgument, "username min 8 character") 60 | } 61 | 62 | userModel = model.User{} 63 | userModel.Pb.Username = in.GetUsername() 64 | err = userModel.GetByUsername(ctx, u.Db) 65 | if err != nil { 66 | if st, ok := status.FromError(err); ok && st.Code() != codes.NotFound { 67 | return &output, err 68 | } 69 | } 70 | 71 | if len(userModel.Pb.GetId()) > 0 { 72 | return &output, status.Error(codes.AlreadyExists, "username must be unique") 73 | } 74 | } 75 | 76 | // email validation 77 | { 78 | var emailRegex = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$") 79 | if valid := func(e string) bool { 80 | if len(e) < 3 && len(e) > 254 { 81 | return false 82 | } 83 | return emailRegex.MatchString(e) 84 | }(in.GetEmail()); !valid { 85 | return &output, status.Error(codes.InvalidArgument, "Please supply valid email") 86 | } 87 | 88 | userModel = model.User{} 89 | userModel.Pb.Email = in.GetEmail() 90 | err = userModel.GetByEmail(ctx, u.Db) 91 | if err != nil { 92 | if st, ok := status.FromError(err); ok && st.Code() != codes.NotFound { 93 | return &output, err 94 | } 95 | } 96 | 97 | if len(userModel.Pb.GetId()) > 0 { 98 | return &output, status.Error(codes.AlreadyExists, "email must be unique") 99 | } 100 | } 101 | 102 | ctx, err = getMetadata(ctx) 103 | if err != nil { 104 | return &output, err 105 | } 106 | 107 | // company validation 108 | { 109 | if len(in.GetCompanyId()) > 0 && in.GetCompanyId() != ctx.Value(app.Ctx("companyID")).(string) { 110 | return &output, status.Error(codes.PermissionDenied, "Please supply valid company id") 111 | } 112 | in.CompanyId = ctx.Value(app.Ctx("companyID")).(string) 113 | } 114 | 115 | // get user login 116 | var userLogin model.User 117 | userLogin.Pb.Id = ctx.Value(app.Ctx("userID")).(string) 118 | err = userLogin.Get(ctx, u.Db) 119 | if err != nil { 120 | return &output, err 121 | } 122 | 123 | // region validation 124 | { 125 | var regionModel model.Region 126 | if len(in.GetRegionId()) > 0 { 127 | if len(userLogin.Pb.GetRegionId()) == 0 { 128 | // check is region belongsto company 129 | regionModel.Pb.Id = in.GetRegionId() 130 | err = regionModel.Get(ctx, u.Db) 131 | if err != nil { 132 | return &output, err 133 | } 134 | 135 | if regionModel.Pb.GetCompanyId() != in.GetCompanyId() { 136 | return &output, status.Error(codes.PermissionDenied, "Please supply valid region id") 137 | } 138 | } else { 139 | if in.GetRegionId() != userLogin.Pb.GetRegionId() { 140 | return &output, status.Error(codes.PermissionDenied, "Please supply valid region id") 141 | } 142 | } 143 | } else { 144 | if len(userLogin.Pb.GetRegionId()) > 0 { 145 | in.RegionId = userLogin.Pb.GetRegionId() 146 | } 147 | } 148 | } 149 | 150 | // branch validation 151 | { 152 | if len(in.GetBranchId()) > 0 { 153 | if len(userLogin.Pb.GetBranchId()) == 0 { 154 | // check is branch belongsto region 155 | var branchModel model.Branch 156 | branchModel.Pb.Id = in.GetBranchId() 157 | err = branchModel.Get(ctx, u.Db) 158 | if err != nil { 159 | return &output, err 160 | } 161 | 162 | if branchModel.Pb.GetRegionId() != in.GetRegionId() { 163 | return &output, status.Error(codes.PermissionDenied, "Please supply valid branch id") 164 | } 165 | } else { 166 | if in.GetBranchId() != userLogin.Pb.GetBranchId() { 167 | return &output, status.Error(codes.PermissionDenied, "Please supply valid branch id") 168 | } 169 | } 170 | } else { 171 | if len(userLogin.Pb.GetBranchId()) > 0 { 172 | in.BranchId = userLogin.Pb.GetBranchId() 173 | } 174 | } 175 | } 176 | 177 | // group validation 178 | { 179 | var groupModel model.Group 180 | groupModel.Pb.Id = in.GetGroup().GetId() 181 | err = groupModel.Get(ctx, u.Db) 182 | if err != nil { 183 | return &output, err 184 | } 185 | 186 | if groupModel.Pb.GetCompanyId() != in.GetCompanyId() { 187 | return &output, status.Error(codes.PermissionDenied, "Please supply valid group id") 188 | } 189 | } 190 | 191 | userModel = model.User{} 192 | userModel.Pb = users.User{ 193 | BranchId: in.GetBranchId(), 194 | CompanyId: in.GetCompanyId(), 195 | Email: in.GetEmail(), 196 | Group: in.GetGroup(), 197 | Name: in.GetName(), 198 | RegionId: in.GetRegionId(), 199 | Username: in.GetUsername(), 200 | } 201 | userModel.Password = generateRandomPassword() 202 | err = userModel.Create(ctx, u.Db) 203 | if err != nil { 204 | return &output, err 205 | } 206 | 207 | // send email registration info 208 | from := mail.NewEmail(os.Getenv("SENDGRID_FROM_NAME"), os.Getenv("SENDGRID_FROM_EMAIL")) 209 | p := mail.NewPersonalization() 210 | tos := []*mail.Email{ 211 | mail.NewEmail(userModel.Pb.GetName(), userModel.Pb.GetEmail()), 212 | } 213 | p.AddTos(tos...) 214 | 215 | p.SetDynamicTemplateData("name", userModel.Pb.GetName()) 216 | p.SetDynamicTemplateData("username", userModel.Pb.GetUsername()) 217 | p.SetDynamicTemplateData("password", userModel.Password) 218 | p.SetDynamicTemplateData("app_name", os.Getenv("APP_NAME")) 219 | p.SetDynamicTemplateData("cs_email", os.Getenv("CUSTOMERSERVICE_EMAIL")) 220 | p.SetDynamicTemplateData("cs_phone", os.Getenv("CUSTOMERSERVICE_PHONE")) 221 | 222 | err = email.SendMailV3(from, p, os.Getenv("SENDGRID_TEMPLATE_NEW_USER")) 223 | if err != nil { 224 | return &output, status.Errorf(codes.Internal, "send new account email: %v", err) 225 | } 226 | 227 | return &userModel.Pb, nil 228 | } 229 | 230 | // Update func 231 | func (u *User) Update(ctx context.Context, in *users.User) (*users.User, error) { 232 | var output users.User 233 | var err error 234 | var userModel model.User 235 | 236 | // basic validation 237 | { 238 | if len(in.GetId()) == 0 { 239 | return &output, status.Error(codes.InvalidArgument, "Please supply valid id") 240 | } 241 | userModel.Pb.Id = in.GetId() 242 | } 243 | 244 | ctx, err = getMetadata(ctx) 245 | if err != nil { 246 | return &output, err 247 | } 248 | 249 | // company validation 250 | { 251 | if len(in.GetCompanyId()) > 0 && in.GetCompanyId() != ctx.Value(app.Ctx("companyID")).(string) { 252 | return &output, status.Error(codes.PermissionDenied, "Please supply valid company id") 253 | } 254 | in.CompanyId = ctx.Value(app.Ctx("companyID")).(string) 255 | } 256 | 257 | // get user login 258 | var userLogin model.User 259 | userLogin.Pb.Id = ctx.Value(app.Ctx("userID")).(string) 260 | err = userLogin.Get(ctx, u.Db) 261 | if err != nil { 262 | return &output, err 263 | } 264 | 265 | // region validation 266 | if len(in.GetRegionId()) > 0 { 267 | var regionModel model.Region 268 | if len(in.GetRegionId()) > 0 { 269 | if len(userLogin.Pb.GetRegionId()) == 0 { 270 | // check is region belongsto company 271 | regionModel.Pb.Id = in.GetRegionId() 272 | err = regionModel.Get(ctx, u.Db) 273 | if err != nil { 274 | return &output, err 275 | } 276 | 277 | if regionModel.Pb.GetCompanyId() != ctx.Value(app.Ctx("companyID")).(string) { 278 | return &output, status.Error(codes.PermissionDenied, "itu Please supply valid region id") 279 | } 280 | } else { 281 | if in.GetRegionId() != userLogin.Pb.GetRegionId() { 282 | return &output, status.Error(codes.PermissionDenied, "ini Please supply valid region id") 283 | } 284 | } 285 | } else { 286 | if len(userLogin.Pb.GetRegionId()) > 0 { 287 | in.RegionId = userLogin.Pb.GetRegionId() 288 | } 289 | } 290 | } 291 | 292 | // branch validation 293 | if len(in.GetBranchId()) > 0 { 294 | if len(in.GetBranchId()) > 0 { 295 | if len(userLogin.Pb.GetBranchId()) == 0 { 296 | // check is branch belongsto region 297 | var branchModel model.Branch 298 | branchModel.Pb.Id = in.GetBranchId() 299 | err = branchModel.Get(ctx, u.Db) 300 | if err != nil { 301 | return &output, err 302 | } 303 | 304 | if branchModel.Pb.GetRegionId() != in.GetRegionId() { 305 | return &output, status.Error(codes.PermissionDenied, "Please supply valid branch id") 306 | } 307 | } else { 308 | if in.GetBranchId() != userLogin.Pb.GetBranchId() { 309 | return &output, status.Error(codes.PermissionDenied, "Please supply valid branch id") 310 | } 311 | } 312 | } else { 313 | if len(userLogin.Pb.GetBranchId()) > 0 { 314 | in.BranchId = userLogin.Pb.GetBranchId() 315 | } 316 | } 317 | } 318 | 319 | // group validation 320 | if len(in.GetGroup().GetId()) > 0 { 321 | var groupModel model.Group 322 | groupModel.Pb.Id = in.GetGroup().GetId() 323 | err = groupModel.Get(ctx, u.Db) 324 | if err != nil { 325 | return &output, err 326 | } 327 | 328 | if groupModel.Pb.GetCompanyId() != in.GetCompanyId() { 329 | return &output, status.Error(codes.PermissionDenied, "Please supply valid group id") 330 | } 331 | } 332 | 333 | err = userModel.Get(ctx, u.Db) 334 | if err != nil { 335 | return &output, err 336 | } 337 | 338 | err = u.checkFilteringContent(ctx, &userLogin, &userModel) 339 | if err != nil { 340 | return &output, err 341 | } 342 | 343 | if len(in.GetName()) > 0 { 344 | userModel.Pb.Name = in.GetName() 345 | } 346 | 347 | if len(in.GetRegionId()) > 0 { 348 | userModel.Pb.RegionId = in.GetRegionId() 349 | } else { 350 | userModel.Pb.RegionId = "" 351 | } 352 | 353 | if len(in.GetBranchId()) > 0 { 354 | userModel.Pb.BranchId = in.GetBranchId() 355 | } else { 356 | userModel.Pb.BranchId = "" 357 | } 358 | 359 | if len(in.GetGroup().GetId()) > 0 { 360 | userModel.Pb.Group = in.GetGroup() 361 | } 362 | 363 | err = userModel.Update(ctx, u.Db) 364 | if err != nil { 365 | return &output, err 366 | } 367 | 368 | return &userModel.Pb, nil 369 | } 370 | 371 | // View func 372 | func (u *User) View(ctx context.Context, in *users.Id) (*users.User, error) { 373 | var output users.User 374 | var err error 375 | var userModel model.User 376 | 377 | if len(in.GetId()) == 0 { 378 | return &output, status.Error(codes.InvalidArgument, "Please supply valid id") 379 | } 380 | 381 | ctx, err = getMetadata(ctx) 382 | if err != nil { 383 | return &output, err 384 | } 385 | 386 | // get user login 387 | var userLogin model.User 388 | userLogin.Pb.Id = ctx.Value(app.Ctx("userID")).(string) 389 | err = userLogin.Get(ctx, u.Db) 390 | if err != nil { 391 | return &output, err 392 | } 393 | 394 | userModel.Pb.Id = in.GetId() 395 | err = userModel.Get(ctx, u.Db) 396 | if err != nil { 397 | return &output, err 398 | } 399 | 400 | err = u.checkFilteringContent(ctx, &userLogin, &userModel) 401 | if err != nil { 402 | return &output, err 403 | } 404 | 405 | return &userModel.Pb, nil 406 | } 407 | 408 | // Delete func 409 | func (u *User) Delete(ctx context.Context, in *users.Id) (*users.MyBoolean, error) { 410 | var output users.MyBoolean 411 | output.Boolean = false 412 | var err error 413 | var userModel model.User 414 | 415 | if len(in.GetId()) == 0 { 416 | return &output, status.Error(codes.InvalidArgument, "Please supply valid id") 417 | } 418 | 419 | ctx, err = getMetadata(ctx) 420 | if err != nil { 421 | return &output, err 422 | } 423 | 424 | // get user login 425 | var userLogin model.User 426 | userLogin.Pb.Id = ctx.Value(app.Ctx("userID")).(string) 427 | err = userLogin.Get(ctx, u.Db) 428 | if err != nil { 429 | return &output, err 430 | } 431 | 432 | userModel.Pb.Id = in.GetId() 433 | err = userModel.Get(ctx, u.Db) 434 | if err != nil { 435 | return &output, err 436 | } 437 | 438 | err = u.checkFilteringContent(ctx, &userLogin, &userModel) 439 | if err != nil { 440 | return &output, err 441 | } 442 | 443 | err = userModel.Delete(ctx, u.Db) 444 | if err != nil { 445 | return &output, err 446 | } 447 | 448 | output.Boolean = true 449 | return &output, nil 450 | } 451 | 452 | // List func 453 | func (u *User) List(in *users.ListUserRequest, stream users.UserService_ListServer) error { 454 | ctx := stream.Context() 455 | var userModel model.User 456 | query, paramQueries, paginationResponse, err := userModel.ListQuery(ctx, u.Db, in) 457 | 458 | rows, err := u.Db.QueryContext(ctx, query, paramQueries...) 459 | if err != nil { 460 | return status.Error(codes.Internal, err.Error()) 461 | } 462 | defer rows.Close() 463 | paginationResponse.BranchId = in.GetBranchId() 464 | paginationResponse.CompanyId = in.GetCompanyId() 465 | paginationResponse.Pagination = in.GetPagination() 466 | 467 | for rows.Next() { 468 | err := contextError(ctx) 469 | if err != nil { 470 | return err 471 | } 472 | 473 | var pbUser users.User 474 | var pbGroup users.Group 475 | var regionID, branchID sql.NullString 476 | err = rows.Scan( 477 | &pbUser.Id, &pbUser.CompanyId, ®ionID, &branchID, &pbUser.Name, &pbUser.Email, 478 | &pbGroup.Id, &pbGroup.Name, 479 | ) 480 | if err != nil { 481 | return err 482 | } 483 | 484 | pbUser.RegionId = regionID.String 485 | pbUser.BranchId = branchID.String 486 | pbUser.Group = &pbGroup 487 | 488 | res := &users.ListUserResponse{ 489 | Pagination: paginationResponse, 490 | User: &pbUser, 491 | } 492 | 493 | err = stream.Send(res) 494 | if err != nil { 495 | return status.Errorf(codes.Unknown, "cannot send stream response: %v", err) 496 | } 497 | } 498 | 499 | return nil 500 | } 501 | 502 | // GetByToken func 503 | func (u *User) GetByToken(ctx context.Context, in *users.MyEmpty) (*users.User, error) { 504 | var output users.User 505 | var err error 506 | var userModel model.User 507 | 508 | ctx, err = getMetadataToken(ctx) 509 | if err != nil { 510 | return &output, err 511 | } 512 | 513 | // validate token 514 | isValid, email := token.ValidateToken(ctx.Value(app.Ctx("token")).(string)) 515 | if !isValid { 516 | return &output, status.Error(codes.Unauthenticated, "invalid token") 517 | } 518 | userModel.Pb.Email = email 519 | err = userModel.GetByEmail(ctx, u.Db) 520 | if err != nil { 521 | return &output, err 522 | } 523 | 524 | return &userModel.Pb, nil 525 | } 526 | 527 | func (u *User) checkFilteringContent(ctx context.Context, userLogin *model.User, userModel *model.User) error { 528 | var err error 529 | if userModel.Pb.GetCompanyId() != userLogin.Pb.GetCompanyId() { 530 | return status.Error(codes.PermissionDenied, "user not in your company") 531 | } 532 | 533 | if len(userLogin.Pb.GetRegionId()) > 0 && userModel.Pb.GetRegionId() != userLogin.Pb.GetRegionId() { 534 | return status.Error(codes.PermissionDenied, "user not in your region") 535 | } 536 | 537 | if len(userLogin.Pb.GetRegionId()) == 0 && len(userModel.Pb.GetRegionId()) > 0 { 538 | // check is region belongsto company 539 | var regionModel model.Region 540 | regionModel.Pb.Id = userModel.Pb.GetRegionId() 541 | err = regionModel.Get(ctx, u.Db) 542 | if err != nil { 543 | return err 544 | } 545 | if regionModel.Pb.GetCompanyId() != userLogin.Pb.GetCompanyId() { 546 | return status.Error(codes.PermissionDenied, "user not in your region") 547 | } 548 | } 549 | 550 | if len(userLogin.Pb.GetBranchId()) > 0 && userModel.Pb.GetBranchId() != userLogin.Pb.GetBranchId() { 551 | return status.Error(codes.PermissionDenied, "user not in your branch") 552 | } 553 | 554 | if len(userLogin.Pb.GetBranchId()) == 0 && len(userModel.Pb.GetBranchId()) > 0 { 555 | var branchModel model.Branch 556 | branchModel.Pb.Id = userModel.Pb.GetBranchId() 557 | err = branchModel.Get(ctx, u.Db) 558 | if err != nil { 559 | return err 560 | } 561 | 562 | if len(userLogin.Pb.GetRegionId()) > 0 && branchModel.Pb.GetRegionId() != userLogin.Pb.GetRegionId() { 563 | return status.Error(codes.PermissionDenied, "user not in your branch") 564 | } 565 | 566 | if branchModel.Pb.GetCompanyId() != userLogin.Pb.GetCompanyId() { 567 | return status.Error(codes.PermissionDenied, "user not in your branch") 568 | } 569 | } 570 | 571 | return nil 572 | } 573 | -------------------------------------------------------------------------------- /internal/service/utils.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "math/rand" 6 | "regexp" 7 | "time" 8 | 9 | "github.com/jacky-htg/erp-pkg/app" 10 | "google.golang.org/grpc/codes" 11 | "google.golang.org/grpc/metadata" 12 | "google.golang.org/grpc/status" 13 | ) 14 | 15 | func contextError(ctx context.Context) error { 16 | switch ctx.Err() { 17 | case context.Canceled: 18 | return status.Error(codes.Canceled, "request is canceled") 19 | case context.DeadlineExceeded: 20 | return status.Error(codes.DeadlineExceeded, "deadline is exceeded") 21 | default: 22 | return nil 23 | } 24 | } 25 | 26 | func getMetadataToken(ctx context.Context) (context.Context, error) { 27 | md, ok := metadata.FromIncomingContext(ctx) 28 | if !ok { 29 | return ctx, status.Errorf(codes.Unauthenticated, "metadata is not provided") 30 | } 31 | 32 | token := md["token"] 33 | if len(token) == 0 { 34 | return ctx, status.Errorf(codes.Unauthenticated, "authorization token is not provided") 35 | } 36 | 37 | ctx = context.WithValue(ctx, app.Ctx("token"), token[0]) 38 | 39 | return ctx, nil 40 | } 41 | 42 | func getMetadata(ctx context.Context) (context.Context, error) { 43 | md, ok := metadata.FromIncomingContext(ctx) 44 | if !ok { 45 | return ctx, status.Errorf(codes.Unauthenticated, "metadata is not provided") 46 | } 47 | 48 | userID := md["user_id"] 49 | if len(userID) == 0 { 50 | return ctx, status.Errorf(codes.Unauthenticated, "user_id is not provided") 51 | } 52 | 53 | ctx = context.WithValue(ctx, app.Ctx("userID"), userID[0]) 54 | 55 | companyID := md["company_id"] 56 | if len(companyID) == 0 { 57 | return ctx, status.Errorf(codes.Unauthenticated, "company_id is not provided") 58 | } 59 | 60 | ctx = context.WithValue(ctx, app.Ctx("companyID"), companyID[0]) 61 | 62 | return ctx, nil 63 | } 64 | 65 | func generateRandomPassword() string { 66 | rand.Seed(time.Now().UnixNano()) 67 | digits := "0123456789" 68 | specials := "~=+%^*/()[]{}/!@#$?|" 69 | all := "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + 70 | "abcdefghijklmnopqrstuvwxyz" + 71 | digits + specials 72 | length := 10 73 | buf := make([]byte, length) 74 | buf[0] = digits[rand.Intn(len(digits))] 75 | buf[1] = specials[rand.Intn(len(specials))] 76 | for i := 2; i < length; i++ { 77 | buf[i] = all[rand.Intn(len(all))] 78 | } 79 | rand.Shuffle(len(buf), func(i, j int) { 80 | buf[i], buf[j] = buf[j], buf[i] 81 | }) 82 | str := string(buf) 83 | 84 | return str 85 | } 86 | 87 | func checkStrongPassword(password string) error { 88 | if len(password) < 10 { 89 | return status.Error(codes.InvalidArgument, "password min 10 character") 90 | } 91 | 92 | num := `[0-9]{1}` 93 | az := `[a-z]{1}` 94 | AZ := `[A-Z]{1}` 95 | symbol := `[!@#~$%^&*()+|_]{1}` 96 | 97 | if b, err := regexp.MatchString(num, password); !b || err != nil { 98 | return status.Errorf(codes.InvalidArgument, "password need num :%v", err) 99 | } 100 | if b, err := regexp.MatchString(az, password); !b || err != nil { 101 | return status.Errorf(codes.InvalidArgument, "password need a_z :%v", err) 102 | } 103 | if b, err := regexp.MatchString(AZ, password); !b || err != nil { 104 | return status.Errorf(codes.InvalidArgument, "password need A_Z :%v", err) 105 | } 106 | if b, err := regexp.MatchString(symbol, password); !b || err != nil { 107 | return status.Errorf(codes.InvalidArgument, "password need symbol :%v", err) 108 | } 109 | 110 | return nil 111 | } 112 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | gen: 2 | protoc --proto_path=proto proto/users/*.proto --go_out=. --go-grpc_out=. 3 | 4 | init: 5 | go mod init user-service 6 | 7 | migrate: 8 | go run cmd/cli.go migrate 9 | 10 | seed: 11 | go run cmd/cli.go seed 12 | 13 | server: 14 | go run server.go 15 | 16 | build: 17 | env GOOS=linux GOARCH=amd64 go build -o user-service 18 | 19 | .PHONY: gen init migrate seed server build -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "net" 8 | "os" 9 | "time" 10 | 11 | "github.com/jacky-htg/erp-pkg/db/postgres" 12 | "github.com/jacky-htg/erp-pkg/db/redis" 13 | "github.com/jacky-htg/user-service/internal/config" 14 | "github.com/jacky-htg/user-service/internal/route" 15 | _ "github.com/lib/pq" 16 | "google.golang.org/grpc" 17 | ) 18 | 19 | const defaultPort = "8000" 20 | 21 | func main() { 22 | // lookup and setup env 23 | if _, ok := os.LookupEnv("PORT"); !ok { 24 | config.Setup(".env") 25 | } 26 | 27 | port := os.Getenv("PORT") 28 | if port == "" { 29 | port = defaultPort 30 | } 31 | 32 | // init log 33 | log := log.New(os.Stdout, "ERROR : ", log.LstdFlags|log.Lmicroseconds|log.Lshortfile) 34 | 35 | // create postgres database connection 36 | db, err := postgres.Open() 37 | if err != nil { 38 | log.Fatalf("connecting to db: %v", err) 39 | return 40 | } 41 | 42 | defer db.Close() 43 | 44 | // create redis cache connection 45 | cache, err := redis.NewCache(context.Background(), os.Getenv("REDIS_ADDRESS"), os.Getenv("REDIS_PASSWORD"), 24*time.Hour) 46 | if err != nil { 47 | log.Fatalf("cannot create redis connection: %v", err) 48 | return 49 | } 50 | 51 | // listen tcp port 52 | lis, err := net.Listen("tcp", ":"+port) 53 | if err != nil { 54 | log.Fatalf("failed to listen: %v", err) 55 | return 56 | } 57 | 58 | grpcServer := grpc.NewServer() 59 | 60 | // routing grpc services 61 | route.GrpcRoute(grpcServer, db, log, cache) 62 | if err := grpcServer.Serve(lis); err != nil { 63 | log.Fatalf("failed to serve: %s", err) 64 | return 65 | } 66 | fmt.Println("serve grpc on port: " + port) 67 | } 68 | --------------------------------------------------------------------------------