├── README.md ├── Auth_service ├── migrations │ ├── 000001_create_table.down.sql │ └── 000001_create_table.up.sql ├── config │ ├── model.conf │ ├── policy.csv │ └── getEnv.go ├── scripts │ └── gen-proto.sh ├── .env ├── Dockerfile ├── api │ ├── handler │ │ └── handler.go │ ├── api.go │ ├── middleware │ │ └── middleware.go │ └── token │ │ └── token.go ├── Makefile ├── kafka │ ├── producer.go │ ├── user.go │ └── consumer.go ├── storage │ ├── storage.go │ ├── postgres │ │ ├── postgres.go │ │ └── user.go │ └── redis │ │ └── redis.go ├── email │ ├── verification_email.html │ └── email.go ├── main.go ├── protos │ └── user.proto ├── go.mod ├── service │ └── user.go └── README.md ├── Budgeting_service ├── .env ├── scripts │ └── gen-proto.sh ├── Dockerfile ├── Makefile ├── protos │ ├── notification.proto │ ├── category.proto │ ├── goal.proto │ ├── account.proto │ ├── budget.proto │ └── transaction.proto ├── storage │ ├── testing │ │ ├── notification_test.go │ │ ├── category_test.go │ │ ├── account_test.go │ │ ├── goal_test.go │ │ ├── budget_test.go │ │ └── transaction_test.go │ ├── mongo │ │ ├── notification.go │ │ ├── mongo.go │ │ ├── category.go │ │ ├── account.go │ │ ├── goal.go │ │ ├── budget.go │ │ └── transaction.go │ └── storage.go ├── main.go ├── service │ ├── notification.go │ ├── account.go │ ├── category.go │ ├── goal.go │ ├── budget.go │ └── transaction.go ├── go.mod ├── models │ └── models.go ├── extra │ └── extra_func.go ├── genprotos │ └── notification_grpc.pb.go └── go.sum ├── Api_Gateway ├── .env ├── api │ ├── model.conf │ ├── handler │ │ ├── handler.go │ │ └── budgeting │ │ │ ├── handler.go │ │ │ ├── notification.go │ │ │ ├── account.go │ │ │ ├── category.go │ │ │ ├── goal.go │ │ │ ├── budget.go │ │ │ └── transaction.go │ ├── policy.csv │ ├── middleware │ │ └── middleware.go │ ├── api.go │ └── token │ │ └── token.go ├── scripts │ └── gen-proto.sh ├── Dockerfile ├── main.go ├── config │ └── config.go ├── Makefile ├── go.mod └── genprotos │ └── budgeting │ └── notification_grpc.pb.go ├── .github └── workflows │ └── github.yml └── docker-compose.yaml /README.md: -------------------------------------------------------------------------------- 1 | # PersonalFinanceTracker -------------------------------------------------------------------------------- /Auth_service/migrations/000001_create_table.down.sql: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Budgeting_service/.env: -------------------------------------------------------------------------------- 1 | MONGO_URI=mongodb://mongo-db:27017/budgeting 2 | -------------------------------------------------------------------------------- /Api_Gateway/.env: -------------------------------------------------------------------------------- 1 | DB_HOST=localhost 2 | LOGPATH=logs/info.log 3 | 4 | BUDGETING_PORT=:50055 5 | AUTH_PORT=:8000~ 6 | API_GATEWAY=:8082 -------------------------------------------------------------------------------- /Api_Gateway/api/model.conf: -------------------------------------------------------------------------------- 1 | [request_definition] 2 | r = sub, obj, act 3 | 4 | [policy_definition] 5 | p = sub, obj, act 6 | 7 | [role_definition] 8 | g = _,_ 9 | 10 | [policy_effect] 11 | e = some(where (p.eft == allow)) 12 | 13 | [matchers] 14 | m = r.sub == p.sub && r.obj == p.obj && r.act == p.act 15 | 16 | -------------------------------------------------------------------------------- /Api_Gateway/scripts/gen-proto.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | CURRENT_DIR=$1 3 | rm -rf ${CURRENT_DIR}/genproto 4 | for x in $(find ${CURRENT_DIR}/staffer_submodule/* -type d); do 5 | protoc -I=${x} -I=${CURRENT_DIR}/staffer_submodule -I /usr/local/go --go_out=${CURRENT_DIR} \ 6 | --go-grpc_out=${CURRENT_DIR} ${x}/*.proto 7 | done 8 | -------------------------------------------------------------------------------- /Auth_service/config/model.conf: -------------------------------------------------------------------------------- 1 | [request_definition] 2 | r = sub, obj, act 3 | 4 | [policy_definition] 5 | p = sub, obj, act 6 | 7 | [role_definition] 8 | g = _, _ 9 | 10 | [policy_effect] 11 | e = some(where (p.eft == allow)) 12 | 13 | [matchers] 14 | m = (r.sub == p.sub || g(r.sub, p.sub)) && r.obj == p.obj && r.act == p.act -------------------------------------------------------------------------------- /Api_Gateway/api/handler/handler.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | u "api/genprotos/auth" 5 | 6 | "google.golang.org/grpc" 7 | ) 8 | 9 | type UserService struct { 10 | Auth u.UserServiceClient 11 | } 12 | 13 | func NewUserService(authConn *grpc.ClientConn) *UserService { 14 | return &UserService{ 15 | Auth: u.NewUserServiceClient(authConn), 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Auth_service/scripts/gen-proto.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | CURRENT_DIR=$(pwd) 4 | 5 | rm -rf ${CURRENT_DIR}/genprotos 6 | mkdir -p ${CURRENT_DIR}/genprotos 7 | 8 | 9 | for x in $(find ${CURRENT_DIR}/protos* -type d); do 10 | protoc -I=${x} -I/usr/local/include \ 11 | --go_out=${CURRENT_DIR}/genprotos --go_opt=paths=source_relative \ 12 | --go-grpc_out=${CURRENT_DIR}/genprotos --go-grpc_opt=paths=source_relative ${x}/*.proto 13 | done 14 | -------------------------------------------------------------------------------- /Budgeting_service/scripts/gen-proto.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | CURRENT_DIR=$(pwd) 4 | 5 | rm -rf ${CURRENT_DIR}/genprotos 6 | mkdir -p ${CURRENT_DIR}/genprotos 7 | 8 | 9 | for x in $(find ${CURRENT_DIR}/protos* -type d); do 10 | protoc -I=${x} -I/usr/local/include \ 11 | --go_out=${CURRENT_DIR}/genprotos --go_opt=paths=source_relative \ 12 | --go-grpc_out=${CURRENT_DIR}/genprotos --go-grpc_opt=paths=source_relative ${x}/*.proto 13 | done 14 | -------------------------------------------------------------------------------- /Budgeting_service/Dockerfile: -------------------------------------------------------------------------------- 1 | # Stage 1: Build the application 2 | FROM golang:1.22.3-alpine AS build 3 | 4 | WORKDIR /app 5 | 6 | COPY . . 7 | 8 | RUN go build -o budgeting-service 9 | 10 | # Stage 2: Run the application 11 | FROM alpine:latest 12 | 13 | WORKDIR /root/ 14 | 15 | COPY --from=build /app/budgeting-service . 16 | 17 | # Uncomment if .env file exists 18 | COPY .env . 19 | 20 | EXPOSE 50055 21 | 22 | CMD ["./budgeting-service"] 23 | -------------------------------------------------------------------------------- /Auth_service/.env: -------------------------------------------------------------------------------- 1 | HTTP_PORT=:8000 2 | GRPC_USER_PORT=:8002 3 | 4 | SMTP_SENDER=sobirovazizbek717@gmail.com 5 | SMTP_PASSWORD=cvmv tosq sjdo yxqk 6 | 7 | POSTGRES_HOST=auth 8 | POSTGRES_PORT=5432 9 | POSTGRES_USER=azizbek 10 | POSTGRES_PASSWORD=123 11 | POSTGRES_DATABASE=auth 12 | 13 | REDIS_DB=0 14 | REDIS_HOST=localhost 15 | REDIS_PASSWORD= 16 | REDIS_PORT=:6370 17 | 18 | 19 | DEFAULT_OFFSET=1 20 | DEFAULT_LIMIT=10 21 | 22 | TOKEN_KEY=my_secret_key 23 | -------------------------------------------------------------------------------- /Api_Gateway/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.22.3 AS builder 2 | WORKDIR /app 3 | COPY go.mod ./ 4 | COPY go.sum ./ 5 | RUN go mod download 6 | COPY . . 7 | RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o myapp . 8 | 9 | FROM alpine:latest 10 | WORKDIR /app 11 | COPY --from=builder /app/myapp . 12 | COPY --from=builder /app/api/model.conf ./api/ 13 | COPY --from=builder /app/api/policy.csv ./api/ 14 | 15 | COPY .env . 16 | EXPOSE 8082 17 | CMD ["./myapp"] 18 | -------------------------------------------------------------------------------- /Auth_service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.22.3 AS builder 2 | WORKDIR /app 3 | COPY go.mod ./ 4 | COPY go.sum ./ 5 | RUN go mod download 6 | COPY . . 7 | RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o myapp . 8 | 9 | FROM alpine:latest 10 | WORKDIR /app 11 | COPY --from=builder /app/myapp . 12 | COPY --from=builder /app/config/model.conf ./config/ 13 | COPY --from=builder /app/config/policy.csv ./config/ 14 | 15 | COPY .env . 16 | EXPOSE 8000 17 | CMD ["./myapp"] 18 | -------------------------------------------------------------------------------- /Auth_service/api/handler/handler.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | pb "auth/genprotos" 5 | "auth/kafka" 6 | "auth/storage" 7 | r "auth/storage/redis" 8 | ) 9 | 10 | type Handler struct { 11 | UserStorage storage.StorageI 12 | User pb.UserServiceClient 13 | redis r.InMemoryStorageI 14 | producer kafka.KafkaProducer 15 | } 16 | 17 | func NewHandler(us pb.UserServiceClient, rdb r.InMemoryStorageI, pr kafka.KafkaProducer, userStorage storage.StorageI) *Handler { 18 | return &Handler{userStorage, us, rdb, pr } 19 | } 20 | -------------------------------------------------------------------------------- /Auth_service/migrations/000001_create_table.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TYPE role_user AS ENUM ('admin', 'user'); 2 | 3 | CREATE TABLE users ( 4 | id UUID PRIMARY KEY DEFAULT gen_random_uuid(), 5 | email VARCHAR(255) UNIQUE NOT NULL, 6 | password_hash VARCHAR(255) NOT NULL, 7 | first_name VARCHAR(255) NOT NULL, 8 | last_name VARCHAR(255) NOT NULL, 9 | role role_user NOT NULL, 10 | created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, 11 | updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, 12 | deleted_at BIGINT DEFAULT 0 13 | ); 14 | 15 | -------------------------------------------------------------------------------- /Auth_service/Makefile: -------------------------------------------------------------------------------- 1 | CURRENT_DIR=$(shell pwd) 2 | DBURL="postgres://azizbek:123@localhost:5432/farimsh?sslmode=disable" 3 | exp: 4 | export DBURL="postgres://azizbek:123@localhost:5432/farimsh?sslmode=disable" 5 | 6 | mig-up: 7 | migrate -path migrations -database ${DBURL} -verbose up 8 | 9 | mig-down: 10 | migrate -path migrations -database ${DBURL} -verbose down 11 | 12 | 13 | mig-create: 14 | migrate create -ext sql -dir migrations -seq create_table 15 | 16 | mig-insert: 17 | migrate create -ext sql -dir migrations -seq insert_table 18 | 19 | proto-gen: 20 | ./scripts/gen-proto.sh ${CURRENT_DIR} 21 | swag-gen: 22 | ~/go/bin/swag init -g ./api/api.go -o docs force 1 -------------------------------------------------------------------------------- /Budgeting_service/Makefile: -------------------------------------------------------------------------------- 1 | CURRENT_DIR=$(shell pwd) 2 | DBURL="postgres://azizbek:123@localhost:5432/farimsh?sslmode=disable" 3 | exp: 4 | export DBURL="postgres://azizbek:123@localhost:5432/farimsh?sslmode=disable" 5 | 6 | mig-up: 7 | migrate -path migrations -database ${DBURL} -verbose up 8 | 9 | mig-down: 10 | migrate -path migrations -database ${DBURL} -verbose down 11 | 12 | 13 | mig-create: 14 | migrate create -ext sql -dir migrations -seq create_table 15 | 16 | mig-insert: 17 | migrate create -ext sql -dir migrations -seq insert_table 18 | 19 | proto-gen: 20 | ./scripts/gen-proto.sh ${CURRENT_DIR} 21 | swag-gen: 22 | ~/go/bin/swag init -g ./api/api.go -o docs force 1 -------------------------------------------------------------------------------- /.github/workflows/github.yml: -------------------------------------------------------------------------------- 1 | name: Hello World 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout our repository 16 | uses: actions/checkout@v4 17 | 18 | - name: Deploy 19 | uses: appleboy/ssh-action@master 20 | with: 21 | host: ${{ secrets.SSH_HOST }} 22 | username: ${{ secrets.SSH_USER }} 23 | key: ${{ secrets.SSH_PASSWORD }} 24 | port: ${{ secrets.SSH_PORT }} 25 | script: | 26 | pwd 27 | ls 28 | cd /home/ubuntu/PersonalFinanceTracker 29 | sudo docker compose down 30 | sudo docker compose up -d 31 | 32 | sudo docker compose up -d -------------------------------------------------------------------------------- /Budgeting_service/protos/notification.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = "genprotos/"; 4 | 5 | package notification; 6 | 7 | service NotificationService{ 8 | rpc CreateNotification(CreateNotificationRequest) returns(CreateNotificationResponse){} 9 | rpc GetNotification(GetNotificationRequest) returns(GetNotificationResponse){} 10 | } 11 | 12 | message CreateNotificationRequest{ 13 | Notification notification = 1; 14 | } 15 | 16 | message CreateNotificationResponse{} 17 | 18 | message Notification{ 19 | string user_id = 1; 20 | string id = 2; 21 | string message = 3; 22 | } 23 | 24 | message GetNotificationRequest{ 25 | int32 limit = 1; 26 | int32 page = 2; 27 | } 28 | 29 | message GetNotificationResponse{ 30 | repeated Notification notification = 1; 31 | } 32 | -------------------------------------------------------------------------------- /Auth_service/kafka/producer.go: -------------------------------------------------------------------------------- 1 | package kafka 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/segmentio/kafka-go" 7 | ) 8 | 9 | type KafkaProducer interface { 10 | ProduceMessages(topic string, message []byte) error 11 | Close() error 12 | } 13 | 14 | type Producer struct { 15 | writer *kafka.Writer 16 | } 17 | 18 | func NewKafkaProducer(brokers []string) (KafkaProducer, error) { 19 | writer := &kafka.Writer{ 20 | Addr: kafka.TCP(brokers...), 21 | AllowAutoTopicCreation: true, 22 | } 23 | return &Producer{writer: writer}, nil 24 | } 25 | 26 | func (p *Producer) ProduceMessages(topic string, message []byte) error { 27 | return p.writer.WriteMessages(context.Background(), kafka.Message{ 28 | Topic: topic, 29 | Value: message, 30 | }) 31 | } 32 | 33 | func (p *Producer) Close() error { 34 | return p.writer.Close() 35 | } 36 | -------------------------------------------------------------------------------- /Auth_service/storage/storage.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import pb "auth/genprotos" 4 | 5 | type StorageI interface { 6 | User() UserI 7 | } 8 | 9 | type UserI interface { 10 | RegisterUser(user *pb.RegisterUserRequest) (*pb.RegisterUserResponse, error) 11 | LoginUser(user *pb.LoginUserRequest) (*pb.LoginUserResponse, error) 12 | GetByIdUser(id *pb.GetByIdUserRequest) (*pb.GetByIdUserResponse, error) 13 | UpdateUser(req *pb.UpdateUserRequest) (*pb.UpdateUserResponse, error) 14 | DeleteUser(id *pb.DeleteUserRequest) (*pb.DeleteUserResponse, error) 15 | ChangePassword(password *pb.ChangePasswordRequest) (*pb.ChangePasswordResponse, error) 16 | ForgotPassword(forgotPass *pb.ForgotPasswordRequest) (*pb.ForgotPasswordResponse, error) 17 | GetUserByEmail(email string) (*pb.UpdateUserResponse, error) 18 | ResetPassword(resetPass *pb.ResetPasswordRequest) (*pb.ResetPasswordResponse, error) 19 | } -------------------------------------------------------------------------------- /Auth_service/storage/postgres/postgres.go: -------------------------------------------------------------------------------- 1 | package postgres 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | 7 | "auth/config" 8 | u "auth/storage" 9 | 10 | _ "github.com/lib/pq" 11 | ) 12 | 13 | type Storage struct { 14 | Db *sql.DB 15 | Users u.UserI 16 | } 17 | 18 | func NewPostgresStorage() (u.StorageI, error) { 19 | config := config.Load() 20 | con := fmt.Sprintf("postgres://%s:%s@%s:%d/%s?sslmode=disable", 21 | config.PostgresUser, config.PostgresPassword, 22 | config.PostgresHost, config.PostgresPort, 23 | config.PostgresDatabase) 24 | db, err := sql.Open("postgres", con) 25 | if err != nil { 26 | return nil, err 27 | } 28 | 29 | err = db.Ping() 30 | if err != nil { 31 | return nil, err 32 | } 33 | 34 | return &Storage{Db: db}, err 35 | } 36 | 37 | func (s *Storage) User() u.UserI { 38 | if s.Users == nil { 39 | s.Users = &UserStorage{s.Db} 40 | } 41 | 42 | return s.Users 43 | } 44 | -------------------------------------------------------------------------------- /Auth_service/kafka/user.go: -------------------------------------------------------------------------------- 1 | // kafka/user.go 2 | package kafka 3 | 4 | import ( 5 | "context" 6 | "encoding/json" 7 | "log" 8 | pb "auth/genprotos" 9 | "auth/service" 10 | ) 11 | 12 | func UserCreateHandler(userServ *service.UserService) func(message []byte) { 13 | return func(message []byte) { 14 | var user pb.RegisterUserRequest 15 | if err := json.Unmarshal(message, &user); err != nil { 16 | log.Printf("Cannot unmarshal JSON: %v", err) 17 | return 18 | } 19 | 20 | res, err := userServ.RegisterUser(context.Background(), &user) 21 | if err != nil { 22 | if err.Error() == "user already exists" { 23 | log.Printf("User already exists: %v", user.Email) 24 | } else { 25 | log.Printf("Cannot create User via Kafka: %v", err) 26 | } 27 | return 28 | } 29 | log.Printf("Created User: %+v", res) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Api_Gateway/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "api/api" 8 | "api/config" 9 | 10 | "google.golang.org/grpc" 11 | "google.golang.org/grpc/credentials/insecure" 12 | ) 13 | 14 | func main() { 15 | cnf := config.Load() 16 | authservice, err := grpc.NewClient(cnf.AUTH_PORT, grpc.WithTransportCredentials(insecure.NewCredentials())) // Update the address 17 | if err != nil { 18 | log.Fatalf("Failed to connect to authservice service %v", err) 19 | } 20 | defer authservice.Close() 21 | 22 | companyService, err := grpc.NewClient("budgeting_service"+cnf.BUDGETING_PORT, grpc.WithTransportCredentials(insecure.NewCredentials())) 23 | if err != nil { 24 | log.Fatalf("Failed to connect to learning service service %v", err) 25 | } 26 | 27 | router := api.NewGin(companyService) 28 | 29 | fmt.Println("API Gateway running on http://localhost:8082") 30 | if err := router.Run(":8082"); err != nil { 31 | log.Fatalf("Failed to connect to gin engine: %v", err) 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /Auth_service/config/policy.csv: -------------------------------------------------------------------------------- 1 | p, unauthorized, /swagger/index.html, GET 2 | p, unauthorized, /swagger/swagger-ui.css, GET 3 | p, unauthorized, /swagger/swagger-ui-bundle.js, GET 4 | p, unauthorized, /swagger/favicon-32x32.png, GET 5 | p, unauthorized, /swagger/favicon-16x16.png, GET 6 | p, unauthorized, /swagger/swagger-ui-standalone-preset.js, GET 7 | p, unauthorized, /swagger/swagger/doc.json, GET 8 | p, unauthorized, /swagger/doc.json, GET 9 | p, unauthorized, /swagger/*any, GET 10 | p, unauthorized, /user/login, POST 11 | p, unauthorized, /user/register, POST 12 | 13 | p, user, /user/change-password, POST 14 | p, user, /user/get-profil, GET 15 | p, user, /user/forgot-password, POST 16 | p, user, /user/reset-password, POST 17 | p, user, /user/update-profil, PUT 18 | p, user, /user/delete-profil, DELETE 19 | 20 | g, admin, user 21 | p, admin, /user/register, POST 22 | p, admin, /user/getall, GET 23 | p, admin, /user/get-by-id/:id, GET 24 | p, admin, /user/update/:id, PUT 25 | p, admin, /user/delete/:id, DELETE 26 | -------------------------------------------------------------------------------- /Budgeting_service/storage/testing/notification_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "testing" 5 | 6 | pb "budgeting/genprotos" 7 | "budgeting/storage/mongo" 8 | 9 | "github.com/stretchr/testify/assert" 10 | "go.mongodb.org/mongo-driver/bson/primitive" 11 | "go.mongodb.org/mongo-driver/mongo/integration/mtest" 12 | ) 13 | 14 | func TestCreateNotification(t *testing.T) { 15 | mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock)) 16 | 17 | mt.Run("CreateNotification_Success", func(mt *mtest.T) { 18 | mt.AddMockResponses(mtest.CreateSuccessResponse()) 19 | 20 | budgetService := mongo.NewNotification(mt.DB) 21 | 22 | req := &pb.CreateNotificationRequest{ 23 | Notification: &pb.Notification{ 24 | Id: primitive.NewObjectID().Hex(), 25 | UserId: "user123", 26 | Message: "nma gap", 27 | }, 28 | } 29 | 30 | resp, err := budgetService.CreateNotification(req) 31 | 32 | assert.NoError(t, err) 33 | assert.NotNil(t, resp) 34 | assert.Equal(t, &pb.CreateNotificationResponse{}, resp) 35 | }) 36 | 37 | } 38 | -------------------------------------------------------------------------------- /Api_Gateway/api/handler/budgeting/handler.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | budgeting "api/genprotos/budgeting" 5 | 6 | "google.golang.org/grpc" 7 | ) 8 | 9 | type BudgetingHandler struct { 10 | Account budgeting.AccountServiceClient 11 | Budget budgeting.BudgetServiceClient 12 | Category budgeting.CategoryServiceClient 13 | Goal budgeting.GoalServiceClient 14 | Transaction budgeting.TransactionServiceClient 15 | Notification budgeting.NotificationServiceClient 16 | } 17 | 18 | func NewBudgetingHandler(budgetingConn *grpc.ClientConn) *BudgetingHandler { 19 | return &BudgetingHandler{ 20 | Account: budgeting.NewAccountServiceClient(budgetingConn), 21 | Budget: budgeting.NewBudgetServiceClient(budgetingConn), 22 | Category: budgeting.NewCategoryServiceClient(budgetingConn), 23 | Goal: budgeting.NewGoalServiceClient(budgetingConn), 24 | Transaction: budgeting.NewTransactionServiceClient(budgetingConn), 25 | Notification: budgeting.NewNotificationServiceClient(budgetingConn), 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Budgeting_service/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net" 6 | 7 | pb "budgeting/genprotos" 8 | "budgeting/service" 9 | "budgeting/storage/mongo" 10 | "google.golang.org/grpc" 11 | ) 12 | 13 | func main(){ 14 | db, err := mongo.ConnectMongo() 15 | if err != nil { 16 | log.Fatal(err) 17 | } 18 | 19 | liss, err := net.Listen("tcp", ":50055") 20 | if err != nil { 21 | log.Fatal(err) 22 | } 23 | 24 | s := grpc.NewServer() 25 | pb.RegisterAccountServiceServer(s, service.NewAccountService(&db)) 26 | pb.RegisterBudgetServiceServer(s, service.NewBudgetService(&db)) 27 | pb.RegisterCategoryServiceServer(s, service.NewCategoryService(&db)) 28 | pb.RegisterGoalServiceServer(s, service.NewGoalService(&db)) 29 | pb.RegisterTransactionServiceServer(s, service.NewTransactionService(&db)) 30 | pb.RegisterNotificationServiceServer(s, service.NewNotificationService(&db)) 31 | 32 | 33 | log.Printf("server listening at %v", liss.Addr()) 34 | if err := s.Serve(liss); err != nil { 35 | log.Fatalf("failed to serve %v", err) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Api_Gateway/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/joho/godotenv" 8 | "github.com/spf13/cast" 9 | ) 10 | 11 | type Config struct { 12 | AUTH_PORT string 13 | BUDGETING_PORT string 14 | 15 | DB_HOST string 16 | DB_PORT int 17 | DB_USER string 18 | DB_PASSWORD string 19 | DB_NAME string 20 | 21 | LOG_PATH string 22 | TokenKey string 23 | } 24 | 25 | func Load() Config { 26 | if err := godotenv.Load(); err != nil { 27 | fmt.Println("No .env file found") 28 | } 29 | 30 | config := Config{} 31 | 32 | config.AUTH_PORT = cast.ToString(getEnv("AUTH_PORT", ":8002")) 33 | config.BUDGETING_PORT = cast.ToString(getEnv("BUDGETING_PORT", ":50055")) 34 | config.LOG_PATH = cast.ToString(getEnv("LOG_PATH", "logs/info.log")) 35 | config.TokenKey = cast.ToString(getEnv("TOKEN_KEY", "my_secret_key")) 36 | 37 | return config 38 | } 39 | 40 | func getEnv(key string, defaultValue interface{}) interface{} { 41 | val, exists := os.LookupEnv(key) 42 | 43 | if exists { 44 | return val 45 | } 46 | 47 | return defaultValue 48 | } 49 | -------------------------------------------------------------------------------- /Budgeting_service/service/notification.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | pb "budgeting/genprotos" 5 | "budgeting/storage" 6 | "context" 7 | 8 | "github.com/google/uuid" 9 | ) 10 | 11 | type NotificationService struct { 12 | stg storage.StorageI 13 | pb.UnimplementedNotificationServiceServer 14 | } 15 | 16 | func NewNotificationService(stg *storage.StorageI) *NotificationService { 17 | return &NotificationService{stg: *stg} 18 | } 19 | 20 | func (s *NotificationService) CreateNotification(c context.Context, req *pb.CreateNotificationRequest) (*pb.CreateNotificationResponse, error) { 21 | id := uuid.NewString() 22 | req.Notification.Id = id 23 | _, err := s.stg.Notification().CreateNotification(req) 24 | if err != nil { 25 | return nil, err 26 | } 27 | return &pb.CreateNotificationResponse{}, nil 28 | } 29 | 30 | func (s *NotificationService) GetNotification(c context.Context, req *pb.GetNotificationRequest) (*pb.GetNotificationResponse, error) { 31 | res, err := s.stg.Notification().GetNotification(req) 32 | if err != nil { 33 | return nil, err 34 | } 35 | return res, nil 36 | } 37 | -------------------------------------------------------------------------------- /Budgeting_service/go.mod: -------------------------------------------------------------------------------- 1 | module budgeting 2 | 3 | go 1.22.3 4 | 5 | require ( 6 | github.com/google/uuid v1.6.0 7 | github.com/stretchr/testify v1.9.0 8 | go.mongodb.org/mongo-driver v1.16.1 9 | google.golang.org/grpc v1.65.0 10 | google.golang.org/protobuf v1.34.2 11 | ) 12 | 13 | require ( 14 | github.com/davecgh/go-spew v1.1.1 // indirect 15 | github.com/golang/snappy v0.0.4 // indirect 16 | github.com/klauspost/compress v1.13.6 // indirect 17 | github.com/montanaflynn/stats v0.7.1 // indirect 18 | github.com/pmezard/go-difflib v1.0.0 // indirect 19 | github.com/xdg-go/pbkdf2 v1.0.0 // indirect 20 | github.com/xdg-go/scram v1.1.2 // indirect 21 | github.com/xdg-go/stringprep v1.0.4 // indirect 22 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect 23 | golang.org/x/crypto v0.23.0 // indirect 24 | golang.org/x/net v0.25.0 // indirect 25 | golang.org/x/sync v0.7.0 // indirect 26 | golang.org/x/sys v0.20.0 // indirect 27 | golang.org/x/text v0.15.0 // indirect 28 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect 29 | gopkg.in/yaml.v3 v3.0.1 // indirect 30 | ) 31 | -------------------------------------------------------------------------------- /Api_Gateway/Makefile: -------------------------------------------------------------------------------- 1 | CURRENT_DIR=$(shell pwd) 2 | 3 | proto-gen: 4 | ./scripts/gen-proto.sh ${CURRENT_DIR} 5 | 6 | exp: 7 | export DBURL='postgres://postgres:root@localhost:5432/staffer?sslmode=disable' 8 | 9 | mig-run: 10 | migrate create -ext sql -dir migrations -seq create_table 11 | 12 | mig-up: 13 | migrate -database 'postgres://postgres:root@localhost:5432/staffer?sslmode=disable' -path migrations up 14 | 15 | mig-down: 16 | migrate -database 'postgres://postgres:root@localhost:5432/staffer?sslmode=disable' -path migrations down 17 | 18 | gen-proto: 19 | go install google.golang.org/protobuf/cmd/protoc-gen-go@latest 20 | go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest 21 | export PATH="$PATH:$(go env GOPATH)/bin" 22 | protoc --go_out=. --go-grpc_out=. city_submodule/*.proto 23 | 24 | gen-protoAll: 25 | #proto fileni hammasini bittada generatsiya qilish 26 | protoc --go_out=./ \ 27 | --go-grpc_out=./ \ 28 | city_submodule/*.proto 29 | 30 | swag-gen: 31 | ~/go/bin/swag init -g ./api/api.go -o docs 32 | 33 | # PROTO_DIR=proto 34 | # OUT_DIR=genproto 35 | 36 | # proto-genn: 37 | # protoc -I=$(PROTO_DIR) --go_out=$(OUT_DIR) --go-grpc_out=$(OUT_DIR) $(PROTO_DIR)/*.proto 38 | -------------------------------------------------------------------------------- /Api_Gateway/api/handler/budgeting/notification.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | pb "api/genprotos/budgeting" 5 | "context" 6 | "net/http" 7 | "strconv" 8 | 9 | "github.com/gin-gonic/gin" 10 | ) 11 | 12 | // @Summary ListNotifications 13 | // @Description ListNotifications 14 | // @Tags Notification 15 | // @Accept json 16 | // @Produce json 17 | // @Security BearerAuth 18 | // @Param limit query int false "Limit" 19 | // @Param page query int false "Page" 20 | // @Success 200 {object} pb.GetNotificationResponse 21 | // @Failure 400 {string} string "Bad Request" 22 | // @Router /notification/get [get] 23 | func (h *BudgetingHandler) ListNotifications(ctx *gin.Context) { 24 | 25 | defaultLimit := 10 26 | defaultPage := 1 27 | 28 | limitStr := ctx.Query("limit") 29 | pageStr := ctx.Query("page") 30 | 31 | limit, err := strconv.Atoi(limitStr) 32 | if err != nil || limit <= 0 { 33 | limit = defaultLimit 34 | } 35 | 36 | page, err := strconv.Atoi(pageStr) 37 | if err != nil || page <= 0 { 38 | page = defaultPage 39 | } 40 | 41 | req := &pb.GetNotificationRequest{ 42 | Limit: int32(limit), 43 | Page: int32(page), 44 | } 45 | 46 | res, err := h.Notification.GetNotification(context.Background(), req) 47 | if err != nil { 48 | ctx.JSON(http.StatusBadRequest, err.Error()) 49 | return 50 | } 51 | 52 | ctx.JSON(http.StatusOK, res) 53 | } 54 | -------------------------------------------------------------------------------- /Budgeting_service/protos/category.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = "genprotos/"; 4 | 5 | package category; 6 | 7 | service CategoryService { 8 | rpc CreateCategory(CreateCategoryRequest) returns(CreateCategoryResponse){} 9 | rpc UpdateCategory(UpdateCategoryRequest) returns(UpdateCategoryResponse){} 10 | rpc DeleteCategory(DeleteCategoryRequest) returns(DeleteCategoryResponse){} 11 | rpc GetCategory(GetCategoryRequest)returns(GetCategoryResponse){} 12 | rpc ListCategories(ListCategoriesRequest) returns(ListCategoriesResponse){} 13 | } 14 | 15 | message CreateCategoryRequest{ 16 | Category category = 1; 17 | } 18 | 19 | message CreateCategoryResponse{} 20 | 21 | message Category{ 22 | string id = 1; 23 | string user_id = 2; 24 | string name = 3; 25 | string type = 4; 26 | } 27 | 28 | message UpdateCategoryRequest{ 29 | Category category = 1; 30 | } 31 | 32 | message UpdateCategoryResponse{} 33 | 34 | 35 | message GetCategoryRequest{ 36 | string id = 1; 37 | } 38 | 39 | message GetCategoryResponse{ 40 | Category category = 1; 41 | } 42 | 43 | message DeleteCategoryRequest{ 44 | string id = 1; 45 | } 46 | 47 | message DeleteCategoryResponse{} 48 | 49 | message ListCategoriesRequest{ 50 | int32 limit = 1; 51 | int32 page = 2; 52 | } 53 | 54 | message ListCategoriesResponse{ 55 | repeated Category categories = 1; 56 | } -------------------------------------------------------------------------------- /Auth_service/email/verification_email.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Email Verification 8 | 46 | 47 | 48 |
49 |

Email Verification

50 |

Your verification code is: {{ .Code }}

51 |
52 | 53 | 54 | -------------------------------------------------------------------------------- /Auth_service/api/api.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/casbin/casbin/v2" 7 | "github.com/gin-gonic/gin" 8 | files "github.com/swaggo/files" 9 | ginSwagger "github.com/swaggo/gin-swagger" 10 | "auth/api/handler" 11 | middleware "auth/api/middleware" 12 | _ "auth/docs" 13 | ) 14 | 15 | // @title auth service API 16 | // @version 1.0 17 | // @description auth service API 18 | // @BasePath / 19 | // @securityDefinitions.apikey BearerAuth 20 | // @in header 21 | // @name Authorization 22 | func NewGin(h *handler.Handler) *gin.Engine { 23 | e, err := casbin.NewEnforcer("config/model.conf", "config/policy.csv") 24 | if err != nil { 25 | panic(err) 26 | } 27 | 28 | err = e.LoadPolicy() 29 | if err != nil { 30 | log.Fatal("casbin error load policy: ", err) 31 | panic(err) 32 | } 33 | 34 | r := gin.Default() 35 | 36 | r.Use(middleware.NewAuth(e)) 37 | 38 | u := r.Group("/user") 39 | u.POST("/register", h.RegisterUser) 40 | u.PUT("/update/:id", h.UpdateUser) 41 | u.DELETE("/delete/:id", h.DeleteUser) 42 | u.GET("/get-by-id/:id", h.GetbyIdUser) 43 | u.POST("/login", h.LoginUser) 44 | u.POST("/change-password", h.ChangePassword) 45 | u.POST("/forgot-password", h.ForgotPassword) 46 | u.POST("/reset-password", h.ResetPassword) 47 | u.GET("/get-profil", h.GetProfil) 48 | u.PUT("/update-profil", h.UpdateProfil) 49 | u.DELETE("/delete-profil", h.DeleteProfil) 50 | 51 | 52 | url := ginSwagger.URL("/swagger/doc.json") 53 | r.GET("/swagger/*any", ginSwagger.WrapHandler(files.Handler, url)) 54 | 55 | return r 56 | } 57 | -------------------------------------------------------------------------------- /Auth_service/storage/redis/redis.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/redis/go-redis/v9" 9 | "auth/config" 10 | e "auth/email" 11 | ) 12 | 13 | type InMemoryStorageI interface { 14 | Set(key, value string, exp time.Duration) error 15 | Get(key string) (string, error) 16 | Del(key string) error 17 | SaveToken(email string, token string, exp time.Duration) error 18 | } 19 | 20 | type storageRedis struct { 21 | client *redis.Client 22 | } 23 | 24 | func NewInMemoryStorage(rdb *redis.Client) InMemoryStorageI { 25 | return &storageRedis{ 26 | client: rdb, 27 | } 28 | } 29 | 30 | func (r *storageRedis) Set(key, value string, exp time.Duration) error { 31 | err := r.client.Set(context.Background(), key, value, exp).Err() 32 | if err != nil { 33 | return err 34 | } 35 | return nil 36 | } 37 | 38 | func (r *storageRedis) Get(key string) (string, error) { 39 | val, err := r.client.Get(context.Background(), key).Result() 40 | if err != nil { 41 | return "", err 42 | } 43 | return val, nil 44 | } 45 | 46 | func (r *storageRedis) Del(key string) error { 47 | err := r.client.Del(context.Background(), key).Err() 48 | if err != nil { 49 | return err 50 | } 51 | return nil 52 | } 53 | 54 | func (r *storageRedis) SaveToken(email string, token string, exp time.Duration) error { 55 | cnf := config.Load() 56 | fmt.Println(token) 57 | st := e.SendEmailRequest{To: []string{email}, Type: "Verification email", Subject: "Vertification", Code: token} 58 | err := e.SendEmail(&cnf, &st) 59 | if err != nil { 60 | return err 61 | } 62 | return r.Set(email, token, exp) 63 | } 64 | -------------------------------------------------------------------------------- /Auth_service/email/email.go: -------------------------------------------------------------------------------- 1 | package email 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "html/template" 7 | "log" 8 | "net/smtp" 9 | "os" 10 | 11 | "auth/config" 12 | ) 13 | 14 | type SendEmailRequest struct { 15 | To []string 16 | Type string 17 | Body map[string]string 18 | Subject string 19 | Code string 20 | } 21 | 22 | const ( 23 | VerificationEmail = "Verification email" 24 | ForgotPasswordEmail = "forgot_password_email" 25 | ) 26 | 27 | func SendEmail(cfg *config.Config, req *SendEmailRequest) error { 28 | from := cfg.SmtpSender 29 | to := req.To 30 | 31 | password := cfg.SmtpPassword 32 | 33 | var body bytes.Buffer 34 | 35 | templatePath := getTemplatePath(req.Type) 36 | t, err := template.ParseFiles(templatePath) 37 | if err != nil { 38 | log.Fatal(err) 39 | return err 40 | } 41 | 42 | err = t.Execute(&body, req) 43 | if err != nil { 44 | log.Fatal(err) 45 | return err 46 | } 47 | 48 | mime := "MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n" 49 | subject := fmt.Sprintf("Subject: %s\n", req.Subject) 50 | msg := []byte(subject + mime + body.String()) 51 | 52 | auth := smtp.PlainAuth("", from, password, "smtp.gmail.com") 53 | err = smtp.SendMail("smtp.gmail.com:587", auth, from, to, msg) 54 | if err != nil { 55 | return err 56 | } 57 | return nil 58 | } 59 | 60 | func getTemplatePath(emailType string) string { 61 | dir, err := os.Getwd() 62 | if err != nil { 63 | log.Fatal(err) 64 | } 65 | switch emailType { 66 | case VerificationEmail: 67 | return dir + "/email/verification_email.html" 68 | case ForgotPasswordEmail: 69 | return dir + "/email/forgot_password_email.html" 70 | } 71 | 72 | return "" 73 | } 74 | -------------------------------------------------------------------------------- /Budgeting_service/models/models.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type Account struct { 4 | ID string `bson:"_id,omitempty"` 5 | UserID string `bson:"user_id"` 6 | Name string `bson:"name"` 7 | Type string `bson:"type"` 8 | Balance float32 `bson:"balance"` 9 | Currency string `bson:"currency"` 10 | } 11 | 12 | type Category struct { 13 | ID string `bson:"_id,omitempty"` 14 | UserID string `bson:"user_id"` 15 | Name string `bson:"name"` 16 | Type string `bson:"type"` 17 | } 18 | 19 | type Budget struct { 20 | ID string `bson:"_id,omitempty"` 21 | UserID string `bson:"user_id"` 22 | CategoryID string `bson:"category_id"` 23 | Period string `bson:"period"` 24 | Amount float32 `bson:"amount"` 25 | StartDate string `bson:"start_date"` 26 | EndDate string `bson:"end_date"` 27 | } 28 | 29 | type Goal struct { 30 | ID string `bson:"_id,omitempty"` 31 | UserID string `bson:"user_id"` 32 | Name string `bson:"name"` 33 | TargetAmount float32 `bson:"target_amount"` 34 | CurrentAmount float32 `bson:"current_amount"` 35 | Deadline string `bson:"deadline"` 36 | Status string `bson:"status"` 37 | } 38 | 39 | type Transaction struct { 40 | ID string `bson:"_id,omitempty"` 41 | UserID string `bson:"user_id"` 42 | CategoryID string `bson:"category_id"` 43 | AccountID string `bson:"account_id"` 44 | Amount float32 `bson:"amount"` 45 | Type string `bson:"type"` 46 | Description string `bson:"description"` 47 | Date string `bson:"date"` 48 | } 49 | 50 | type Notification struct{ 51 | ID string `bson:"_id,omitempty"` 52 | UserID string `bson:"user_id"` 53 | Message string `bson:"message"` 54 | } -------------------------------------------------------------------------------- /Budgeting_service/storage/mongo/notification.go: -------------------------------------------------------------------------------- 1 | package mongo 2 | 3 | import ( 4 | "context" 5 | 6 | e "budgeting/extra" 7 | pb "budgeting/genprotos" 8 | m "budgeting/models" 9 | 10 | "go.mongodb.org/mongo-driver/bson" 11 | "go.mongodb.org/mongo-driver/mongo" 12 | "go.mongodb.org/mongo-driver/mongo/options" 13 | ) 14 | 15 | type Notification struct { 16 | mongo *mongo.Collection 17 | } 18 | 19 | func NewNotification(db *mongo.Database) *Notification { 20 | return &Notification{mongo: db.Collection("Notification")} 21 | } 22 | 23 | func (s *Notification) CreateNotification(req *pb.CreateNotificationRequest) (*pb.CreateNotificationResponse, error) { 24 | bsonNotification := e.NotificationToBson(req.Notification) 25 | _, err := s.mongo.InsertOne(context.TODO(), bsonNotification) 26 | if err != nil { 27 | return nil, err 28 | } 29 | return &pb.CreateNotificationResponse{}, nil 30 | } 31 | 32 | func (s *Notification) GetNotification(req *pb.GetNotificationRequest) (*pb.GetNotificationResponse, error) { 33 | limit := req.Limit 34 | page := req.Page 35 | skip := (page - 1) * limit 36 | 37 | cursor, err := s.mongo.Find(context.TODO(), bson.M{}, options.Find().SetSkip(int64(skip)).SetLimit(int64(limit))) 38 | if err != nil { 39 | return nil, err 40 | } 41 | defer cursor.Close(context.TODO()) 42 | 43 | var accounts []*pb.Notification 44 | for cursor.Next(context.TODO()) { 45 | var bsonAccount m.Notification 46 | if err := cursor.Decode(&bsonAccount); err != nil { 47 | return nil, err 48 | } 49 | account := e.BsonToNotification(&bsonAccount) 50 | accounts = append(accounts, account) 51 | } 52 | 53 | if err := cursor.Err(); err != nil { 54 | return nil, err 55 | } 56 | 57 | return &pb.GetNotificationResponse{ 58 | Notification: accounts, 59 | }, nil 60 | } 61 | -------------------------------------------------------------------------------- /Budgeting_service/protos/goal.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = "genprotos/"; 4 | 5 | package goal; 6 | 7 | service GoalService { 8 | rpc CreateGoal(CreateGoalRequest) returns(CreateGoalResponse){} 9 | rpc UpdateGoal(UpdateGoalRequest) returns(UpdateGoalResponse){} 10 | rpc DeleteGoal(DeleteGoalRequest) returns(DeleteGoalResponse){} 11 | rpc GetGoal(GetGoalRequest)returns(GetGoalResponse){} 12 | rpc ListGoals(ListGoalsRequest) returns(ListGoalsResponse){} 13 | rpc GenerateGoalProgressReport(GenerateGoalProgressReportRequest)returns(GenerateGoalProgressReportResponse){} 14 | } 15 | 16 | message CreateGoalRequest{ 17 | Goal goal = 1; 18 | } 19 | 20 | 21 | message CreateGoalResponse{} 22 | 23 | message Goal{ 24 | string id = 1; 25 | string user_id = 2; 26 | string name = 3; 27 | float target_amount = 4; 28 | float current_amount = 5; 29 | string deadline = 6; 30 | string status = 7; 31 | } 32 | 33 | message UpdateGoalRequest{ 34 | Goal goal = 1; 35 | } 36 | 37 | message UpdateGoalResponse{} 38 | 39 | 40 | message GetGoalRequest{ 41 | string id = 1; 42 | } 43 | 44 | message GetGoalResponse{ 45 | Goal goal = 1; 46 | } 47 | 48 | message DeleteGoalRequest{ 49 | string id = 1; 50 | } 51 | 52 | message DeleteGoalResponse{} 53 | 54 | message ListGoalsRequest{ 55 | int32 limit = 1; 56 | int32 page = 2; 57 | } 58 | 59 | message ListGoalsResponse{ 60 | repeated Goal goals = 1; 61 | } 62 | 63 | message GenerateGoalProgressReportRequest{ 64 | string id = 1; 65 | } 66 | 67 | message GenerateGoalProgressReportResponse{ 68 | string user_id = 1; 69 | string name = 2; 70 | float target_amount = 3; 71 | float current_amount = 4; 72 | float remain_amount = 5; 73 | string deadline = 6; 74 | string status = 7; 75 | } -------------------------------------------------------------------------------- /Api_Gateway/api/policy.csv: -------------------------------------------------------------------------------- 1 | p, user, /account/create, POST 2 | p, user, /account/update/:id, PUT 3 | p, user, /account/delete/:id, DELETE 4 | p, user, /account/get, GET 5 | p, user, /account/get/:id, GET 6 | 7 | p, user, /budget/create, POST 8 | p, user, /budget/update/:id, PUT 9 | p, user, /budget/delete/:id, DELETE 10 | p, user, /budget/get, GET 11 | p, user, /budget/get/:id, GET 12 | p, user, /budget/:id/performance-report, GET 13 | 14 | p, admin, /category/create, POST 15 | p, admin, /category/update/:id, PUT 16 | p, admin, /category/delete/:id, DELETE 17 | p, user, /category/get, GET 18 | p, user, /category/get/:id, GET 19 | 20 | p, user, /goal/create, POST 21 | p, user, /goal/update/:id, PUT 22 | p, user, /goal/delete/:id, DELETE 23 | p, user, /goal/get, GET 24 | p, user, /goal/get/:id, GET 25 | p, user, /goal/getprogress/:id, GET 26 | 27 | 28 | p, user, /transaction/create, POST 29 | p, user, /transaction/update/:id, PUT 30 | p, user, /transaction/delete/:id, DELETE 31 | p, user, /transaction/get, GET 32 | p, user, /transaction/get/:id, GET 33 | p, user, /transaction/income, GET 34 | p, user, /transaction/spending, GET 35 | p, user, /notification/get, GET 36 | 37 | p, unauthorized, /api/swagger/index.html, GET 38 | p, unauthorized, /api/swagger/swagger-ui.css, GET 39 | p, unauthorized, /api/swagger/swagger-ui-bundle.js, GET 40 | p, unauthorized, /api/swagger/favicon-32x32.png, GET 41 | p, unauthorized, /api/swagger/favicon-16x16.png, GET 42 | p, unauthorized, /api/swagger/swagger-ui-standalone-preset.js, GET 43 | p, unauthorized, /api/swagger/swagger/doc.json, GET 44 | p, unauthorized, /api/swagger/doc.json, GET 45 | p, unauthorized, /api/swagger/*any, GET 46 | g, admin, user -------------------------------------------------------------------------------- /Auth_service/kafka/consumer.go: -------------------------------------------------------------------------------- 1 | package kafka 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "log" 7 | "sync" 8 | 9 | "github.com/segmentio/kafka-go" 10 | ) 11 | 12 | type KafkaConsumerManager struct { 13 | consumers map[string]*kafka.Reader 14 | handlers map[string]func(message []byte) 15 | mu sync.Mutex 16 | } 17 | 18 | func NewKafkaConsumerManager() *KafkaConsumerManager { 19 | return &KafkaConsumerManager{ 20 | consumers: make(map[string]*kafka.Reader), 21 | handlers: make(map[string]func(message []byte)), 22 | } 23 | } 24 | 25 | var ErrConsumerAlreadyExists = errors.New("consumer for this topic already exists") 26 | 27 | func (kcm *KafkaConsumerManager) RegisterConsumer(brokers []string, topic, groupID string, handler func(message []byte)) error { 28 | kcm.mu.Lock() 29 | defer kcm.mu.Unlock() 30 | 31 | if _, exists := kcm.consumers[topic]; exists { 32 | return ErrConsumerAlreadyExists 33 | } 34 | 35 | reader := kafka.NewReader(kafka.ReaderConfig{ 36 | Brokers: brokers, 37 | Topic: topic, 38 | GroupID: groupID, 39 | }) 40 | kcm.consumers[topic] = reader 41 | kcm.handlers[topic] = handler 42 | 43 | go kcm.consumeMessages(topic) 44 | 45 | return nil 46 | } 47 | 48 | func (kcm *KafkaConsumerManager) consumeMessages(topic string) { 49 | reader := kcm.consumers[topic] 50 | handler := kcm.handlers[topic] 51 | 52 | for { 53 | msg, err := reader.ReadMessage(context.Background()) 54 | if err != nil { 55 | log.Printf("Error reading message from topic %s: %v", topic, err) 56 | continue 57 | } 58 | handler(msg.Value) 59 | } 60 | } 61 | 62 | func (kcm *KafkaConsumerManager) Close() error { 63 | kcm.mu.Lock() 64 | defer kcm.mu.Unlock() 65 | 66 | for _, reader := range kcm.consumers { 67 | if err := reader.Close(); err != nil { 68 | return err 69 | } 70 | } 71 | return nil 72 | } 73 | -------------------------------------------------------------------------------- /Budgeting_service/protos/account.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = "genprotos/"; 4 | 5 | package account; 6 | 7 | service AccountService { 8 | rpc CreateAccount(CreateAccountRequest) returns(CreateAccountResponse){} 9 | rpc UpdateAccount(UpdateAccountRequest) returns(UpdateAccountResponse){} 10 | rpc DeleteAccount(DeleteAccountRequest) returns(DeleteAccountResponse){} 11 | rpc GetAccount(GetAccountRequest)returns(GetAccountResponse){} 12 | rpc ListAccounts(ListAccountsRequest) returns(ListAccountsResponse){} 13 | rpc GetAmount(GetAmountRequest) returns(GetAmountResponse){} 14 | rpc UpdateAmount(UpdateAmountRequest)returns(UpdateAmountResponse){} 15 | } 16 | 17 | message CreateAccountRequest{ 18 | Account account = 1; 19 | } 20 | 21 | message CreateAccountResponse{} 22 | 23 | message Account{ 24 | string id = 1; 25 | string user_id = 2; 26 | string name = 3; 27 | string type = 4; 28 | float balance = 5; 29 | string currency = 6; 30 | } 31 | 32 | message UpdateAccountRequest{ 33 | Account account = 1; 34 | } 35 | 36 | message UpdateAccountResponse{} 37 | 38 | 39 | message GetAccountRequest{ 40 | string id = 1; 41 | } 42 | 43 | message GetAccountResponse{ 44 | Account account = 1; 45 | } 46 | 47 | message DeleteAccountRequest{ 48 | string id = 1; 49 | } 50 | 51 | message DeleteAccountResponse{} 52 | 53 | message ListAccountsRequest{ 54 | int32 limit = 1; 55 | int32 page = 2; 56 | } 57 | 58 | message ListAccountsResponse{ 59 | repeated Account accounts = 1; 60 | } 61 | 62 | message GetAmountRequest{ 63 | string user_id = 1; 64 | } 65 | 66 | message GetAmountResponse{ 67 | float balance = 1; 68 | } 69 | 70 | message UpdateAmountRequest{ 71 | string user_id = 1; 72 | float balance = 2; 73 | } 74 | 75 | message UpdateAmountResponse{ 76 | 77 | } -------------------------------------------------------------------------------- /Budgeting_service/protos/budget.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = "genprotos/"; 4 | 5 | package budget; 6 | 7 | service BudgetService { 8 | rpc CreateBudget(CreateBudgetRequest) returns(CreateBudgetResponse){} 9 | rpc UpdateBudget(UpdateBudgetRequest) returns(UpdateBudgetResponse){} 10 | rpc DeleteBudget(DeleteBudgetRequest) returns(DeleteBudgetResponse){} 11 | rpc GetBudget(GetBudgetRequest)returns(GetBudgetResponse){} 12 | rpc ListBudgets(ListBudgetsRequest) returns(ListBudgetsResponse){} 13 | rpc GenerateBudgetPerformanceReport(GenerateBudgetPerformanceReportRequest)returns(GenerateBudgetPerformanceReportResponse){} 14 | } 15 | 16 | message CreateBudgetRequest{ 17 | Budget budget = 1; 18 | } 19 | 20 | message CreateBudgetResponse{} 21 | 22 | message Budget{ 23 | string id = 1; 24 | string user_id = 2; 25 | string category_id = 3; 26 | string period = 4; 27 | float amount = 5; 28 | string start_date = 6; 29 | string end_date = 7; 30 | } 31 | 32 | message UpdateBudgetRequest{ 33 | Budget budget = 1; 34 | } 35 | 36 | message UpdateBudgetResponse{} 37 | 38 | 39 | message GetBudgetRequest{ 40 | string id = 1; 41 | } 42 | 43 | message GetBudgetResponse{ 44 | Budget budget = 1; 45 | } 46 | 47 | message DeleteBudgetRequest{ 48 | string id = 1; 49 | } 50 | 51 | message DeleteBudgetResponse{} 52 | 53 | message ListBudgetsRequest{ 54 | int32 limit = 1; 55 | int32 page = 2; 56 | } 57 | 58 | message ListBudgetsResponse{ 59 | repeated Budget budgets = 1; 60 | } 61 | 62 | message GenerateBudgetPerformanceReportRequest{ 63 | string id = 1; 64 | } 65 | message GenerateBudgetPerformanceReportResponse{ 66 | string id = 1; 67 | string user_id = 2; 68 | string category_id = 3; 69 | float amount = 4; 70 | string period = 5; 71 | string start_date = 6; 72 | string end_date = 7; 73 | float spent_amount = 8; 74 | } -------------------------------------------------------------------------------- /Auth_service/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net" 7 | 8 | "github.com/redis/go-redis/v9" 9 | "auth/api" 10 | "auth/api/handler" 11 | "auth/config" 12 | pb "auth/genprotos" 13 | "auth/kafka" 14 | "auth/service" 15 | "auth/storage/postgres" 16 | r "auth/storage/redis" 17 | "google.golang.org/grpc" 18 | "google.golang.org/grpc/credentials/insecure" 19 | ) 20 | 21 | func main() { 22 | cnf := config.Load() 23 | db, err := postgres.NewPostgresStorage() 24 | if err != nil { 25 | log.Fatal("Error while connection on db: ", err.Error()) 26 | } 27 | 28 | liss, err := net.Listen("tcp", cnf.GrpcUserPort) 29 | if err != nil { 30 | log.Fatal("Error while connection on tcp: ", err.Error()) 31 | } 32 | udb := service.NewUserService(db) 33 | 34 | cus := kafka.NewKafkaConsumerManager() 35 | broker := []string{"kafka:9092"} 36 | cus.RegisterConsumer(broker, "user", "u", kafka.UserCreateHandler(udb)) 37 | 38 | s := grpc.NewServer() 39 | pb.RegisterUserServiceServer(s, service.NewUserService(db)) 40 | log.Printf("server listening at %v", liss.Addr()) 41 | go func() { 42 | if err := s.Serve(liss); err != nil { 43 | log.Fatalf("failed to serve: %v", err) 44 | } 45 | }() 46 | 47 | userConn, err := grpc.NewClient(fmt.Sprintf("auth%s", cnf.GrpcUserPort), grpc.WithTransportCredentials(insecure.NewCredentials())) 48 | if err != nil { 49 | log.Fatal("Error while NewClient: ", err.Error()) 50 | } 51 | defer userConn.Close() 52 | 53 | client := redis.NewClient(&redis.Options{ 54 | Addr: cnf.RedisHost + cnf.RedisPort, 55 | }) 56 | 57 | rdb := r.NewInMemoryStorage(client) 58 | pr, err := kafka.NewKafkaProducer(broker) 59 | if err != nil { 60 | log.Fatal("Error while producer: ", err) 61 | } 62 | 63 | 64 | us := pb.NewUserServiceClient(userConn) 65 | h := handler.NewHandler(us, rdb, pr, db) 66 | r := api.NewGin(h) 67 | 68 | fmt.Println("Server started on port:", cnf.HTTPPort) 69 | 70 | err = r.Run(cnf.HTTPPort) 71 | if err != nil { 72 | log.Fatal("Error while Run: ", err.Error()) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Budgeting_service/protos/transaction.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = "genprotos/"; 4 | 5 | package transaction; 6 | 7 | 8 | service TransactionService { 9 | rpc CreateTransaction(CreateTransactionRequest) returns(CreateTransactionResponse){} 10 | rpc UpdateTransaction(UpdateTransactionRequest) returns(UpdateTransactionResponse){} 11 | rpc DeleteTransaction(DeleteTransactionRequest) returns(DeleteTransactionResponse){} 12 | rpc GetTransaction(GetTransactionRequest)returns(GetTransactionResponse){} 13 | rpc ListTransactions(ListTransactionsRequest) returns(ListTransactionsResponse){} 14 | rpc Spending(SpendingRequest)returns(SpendingResponse){} 15 | rpc Income(IncomeRequest) returns(IncomeResponse){} 16 | } 17 | 18 | message CreateTransactionRequest{ 19 | Transaction transaction = 1; 20 | } 21 | 22 | message CreateTransactionResponse{} 23 | 24 | message Transaction{ 25 | string id = 1; 26 | string user_id = 2; 27 | string account_id = 3; 28 | string category_id = 4; 29 | float amount = 5; 30 | string type = 6; 31 | string description = 7; 32 | string date = 8; 33 | } 34 | 35 | message UpdateTransactionRequest{ 36 | Transaction transaction = 1; 37 | } 38 | 39 | message UpdateTransactionResponse{} 40 | 41 | 42 | message GetTransactionRequest{ 43 | string id = 1; 44 | } 45 | 46 | message GetTransactionResponse{ 47 | Transaction transaction = 1; 48 | } 49 | 50 | message DeleteTransactionRequest{ 51 | string id = 1; 52 | } 53 | 54 | message DeleteTransactionResponse{} 55 | 56 | message ListTransactionsRequest{ 57 | int32 limit = 1; 58 | int32 page = 2; 59 | } 60 | 61 | message ListTransactionsResponse{ 62 | repeated Transaction transactions = 1; 63 | } 64 | 65 | message SpendingRequest{ 66 | string user_id = 1; 67 | } 68 | 69 | message SpendingResponse{ 70 | int32 spending_count = 1; 71 | float spending_money = 2; 72 | } 73 | 74 | message IncomeRequest{ 75 | string user_id = 1; 76 | } 77 | 78 | message IncomeResponse{ 79 | int32 income_count = 1; 80 | float income_money = 2; 81 | } -------------------------------------------------------------------------------- /Auth_service/api/middleware/middleware.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "strings" 7 | 8 | "log" 9 | 10 | "github.com/casbin/casbin/v2" 11 | "github.com/gin-gonic/gin" 12 | "github.com/golang-jwt/jwt" 13 | "auth/api/token" 14 | "auth/config" 15 | ) 16 | 17 | type JwtRoleAuth struct { 18 | enforcer *casbin.Enforcer 19 | jwtHandler token.JWTHandler 20 | } 21 | 22 | func NewAuth(enforce *casbin.Enforcer) gin.HandlerFunc { 23 | 24 | auth := JwtRoleAuth{ 25 | enforcer: enforce, 26 | } 27 | 28 | return func(ctx *gin.Context) { 29 | allow, err := auth.CheckPermission(ctx) 30 | if err != nil { 31 | valid, _ := err.(jwt.ValidationError) 32 | if valid.Errors == jwt.ValidationErrorExpired { 33 | ctx.AbortWithStatusJSON(http.StatusForbidden, "Invalid token !!!") 34 | } else { 35 | ctx.AbortWithStatusJSON(401, "Access token expired") 36 | } 37 | } else if !allow { 38 | ctx.AbortWithStatusJSON(http.StatusForbidden, "Permission denied") 39 | } 40 | } 41 | } 42 | 43 | func (a *JwtRoleAuth) GetRole(r *http.Request) (string, error) { 44 | var ( 45 | claims jwt.MapClaims 46 | err error 47 | ) 48 | 49 | jwtToken := r.Header.Get("Authorization") 50 | 51 | if jwtToken == "" { 52 | return "unauthorized", nil 53 | } else if strings.Contains(jwtToken, "Basic") { 54 | return "unauthorized", nil 55 | } 56 | a.jwtHandler.Token = jwtToken 57 | a.jwtHandler.SigningKey = config.Load().TokenKey 58 | claims, err = a.jwtHandler.ExtractClaims() 59 | 60 | if err != nil { 61 | log.Println("Error while extracting claims: ", err) 62 | return "unauthorized", err 63 | } 64 | 65 | return claims["role"].(string), nil 66 | } 67 | 68 | func (a *JwtRoleAuth) CheckPermission(ctx *gin.Context) (bool, error) { 69 | role, err := a.GetRole(ctx.Request) 70 | if err != nil { 71 | log.Println("Error while getting role from token: ", err) 72 | return false, err 73 | } 74 | method := ctx.Request.Method 75 | path := ctx.FullPath() 76 | fmt.Println(role, path, method) 77 | allowed, err := a.enforcer.Enforce(role, path, method) 78 | if err != nil { 79 | log.Println("Error while comparing role from csv list: ", err) 80 | return false, err 81 | } 82 | 83 | return allowed, nil 84 | } 85 | -------------------------------------------------------------------------------- /Budgeting_service/storage/mongo/mongo.go: -------------------------------------------------------------------------------- 1 | package mongo 2 | 3 | import ( 4 | "budgeting/storage" 5 | "context" 6 | 7 | "go.mongodb.org/mongo-driver/mongo" 8 | "go.mongodb.org/mongo-driver/mongo/options" 9 | ) 10 | 11 | type Storage struct { 12 | DB *mongo.Database 13 | AccountS storage.AccountI 14 | BudgetS storage.BudgetI 15 | CategoryS storage.CategoryI 16 | GoalS storage.GoalI 17 | TransactionS storage.TransactionI 18 | NotificationS storage.NotificationI 19 | } 20 | 21 | func ConnectMongo() (storage.StorageI, error) { 22 | clientOptions := options.Client().ApplyURI("mongodb://mongo-db:27017") 23 | 24 | client, err := mongo.Connect(context.TODO(), clientOptions) 25 | if err != nil { 26 | return nil, err 27 | } 28 | err = client.Ping(context.TODO(), nil) 29 | if err != nil { 30 | return nil, err 31 | } 32 | db := client.Database("Budgeting") 33 | 34 | accountS := NewAccount(db) 35 | budgetS := NewBudget(db) 36 | categoryS := NewCategory(db) 37 | goalS := NewGoal(db) 38 | transaction := NewTransaction(db) 39 | notificationS := NewNotification(db) 40 | 41 | return &Storage{ 42 | DB: db, 43 | AccountS: accountS, 44 | BudgetS: budgetS, 45 | CategoryS: categoryS, 46 | GoalS: goalS, 47 | TransactionS: transaction, 48 | NotificationS: notificationS, 49 | }, nil 50 | } 51 | 52 | func (s *Storage) Account() storage.AccountI { 53 | if s.AccountS == nil { 54 | s.AccountS = NewAccount(s.DB) 55 | } 56 | return s.AccountS 57 | } 58 | 59 | func (s *Storage) Budget() storage.BudgetI { 60 | if s.BudgetS == nil { 61 | s.BudgetS = NewBudget(s.DB) 62 | } 63 | return s.BudgetS 64 | } 65 | 66 | func (s *Storage) Category() storage.CategoryI{ 67 | if s.CategoryS == nil{ 68 | s.CategoryS = NewCategory(s.DB) 69 | } 70 | return s.CategoryS 71 | } 72 | 73 | func (s *Storage) Goal() storage.GoalI{ 74 | if s.GoalS == nil{ 75 | s.GoalS = NewGoal(s.DB) 76 | } 77 | return s.GoalS 78 | } 79 | 80 | func (s *Storage) Transaction() storage.TransactionI{ 81 | if s.TransactionS == nil{ 82 | s.TransactionS = NewTransaction(s.DB) 83 | } 84 | return s.TransactionS 85 | } 86 | 87 | func (s *Storage) Notification() storage.NotificationI{ 88 | if s.NotificationS == nil{ 89 | s.NotificationS = NewNotification(s.DB) 90 | } 91 | return s.NotificationS 92 | } -------------------------------------------------------------------------------- /Api_Gateway/api/middleware/middleware.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "strings" 7 | 8 | "log" 9 | 10 | "github.com/casbin/casbin/v2" 11 | "github.com/gin-gonic/gin" 12 | "github.com/golang-jwt/jwt" 13 | "api/api/token" 14 | "api/config" 15 | ) 16 | 17 | type JwtRoleAuth struct { 18 | enforcer *casbin.Enforcer 19 | jwtHandler token.JWTHandler 20 | } 21 | 22 | func NewAuth(enforce *casbin.Enforcer) gin.HandlerFunc { 23 | 24 | auth := JwtRoleAuth{ 25 | enforcer: enforce, 26 | } 27 | 28 | return func(ctx *gin.Context) { 29 | allow, err := auth.CheckPermission(ctx) 30 | if err != nil { 31 | valid, _ := err.(jwt.ValidationError) 32 | if valid.Errors == jwt.ValidationErrorExpired { 33 | ctx.AbortWithStatusJSON(http.StatusForbidden, "Invalid token !!!") 34 | } else { 35 | ctx.AbortWithStatusJSON(401, "Access token expired") 36 | } 37 | } else if !allow { 38 | ctx.AbortWithStatusJSON(http.StatusForbidden, "Permission denied") 39 | } 40 | } 41 | } 42 | 43 | func (a *JwtRoleAuth) GetRole(r *http.Request) (string, error) { 44 | var ( 45 | claims jwt.MapClaims 46 | err error 47 | ) 48 | 49 | jwtToken := r.Header.Get("Authorization") 50 | 51 | if jwtToken == "" { 52 | return "unauthorized", nil 53 | } else if strings.Contains(jwtToken, "Basic") { 54 | return "unauthorized", nil 55 | } 56 | a.jwtHandler.Token = jwtToken 57 | a.jwtHandler.SigningKey = config.Load().TokenKey 58 | claims, err = a.jwtHandler.ExtractClaims() 59 | 60 | if err != nil { 61 | log.Println("Error while extracting claims: ", err) 62 | return "unauthorized", err 63 | } 64 | 65 | return claims["role"].(string), nil 66 | } 67 | 68 | func (a *JwtRoleAuth) CheckPermission(ctx *gin.Context) (bool, error) { 69 | role, err := a.GetRole(ctx.Request) 70 | if err != nil { 71 | log.Println("Error while getting role from token: ", err) 72 | return false, err 73 | } 74 | method := ctx.Request.Method 75 | path := ctx.FullPath() 76 | fmt.Println(role, path, method) 77 | allowed, err := a.enforcer.Enforce(role, path, method) 78 | if err != nil { 79 | log.Println("Error while comparing role from csv list: ", err) 80 | return false, err 81 | } 82 | 83 | return allowed, nil 84 | } 85 | -------------------------------------------------------------------------------- /Budgeting_service/service/account.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | pb "budgeting/genprotos" 5 | "budgeting/storage" 6 | "context" 7 | 8 | "github.com/google/uuid" 9 | ) 10 | 11 | type AccountService struct { 12 | stg storage.StorageI 13 | pb.UnimplementedAccountServiceServer 14 | } 15 | 16 | func NewAccountService(stg *storage.StorageI) *AccountService { 17 | return &AccountService{stg: *stg} 18 | } 19 | 20 | func (s *AccountService) CreateAccount(c context.Context, req *pb.CreateAccountRequest) (*pb.CreateAccountResponse, error) { 21 | id := uuid.NewString() 22 | req.Account.Id = id 23 | 24 | _, err := s.stg.Account().CreateAccount(req) 25 | if err != nil { 26 | return nil, err 27 | } 28 | return &pb.CreateAccountResponse{}, nil 29 | } 30 | 31 | func (s *AccountService) UpdateAccount(c context.Context, req *pb.UpdateAccountRequest) (*pb.UpdateAccountResponse, error) { 32 | _, err := s.stg.Account().UpdateAccount(req) 33 | if err != nil { 34 | return nil, err 35 | } 36 | return &pb.UpdateAccountResponse{}, nil 37 | } 38 | 39 | func (s *AccountService) DeleteAccount(c context.Context, req *pb.DeleteAccountRequest) (*pb.DeleteAccountResponse, error) { 40 | _, err := s.stg.Account().DeleteAccount(req) 41 | if err != nil { 42 | return nil, err 43 | } 44 | return &pb.DeleteAccountResponse{}, nil 45 | } 46 | 47 | func (s *AccountService) GetAccount(c context.Context, req *pb.GetAccountRequest) (*pb.GetAccountResponse, error) { 48 | res, err := s.stg.Account().GetAccount(req) 49 | if err != nil { 50 | return nil, err 51 | } 52 | return res, nil 53 | } 54 | 55 | func (s *AccountService) ListAccounts(c context.Context, req *pb.ListAccountsRequest) (*pb.ListAccountsResponse, error) { 56 | res, err := s.stg.Account().ListAccounts(req) 57 | if err != nil { 58 | return nil, err 59 | } 60 | return res, nil 61 | } 62 | 63 | func (s *AccountService) GetAmount(c context.Context, req *pb.GetAmountRequest) (*pb.GetAmountResponse, error) { 64 | res, err := s.stg.Account().GetAmount(req) 65 | if err != nil { 66 | return nil, err 67 | } 68 | return res, nil 69 | } 70 | 71 | func (s *AccountService) UpdateAmount(c context.Context, req *pb.UpdateAmountRequest) (*pb.UpdateAmountResponse, error) { 72 | _, err := s.stg.Account().UpdateAmount(req) 73 | if err != nil { 74 | return nil, err 75 | } 76 | return nil, nil 77 | } 78 | -------------------------------------------------------------------------------- /Auth_service/config/getEnv.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/joho/godotenv" 8 | "github.com/spf13/cast" 9 | ) 10 | 11 | type Config struct { 12 | HTTPPort string 13 | GrpcUserPort string 14 | 15 | PostgresHost string 16 | PostgresPort int 17 | PostgresUser string 18 | PostgresPassword string 19 | PostgresDatabase string 20 | 21 | RedisDB int 22 | RedisHost string 23 | RedisPassword string 24 | RedisPort string 25 | 26 | SmtpSender string 27 | SmtpPassword string 28 | 29 | DefaultOffset string 30 | DefaultLimit string 31 | 32 | TokenKey string 33 | } 34 | 35 | func Load() Config { 36 | if err := godotenv.Load(); err != nil { 37 | fmt.Println("No .env file found") 38 | } 39 | 40 | config := Config{} 41 | 42 | config.HTTPPort = cast.ToString(getOrReturnDefaultValue("HTTP_PORT", ":8000")) 43 | config.GrpcUserPort = cast.ToString(getOrReturnDefaultValue("GRPC_USER_PORT", ":8002")) 44 | 45 | config.SmtpSender = cast.ToString(getOrReturnDefaultValue("SMTP_SENDER", "example@gmail.com")) 46 | config.SmtpPassword = cast.ToString(getOrReturnDefaultValue("SMTP_PASSWORD", "1234")) 47 | 48 | config.PostgresHost = cast.ToString(getOrReturnDefaultValue("POSTGRES_HOST", "auth")) 49 | config.PostgresPort = cast.ToInt(getOrReturnDefaultValue("POSTGRES_PORT", 5432)) 50 | config.PostgresUser = cast.ToString(getOrReturnDefaultValue("POSTGRES_USER", "azizbek")) 51 | config.PostgresPassword = cast.ToString(getOrReturnDefaultValue("POSTGRES_PASSWORD", "123")) 52 | config.PostgresDatabase = cast.ToString(getOrReturnDefaultValue("POSTGRES_DATABASE", "auth")) 53 | 54 | config.RedisHost = cast.ToString(getOrReturnDefaultValue("REDIS_HOST", "localhost")) 55 | config.RedisPort = cast.ToString(getOrReturnDefaultValue("REDIS_PORT", ":6370")) 56 | config.RedisDB = cast.ToInt(getOrReturnDefaultValue("REDIS_DB", 0)) 57 | config.RedisPassword = cast.ToString(getOrReturnDefaultValue("REDIS_PASSWORD", "")) 58 | 59 | config.DefaultOffset = cast.ToString(getOrReturnDefaultValue("DEFAULT_OFFSET", "0")) 60 | config.DefaultLimit = cast.ToString(getOrReturnDefaultValue("DEFAULT_LIMIT", "10")) 61 | config.TokenKey = cast.ToString(getOrReturnDefaultValue("TokenKey", "my_secret_key")) 62 | 63 | return config 64 | } 65 | 66 | func getOrReturnDefaultValue(key string, defaultValue interface{}) interface{} { 67 | val, exists := os.LookupEnv(key) 68 | 69 | if exists { 70 | return val 71 | } 72 | 73 | return defaultValue 74 | } 75 | -------------------------------------------------------------------------------- /Budgeting_service/service/category.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | pb "budgeting/genprotos" 5 | "budgeting/storage" 6 | "context" 7 | "fmt" 8 | 9 | "github.com/google/uuid" 10 | ) 11 | 12 | type CategoryService struct { 13 | stg storage.StorageI 14 | pb.UnimplementedCategoryServiceServer 15 | } 16 | 17 | func NewCategoryService(stg *storage.StorageI) *CategoryService { 18 | return &CategoryService{stg: *stg} 19 | } 20 | 21 | func (s *CategoryService) CreateCategory(c context.Context,req *pb.CreateCategoryRequest) (*pb.CreateCategoryResponse, error) { 22 | id := uuid.NewString() 23 | req.Category.Id = id 24 | 25 | if _, err := uuid.Parse(req.Category.UserId);err != nil{ 26 | return nil, fmt.Errorf("invalid UserId: must be a valid UUID") 27 | } 28 | 29 | if req.Category.Type != "expense" && req.Category.Type != "income" { 30 | return nil, fmt.Errorf("invalid Category Type: must be either 'expense' or 'income'") 31 | } 32 | 33 | _, err := s.stg.Category().CreateCategory(req) 34 | if err != nil { 35 | return nil, err 36 | } 37 | return &pb.CreateCategoryResponse{}, nil 38 | } 39 | 40 | func (s *CategoryService) UpdateCategory(c context.Context,req *pb.UpdateCategoryRequest) (*pb.UpdateCategoryResponse, error) { 41 | _, err := s.stg.Category().UpdateCategory(req) 42 | if err != nil { 43 | return nil, err 44 | } 45 | if _, err := uuid.Parse(req.Category.UserId);err != nil{ 46 | return nil, fmt.Errorf("invalid UserId: must be a valid UUID") 47 | } 48 | 49 | if req.Category.Type != "expense" && req.Category.Type != "income" { 50 | return nil, fmt.Errorf("invalid Category Type: must be either 'expense' or 'income'") 51 | } 52 | return &pb.UpdateCategoryResponse{}, nil 53 | } 54 | 55 | func (s *CategoryService) DeleteCategory(c context.Context,req *pb.DeleteCategoryRequest) (*pb.DeleteCategoryResponse, error) { 56 | _, err := s.stg.Category().DeleteCategory(req) 57 | if err != nil { 58 | return nil, err 59 | } 60 | return &pb.DeleteCategoryResponse{}, nil 61 | } 62 | 63 | func (s *CategoryService) GetCategory(c context.Context,req *pb.GetCategoryRequest) (*pb.GetCategoryResponse, error) { 64 | res, err := s.stg.Category().GetCategory(req) 65 | if err != nil { 66 | return nil, err 67 | } 68 | return res, nil 69 | } 70 | 71 | func (s *CategoryService) ListCategories(c context.Context,req *pb.ListCategoriesRequest) (*pb.ListCategoriesResponse, error) { 72 | res, err := s.stg.Category().ListCategories(req) 73 | if err != nil { 74 | return nil, err 75 | } 76 | return res, nil 77 | } 78 | -------------------------------------------------------------------------------- /Api_Gateway/go.mod: -------------------------------------------------------------------------------- 1 | module api 2 | 3 | go 1.22.3 4 | 5 | require ( 6 | github.com/casbin/casbin/v2 v2.98.0 7 | github.com/gin-contrib/cors v1.7.2 8 | github.com/gin-gonic/gin v1.10.0 9 | github.com/golang-jwt/jwt v3.2.2+incompatible 10 | github.com/joho/godotenv v1.5.1 11 | github.com/spf13/cast v1.7.0 12 | github.com/swaggo/files v1.0.1 13 | github.com/swaggo/gin-swagger v1.6.0 14 | github.com/swaggo/swag v1.16.3 15 | google.golang.org/grpc v1.65.0 16 | google.golang.org/protobuf v1.34.2 17 | ) 18 | 19 | require ( 20 | github.com/KyleBanks/depth v1.2.1 // indirect 21 | github.com/PuerkitoBio/purell v1.1.1 // indirect 22 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect 23 | github.com/bytedance/sonic v1.11.6 // indirect 24 | github.com/bytedance/sonic/loader v0.1.1 // indirect 25 | github.com/casbin/govaluate v1.2.0 // indirect 26 | github.com/cloudwego/base64x v0.1.4 // indirect 27 | github.com/cloudwego/iasm v0.2.0 // indirect 28 | github.com/gabriel-vasile/mimetype v1.4.3 // indirect 29 | github.com/gin-contrib/sse v0.1.0 // indirect 30 | github.com/go-openapi/jsonpointer v0.19.5 // indirect 31 | github.com/go-openapi/jsonreference v0.19.6 // indirect 32 | github.com/go-openapi/spec v0.20.4 // indirect 33 | github.com/go-openapi/swag v0.19.15 // indirect 34 | github.com/go-playground/locales v0.14.1 // indirect 35 | github.com/go-playground/universal-translator v0.18.1 // indirect 36 | github.com/go-playground/validator/v10 v10.20.0 // indirect 37 | github.com/goccy/go-json v0.10.2 // indirect 38 | github.com/josharian/intern v1.0.0 // indirect 39 | github.com/json-iterator/go v1.1.12 // indirect 40 | github.com/klauspost/cpuid/v2 v2.2.7 // indirect 41 | github.com/leodido/go-urn v1.4.0 // indirect 42 | github.com/mailru/easyjson v0.7.6 // indirect 43 | github.com/mattn/go-isatty v0.0.20 // indirect 44 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 45 | github.com/modern-go/reflect2 v1.0.2 // indirect 46 | github.com/pelletier/go-toml/v2 v2.2.2 // indirect 47 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 48 | github.com/ugorji/go/codec v1.2.12 // indirect 49 | golang.org/x/arch v0.8.0 // indirect 50 | golang.org/x/crypto v0.23.0 // indirect 51 | golang.org/x/net v0.25.0 // indirect 52 | golang.org/x/sys v0.20.0 // indirect 53 | golang.org/x/text v0.15.0 // indirect 54 | golang.org/x/tools v0.7.0 // indirect 55 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect 56 | gopkg.in/yaml.v2 v2.4.0 // indirect 57 | gopkg.in/yaml.v3 v3.0.1 // indirect 58 | ) 59 | -------------------------------------------------------------------------------- /Auth_service/protos/user.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = "genprotos/"; 4 | 5 | package user; 6 | 7 | service UserService{ 8 | rpc RegisterUser(RegisterUserRequest) returns(RegisterUserResponse){} 9 | rpc LoginUser(LoginUserRequest) returns(LoginUserResponse){} 10 | rpc GetByIdUser(GetByIdUserRequest)returns(GetByIdUserResponse){} 11 | rpc GetAllUsers(GetAllUsersRequest)returns(GetAllUsersResponse){} 12 | rpc UpdateUser(UpdateUserRequest) returns(UpdateUserResponse){} 13 | rpc DeleteUser(DeleteUserRequest) returns(DeleteUserResponse){} 14 | rpc ChangePassword(ChangePasswordRequest)returns(ChangePasswordResponse){} 15 | rpc ForgotPassword(ForgotPasswordRequest)returns(ForgotPasswordResponse){} 16 | rpc ResetPassword(ResetPasswordRequest)returns(ResetPasswordResponse){} 17 | } 18 | 19 | message ResetPasswordRequest{ 20 | string id = 1; 21 | string password_hash = 2; 22 | string reset_token = 3; 23 | } 24 | 25 | message ResetPasswordResponse{} 26 | 27 | message RegisterUserRequest{ 28 | string email = 1; 29 | string password_hash = 2; 30 | string first_name = 3; 31 | string last_name = 4; 32 | string role = 5; 33 | } 34 | 35 | message RegisterUserResponse{} 36 | 37 | message LoginUserRequest{ 38 | string email = 1; 39 | string password_hash = 2; 40 | } 41 | 42 | message LoginUserResponse{ 43 | string id = 1; 44 | string email = 2; 45 | string password_hash = 3; 46 | string first_name = 4; 47 | string last_name = 5; 48 | string role = 6; 49 | } 50 | 51 | message GetByIdUserRequest{ 52 | string id = 1; 53 | } 54 | 55 | message GetByIdUserResponse{ 56 | string id = 1; 57 | string email = 2; 58 | string first_name = 3; 59 | string last_name = 4; 60 | } 61 | 62 | message GetAllUsersRequest{ 63 | int32 limit = 1; 64 | int32 offset = 2; 65 | } 66 | 67 | message GetAllUsersResponse{ 68 | repeated LoginUserResponse user = 1; 69 | } 70 | 71 | message UpdateUserRequest{ 72 | string id = 1; 73 | string email = 2; 74 | string first_name = 3; 75 | string last_name = 4; 76 | } 77 | 78 | message UpdateUserResponse{ 79 | string email = 1; 80 | string first_name = 3; 81 | string id = 4; 82 | string last_name = 2; 83 | } 84 | 85 | message DeleteUserRequest{ 86 | string id = 1; 87 | } 88 | message DeleteUserResponse{} 89 | 90 | message ChangePasswordRequest{ 91 | string current_password = 1; 92 | string new_password = 2; 93 | string id = 3; 94 | } 95 | 96 | message ChangePasswordResponse{} 97 | 98 | message ForgotPasswordRequest{ 99 | string email = 1; 100 | } 101 | 102 | message ForgotPasswordResponse{} -------------------------------------------------------------------------------- /Budgeting_service/storage/mongo/category.go: -------------------------------------------------------------------------------- 1 | package mongo 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | 7 | e "budgeting/extra" 8 | pb "budgeting/genprotos" 9 | m "budgeting/models" 10 | 11 | "go.mongodb.org/mongo-driver/bson" 12 | "go.mongodb.org/mongo-driver/mongo" 13 | "go.mongodb.org/mongo-driver/mongo/options" 14 | ) 15 | 16 | type Category struct { 17 | mongo *mongo.Collection 18 | } 19 | 20 | func NewCategory(db *mongo.Database) *Category { 21 | return &Category{mongo: db.Collection("category")} 22 | } 23 | 24 | func (s *Category) CreateCategory(req *pb.CreateCategoryRequest) (*pb.CreateCategoryResponse, error) { 25 | bsonAccount := e.CategoryToBSON(req.Category) 26 | _, err := s.mongo.InsertOne(context.TODO(), bsonAccount) 27 | if err != nil { 28 | return nil, err 29 | 30 | } 31 | return &pb.CreateCategoryResponse{}, nil 32 | } 33 | 34 | func (s *Category) UpdateCategory(req *pb.UpdateCategoryRequest) (*pb.UpdateCategoryResponse, error) { 35 | bsonAccount := e.CategoryToBSON(req.Category) 36 | filter := bson.M{"_id": bsonAccount.ID} 37 | update := bson.M{"$set": bsonAccount} 38 | 39 | _, err := s.mongo.UpdateOne(context.TODO(), filter, update) 40 | if err != nil { 41 | return nil, err 42 | } 43 | 44 | return &pb.UpdateCategoryResponse{}, nil 45 | } 46 | 47 | func (s *Category) DeleteCategory(req *pb.DeleteCategoryRequest) (*pb.DeleteCategoryResponse, error) { 48 | _, err := s.mongo.DeleteOne(context.TODO(), bson.M{"_id": req.Id}) 49 | if err != nil { 50 | return nil, err 51 | } 52 | return &pb.DeleteCategoryResponse{}, nil 53 | } 54 | 55 | func (s *Category) GetCategory(req *pb.GetCategoryRequest) (*pb.GetCategoryResponse, error) { 56 | var bsonAccount m.Category 57 | err := s.mongo.FindOne(context.TODO(), bson.M{"_id": req.Id}).Decode(&bsonAccount) 58 | if err == mongo.ErrNoDocuments { 59 | return nil, errors.New("category not found") 60 | } else if err != nil { 61 | return nil, err 62 | } 63 | return &pb.GetCategoryResponse{Category: e.BsonToCategory(&bsonAccount)}, nil 64 | } 65 | 66 | func (s *Category) ListCategories(req *pb.ListCategoriesRequest) (*pb.ListCategoriesResponse, error) { 67 | limit := req.Limit 68 | page := req.Page 69 | skip := (page - 1) * limit 70 | 71 | cursor, err := s.mongo.Find(context.TODO(), bson.M{}, options.Find().SetSkip(int64(skip)).SetLimit(int64(limit))) 72 | if err != nil { 73 | return nil, err 74 | } 75 | defer cursor.Close(context.TODO()) 76 | 77 | var accounts []*pb.Category 78 | for cursor.Next(context.TODO()) { 79 | var bsonAccount m.Category 80 | if err := cursor.Decode(&bsonAccount); err != nil { 81 | return nil, err 82 | } 83 | account := e.BsonToCategory(&bsonAccount) 84 | accounts = append(accounts, account) 85 | } 86 | 87 | if err := cursor.Err(); err != nil { 88 | return nil, err 89 | } 90 | 91 | return &pb.ListCategoriesResponse{ 92 | Categories: accounts, 93 | }, nil 94 | } 95 | -------------------------------------------------------------------------------- /Auth_service/go.mod: -------------------------------------------------------------------------------- 1 | module auth 2 | 3 | go 1.22.3 4 | 5 | require ( 6 | github.com/casbin/casbin/v2 v2.98.0 7 | github.com/joho/godotenv v1.5.1 8 | github.com/spf13/cast v1.6.0 9 | github.com/swaggo/files v1.0.1 10 | github.com/swaggo/gin-swagger v1.6.0 11 | golang.org/x/crypto v0.25.0 12 | google.golang.org/grpc v1.65.0 13 | google.golang.org/protobuf v1.34.2 14 | ) 15 | 16 | require ( 17 | github.com/KyleBanks/depth v1.2.1 // indirect 18 | github.com/PuerkitoBio/purell v1.1.1 // indirect 19 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect 20 | github.com/bytedance/sonic v1.11.6 // indirect 21 | github.com/bytedance/sonic/loader v0.1.1 // indirect 22 | github.com/casbin/govaluate v1.2.0 // indirect 23 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 24 | github.com/cloudwego/base64x v0.1.4 // indirect 25 | github.com/cloudwego/iasm v0.2.0 // indirect 26 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect 27 | github.com/gabriel-vasile/mimetype v1.4.3 // indirect 28 | github.com/gin-contrib/sse v0.1.0 // indirect 29 | github.com/go-openapi/jsonpointer v0.19.5 // indirect 30 | github.com/go-openapi/jsonreference v0.19.6 // indirect 31 | github.com/go-openapi/spec v0.20.4 // indirect 32 | github.com/go-openapi/swag v0.19.15 // indirect 33 | github.com/go-playground/locales v0.14.1 // indirect 34 | github.com/go-playground/universal-translator v0.18.1 // indirect 35 | github.com/go-playground/validator/v10 v10.20.0 // indirect 36 | github.com/goccy/go-json v0.10.2 // indirect 37 | github.com/josharian/intern v1.0.0 // indirect 38 | github.com/json-iterator/go v1.1.12 // indirect 39 | github.com/klauspost/compress v1.15.9 // indirect 40 | github.com/klauspost/cpuid/v2 v2.2.7 // indirect 41 | github.com/leodido/go-urn v1.4.0 // indirect 42 | github.com/mailru/easyjson v0.7.6 // indirect 43 | github.com/mattn/go-isatty v0.0.20 // indirect 44 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 45 | github.com/modern-go/reflect2 v1.0.2 // indirect 46 | github.com/pelletier/go-toml/v2 v2.2.2 // indirect 47 | github.com/pierrec/lz4/v4 v4.1.15 // indirect 48 | github.com/swaggo/swag v1.16.3 49 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 50 | github.com/ugorji/go/codec v1.2.12 // indirect 51 | golang.org/x/arch v0.8.0 // indirect 52 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect 53 | gopkg.in/yaml.v2 v2.4.0 // indirect 54 | gopkg.in/yaml.v3 v3.0.1 // indirect 55 | ) 56 | 57 | require ( 58 | github.com/gin-gonic/gin v1.10.0 59 | github.com/golang-jwt/jwt v3.2.2+incompatible 60 | github.com/lib/pq v1.10.9 61 | github.com/redis/go-redis/v9 v9.6.1 62 | github.com/segmentio/kafka-go v0.4.47 63 | golang.org/x/net v0.25.0 // indirect 64 | golang.org/x/sys v0.22.0 // indirect 65 | golang.org/x/text v0.16.0 // indirect 66 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect 67 | ) 68 | -------------------------------------------------------------------------------- /Auth_service/service/user.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "log" 7 | 8 | pb "auth/genprotos" 9 | s "auth/storage" 10 | ) 11 | 12 | type UserService struct { 13 | stg s.StorageI 14 | pb.UnimplementedUserServiceServer 15 | } 16 | 17 | func NewUserService(stg s.StorageI) *UserService { 18 | return &UserService{stg: stg} 19 | } 20 | 21 | var ErrUserAlreadyExists = errors.New("user already exists") 22 | 23 | func (s *UserService) RegisterUser(ctx context.Context, req *pb.RegisterUserRequest) (*pb.RegisterUserResponse, error) { 24 | existingUser, err := s.stg.User().GetUserByEmail(req.Email) 25 | if err != nil { 26 | return nil, err 27 | } 28 | if existingUser != nil { 29 | return nil, ErrUserAlreadyExists 30 | } 31 | _, err = s.stg.User().RegisterUser(req) 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | return &pb.RegisterUserResponse{}, nil 37 | } 38 | 39 | func (c *UserService) LoginUser(ctx context.Context, login *pb.LoginUserRequest) (*pb.LoginUserResponse, error) { 40 | user, err := c.stg.User().LoginUser(login) 41 | if err != nil { 42 | log.Print(err) 43 | return nil, err 44 | } 45 | return user, nil 46 | } 47 | 48 | func (c *UserService) GetByIdUser(ctx context.Context, id *pb.GetByIdUserRequest) (*pb.GetByIdUserResponse, error) { 49 | user, err := c.stg.User().GetByIdUser(id) 50 | if err != nil { 51 | log.Print(err) 52 | return nil, err 53 | } 54 | return user, nil 55 | } 56 | 57 | func (c *UserService) UpdateUser(ctx context.Context, user *pb.UpdateUserRequest) (*pb.UpdateUserResponse, error) { 58 | res, err := c.stg.User().UpdateUser(user) 59 | if err != nil { 60 | log.Print(err) 61 | return nil, err 62 | } 63 | return res, nil 64 | } 65 | 66 | func (c *UserService) DeleteUser(ctx context.Context, id *pb.DeleteUserRequest) (*pb.DeleteUserResponse, error) { 67 | void, err := c.stg.User().DeleteUser(id) 68 | if err != nil { 69 | log.Print(err) 70 | return nil, err 71 | } 72 | return void, nil 73 | } 74 | 75 | func (c *UserService) ChangePassword(ctx context.Context, changePass *pb.ChangePasswordRequest) (*pb.ChangePasswordResponse, error) { 76 | void, err := c.stg.User().ChangePassword(changePass) 77 | if err != nil { 78 | log.Print(err) 79 | return nil, err 80 | } 81 | return void, nil 82 | } 83 | 84 | func (c *UserService) ForgotPassword(ctx context.Context, forgotPass *pb.ForgotPasswordRequest) (*pb.ForgotPasswordResponse, error) { 85 | void, err := c.stg.User().ForgotPassword(forgotPass) 86 | if err != nil { 87 | log.Print(err) 88 | return nil, err 89 | } 90 | return void, nil 91 | } 92 | 93 | func (c *UserService) ResetPassword(ctx context.Context,resetPass *pb.ResetPasswordRequest) (*pb.ResetPasswordResponse, error){ 94 | void, err := c.stg.User().ResetPassword(resetPass) 95 | if err != nil{ 96 | log.Println(err) 97 | return nil, err 98 | } 99 | return void, err 100 | } -------------------------------------------------------------------------------- /Budgeting_service/storage/storage.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | pb "budgeting/genprotos" 5 | ) 6 | 7 | type StorageI interface { 8 | Account() AccountI 9 | Budget() BudgetI 10 | Category() CategoryI 11 | Goal() GoalI 12 | Transaction() TransactionI 13 | Notification() NotificationI 14 | } 15 | 16 | type AccountI interface{ 17 | CreateAccount(req *pb.CreateAccountRequest) (*pb.CreateAccountResponse, error) 18 | UpdateAccount(req *pb.UpdateAccountRequest) (*pb.UpdateAccountResponse, error) 19 | DeleteAccount(req *pb.DeleteAccountRequest) (*pb.DeleteAccountResponse, error) 20 | GetAccount(req *pb.GetAccountRequest) (*pb.GetAccountResponse, error) 21 | ListAccounts(req *pb.ListAccountsRequest) (*pb.ListAccountsResponse, error) 22 | GetAmount(req *pb.GetAmountRequest) (*pb.GetAmountResponse, error) 23 | UpdateAmount(req *pb.UpdateAmountRequest) (*pb.UpdateAmountResponse, error) 24 | } 25 | 26 | type BudgetI interface{ 27 | CreateBudget(req *pb.CreateBudgetRequest) (*pb.CreateBudgetResponse, error) 28 | UpdateBudget(req *pb.UpdateBudgetRequest) (*pb.UpdateBudgetResponse, error) 29 | DeleteBudget(req *pb.DeleteBudgetRequest) (*pb.DeleteBudgetResponse, error) 30 | GetBudget(req *pb.GetBudgetRequest) (*pb.GetBudgetResponse, error) 31 | ListBudgets(req *pb.ListBudgetsRequest) (*pb.ListBudgetsResponse, error) 32 | GenerateBudgetPerformanceReport(req *pb.GenerateBudgetPerformanceReportRequest) (*pb.GenerateBudgetPerformanceReportResponse, error) 33 | } 34 | 35 | type CategoryI interface{ 36 | CreateCategory(req *pb.CreateCategoryRequest) (*pb.CreateCategoryResponse, error) 37 | UpdateCategory(req *pb.UpdateCategoryRequest) (*pb.UpdateCategoryResponse, error) 38 | DeleteCategory(req *pb.DeleteCategoryRequest) (*pb.DeleteCategoryResponse, error) 39 | GetCategory(req *pb.GetCategoryRequest) (*pb.GetCategoryResponse, error) 40 | ListCategories(req *pb.ListCategoriesRequest) (*pb.ListCategoriesResponse, error) 41 | } 42 | 43 | type GoalI interface { 44 | CreateGoal(req *pb.CreateGoalRequest) (*pb.CreateGoalResponse, error) 45 | UpdateGoal(req *pb.UpdateGoalRequest) (*pb.UpdateGoalResponse, error) 46 | DeleteGoal(req *pb.DeleteGoalRequest) (*pb.DeleteGoalResponse, error) 47 | GetGoal(req *pb.GetGoalRequest) (*pb.GetGoalResponse, error) 48 | ListGoals(req *pb.ListGoalsRequest) (*pb.ListGoalsResponse, error) 49 | GenerateGoalProgressReport(req *pb.GenerateGoalProgressReportRequest) (*pb.GenerateGoalProgressReportResponse, error) 50 | 51 | } 52 | 53 | type TransactionI interface { 54 | CreateTransaction(req *pb.CreateTransactionRequest) (*pb.CreateTransactionResponse, error) 55 | UpdateTransaction(req *pb.UpdateTransactionRequest) (*pb.UpdateTransactionResponse, error) 56 | DeleteTransaction(req *pb.DeleteTransactionRequest) (*pb.DeleteTransactionResponse, error) 57 | GetTransaction(req *pb.GetTransactionRequest) (*pb.GetTransactionResponse, error) 58 | ListTransactions(req *pb.ListTransactionsRequest) (*pb.ListTransactionsResponse, error) 59 | Spending(req *pb.SpendingRequest) (*pb.SpendingResponse, error) 60 | Income(req *pb.IncomeRequest) (*pb.IncomeResponse, error) 61 | } 62 | 63 | type NotificationI interface{ 64 | CreateNotification(req *pb.CreateNotificationRequest) (*pb.CreateNotificationResponse, error) 65 | GetNotification(req *pb.GetNotificationRequest) (*pb.GetNotificationResponse, error) 66 | } -------------------------------------------------------------------------------- /Budgeting_service/extra/extra_func.go: -------------------------------------------------------------------------------- 1 | package extra 2 | 3 | import ( 4 | pb "budgeting/genprotos" 5 | m "budgeting/models" 6 | ) 7 | 8 | func AccountToBSON(p *pb.Account) *m.Account { 9 | return &m.Account{ 10 | ID: p.Id, 11 | UserID: p.UserId, 12 | Name: p.Name, 13 | Type: p.Type, 14 | Balance: p.Balance, 15 | Currency: p.Currency, 16 | } 17 | } 18 | 19 | func BsonToAccount(b *m.Account) *pb.Account { 20 | return &pb.Account{ 21 | Id: b.ID, 22 | UserId: b.UserID, 23 | Name: b.Name, 24 | Type: b.Type, 25 | Balance: b.Balance, 26 | Currency: b.Currency, 27 | } 28 | } 29 | 30 | func CategoryToBSON(p *pb.Category) *m.Category { 31 | return &m.Category{ 32 | ID: p.Id, 33 | UserID: p.UserId, 34 | Name: p.Name, 35 | Type: p.Type, 36 | } 37 | } 38 | 39 | func BsonToCategory(b *m.Category) *pb.Category { 40 | return &pb.Category{ 41 | Id: b.ID, 42 | UserId: b.UserID, 43 | Name: b.Name, 44 | Type: b.Type, 45 | } 46 | } 47 | 48 | func BudgetToBSON(p *pb.Budget) *m.Budget { 49 | return &m.Budget{ 50 | ID: p.Id, 51 | UserID: p.UserId, 52 | CategoryID: p.CategoryId, 53 | Period: p.Period, 54 | Amount: p.Amount, 55 | StartDate: p.StartDate, 56 | EndDate: p.EndDate, 57 | } 58 | } 59 | 60 | func BsonToBudget(b *m.Budget) *pb.Budget { 61 | return &pb.Budget{ 62 | Id: b.ID, 63 | UserId: b.UserID, 64 | CategoryId: b.CategoryID, 65 | Period: b.Period, 66 | Amount: b.Amount, 67 | StartDate: b.StartDate, 68 | EndDate: b.EndDate, 69 | } 70 | } 71 | 72 | func GoalToBSON(p *pb.Goal) *m.Goal { 73 | return &m.Goal{ 74 | ID: p.Id, 75 | UserID: p.UserId, 76 | Name: p.Name, 77 | TargetAmount: p.TargetAmount, 78 | CurrentAmount: p.CurrentAmount, 79 | Deadline: p.Deadline, 80 | Status: p.Status, 81 | } 82 | } 83 | 84 | func BsonToGoal(b *m.Goal) *pb.Goal { 85 | return &pb.Goal{ 86 | Id: b.ID, 87 | UserId: b.UserID, 88 | Name: b.Name, 89 | TargetAmount: b.TargetAmount, 90 | CurrentAmount: b.CurrentAmount, 91 | Deadline: b.Deadline, 92 | Status: b.Status, 93 | } 94 | } 95 | 96 | func TransactionToBSON(p *pb.Transaction) *m.Transaction { 97 | return &m.Transaction{ 98 | ID: p.Id, 99 | UserID: p.UserId, 100 | CategoryID: p.CategoryId, 101 | AccountID: p.AccountId, 102 | Amount: p.Amount, 103 | Type: p.Type, 104 | Description: p.Description, 105 | Date: p.Date, 106 | } 107 | } 108 | 109 | func BsonToTransaction(b *m.Transaction) *pb.Transaction { 110 | return &pb.Transaction{ 111 | Id: b.ID, 112 | UserId: b.UserID, 113 | CategoryId: b.CategoryID, 114 | AccountId: b.AccountID, 115 | Amount: b.Amount, 116 | Type: b.Type, 117 | Description: b.Description, 118 | Date: b.Date, 119 | } 120 | } 121 | 122 | 123 | func BsonToNotification(b *m.Notification) *pb.Notification{ 124 | return &pb.Notification{ 125 | Id: b.ID, 126 | UserId: b.UserID, 127 | Message: b.Message, 128 | } 129 | } 130 | 131 | func NotificationToBson(p *pb.Notification) *m.Notification{ 132 | return &m.Notification{ 133 | ID: p.Id, 134 | UserID: p.UserId, 135 | Message: p.Message, 136 | } 137 | } -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '1.0' 2 | 3 | services: 4 | api-gateway: 5 | container_name: gateway1 6 | build: ./Api_Gateway 7 | depends_on: 8 | - auth-service 9 | - budgeting-service 10 | ports: 11 | - "8082:8082" 12 | networks: 13 | - budgeting 14 | volumes: 15 | - ./Api_Gateway:/home/sobirov/go/src/gitlab.com/PersonalFinanceTracker/Api_Gateway 16 | 17 | auth-service: 18 | container_name: auth_service2 19 | build: ./Auth_service 20 | depends_on: 21 | - postgres-db 22 | - budgeting-service 23 | - kafka 24 | - redis 25 | - zookeeper 26 | ports: 27 | - "8000:8000" 28 | networks: 29 | - budgeting 30 | 31 | budgeting-service: 32 | container_name: budgeting_service 33 | build: ./Budgeting_service 34 | depends_on: 35 | - mongo-db 36 | ports: 37 | - "50055:50055" 38 | networks: 39 | - budgeting 40 | environment: 41 | MONGO_URI: mongodb://mongo-db:27017/budgeting 42 | 43 | postgres-db: 44 | container_name: auth 45 | image: postgres:16.3 46 | environment: 47 | POSTGRES_USER: azizbek 48 | POSTGRES_PASSWORD: 123 49 | POSTGRES_DB: auth 50 | volumes: 51 | - postgres_data:/var/lib/postgresql/data 52 | ports: 53 | - "5430:5432" 54 | networks: 55 | - budgeting 56 | restart: unless-stopped 57 | healthcheck: 58 | test: ["CMD-SHELL", "pg_isready -d auth -U azizbek"] 59 | interval: 30s 60 | timeout: 10s 61 | retries: 5 62 | 63 | migrate: 64 | image: migrate/migrate 65 | networks: 66 | - budgeting 67 | volumes: 68 | - ./Auth_service/migrations:/migrations 69 | command: [ "-path", "/migrations", "-database", "postgres://azizbek:123@auth:5432/auth?sslmode=disable", "up" ] 70 | depends_on: 71 | - postgres-db 72 | 73 | mongo-db: 74 | container_name: mongo-db 75 | image: mongo:latest 76 | volumes: 77 | - mongo_data:/data/db 78 | ports: 79 | - "27018:27017" 80 | networks: 81 | - budgeting 82 | restart: unless-stopped 83 | 84 | zookeeper: 85 | image: confluentinc/cp-zookeeper:7.4.4 86 | container_name: zookeeper1 87 | environment: 88 | ZOOKEEPER_CLIENT_PORT: 2181 89 | ZOOKEEPER_TICK_TIME: 2000 90 | ports: 91 | - 22181:2181 92 | networks: 93 | - budgeting 94 | 95 | kafka: 96 | image: confluentinc/cp-kafka:7.4.4 97 | container_name: kafka1 98 | depends_on: 99 | - zookeeper 100 | ports: 101 | - 29092:29092 102 | networks: 103 | - budgeting 104 | environment: 105 | KAFKA_BROKER_ID: 1 106 | KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 107 | KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,PLAINTEXT_HOST://localhost:29092 108 | KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT 109 | KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT 110 | KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 111 | 112 | redis: 113 | image: redis:latest 114 | container_name: redis1 115 | ports: 116 | - "6370:6370" 117 | networks: 118 | - budgeting 119 | 120 | networks: 121 | budgeting: 122 | driver: bridge 123 | 124 | volumes: 125 | postgres_data: 126 | mongo_data: 127 | -------------------------------------------------------------------------------- /Api_Gateway/api/api.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | handlerC "api/api/handler/budgeting" 5 | 6 | _ "api/docs" 7 | _ "api/genprotos/auth" 8 | _ "api/genprotos/budgeting" 9 | 10 | "api/api/middleware" 11 | "log" 12 | 13 | "github.com/casbin/casbin/v2" 14 | 15 | "github.com/gin-contrib/cors" 16 | "github.com/gin-gonic/gin" 17 | swaggerFiles "github.com/swaggo/files" 18 | ginSwagger "github.com/swaggo/gin-swagger" 19 | "google.golang.org/grpc" 20 | ) 21 | 22 | // @title Budgeting SYSTEM API 23 | // @version 1.0 24 | // @description Developing a platform that helps users track their spending, set a budget and manage their financial goals 25 | // @BasePath / 26 | // @securityDefinitions.apikey BearerAuth 27 | // @in header 28 | // @name Authorization 29 | func NewGin( /*AuthConn, */ budgetingConn *grpc.ClientConn) *gin.Engine { 30 | budgeting := handlerC.NewBudgetingHandler(budgetingConn) 31 | 32 | router := gin.Default() 33 | 34 | enforcer, err := casbin.NewEnforcer("/home/sobirov/go/src/gitlab.com/PersonalFinanceTracker/Api_Gateway/api/model.conf", "/home/sobirov/go/src/gitlab.com/PersonalFinanceTracker/Api_Gateway/api/policy.csv") 35 | if err != nil { 36 | log.Fatal(err) 37 | } 38 | 39 | // sw := router.Group("/") 40 | router.Use(middleware.NewAuth(enforcer)) 41 | 42 | router.GET("/api/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) 43 | router.Use(cors.New(cors.Config{ 44 | AllowOrigins: []string{"*"}, 45 | AllowMethods: []string{"GET", "POST", "PUT", "DELETE"}, 46 | AllowHeaders: []string{"Origin", "Content-Type", "Accept", "Authorization"}, 47 | ExposeHeaders: []string{"Content-Length"}, 48 | AllowCredentials: true, 49 | })) 50 | 51 | account := router.Group("/account") 52 | { 53 | account.POST("/create", budgeting.CreateAccount) 54 | account.PUT("/update", budgeting.UpdateAccount) 55 | account.GET("/get/:id", budgeting.GetAccount) 56 | account.DELETE("/delete/:id",budgeting.DeleteAccount) 57 | account.GET("/get",budgeting.ListAccounts) 58 | } 59 | budget := router.Group("/budget") 60 | { 61 | budget.POST("/create", budgeting.CreateBudget) 62 | budget.PUT("/update", budgeting.UpdateBudget) 63 | budget.GET("/get/:id", budgeting.GetBudget) 64 | budget.DELETE("/delete/:id",budgeting.DeleteBudget) 65 | budget.GET("/get",budgeting.ListBudgets) 66 | budget.GET("/:id/performance-report",budgeting.GenerateBudgetPerformanceReport) 67 | } 68 | category := router.Group("/category") 69 | { 70 | category.POST("/create", budgeting.CreateCategory) 71 | category.PUT("/update", budgeting.UpdateCategory) 72 | category.GET("/get/:id", budgeting.GetCategory) 73 | category.DELETE("/delete/:id",budgeting.DeleteCategory) 74 | category.GET("/get",budgeting.ListCategories) 75 | } 76 | 77 | goal := router.Group("/goal") 78 | { 79 | goal.POST("/create", budgeting.CreateGoal) 80 | goal.PUT("/update", budgeting.UpdateGoal) 81 | goal.GET("/get/:id", budgeting.GetGoal) 82 | goal.DELETE("/delete/:id",budgeting.DeleteGoal) 83 | goal.GET("/get",budgeting.ListGoals) 84 | goal.GET("/getprogress/:id",budgeting.GenerateGoalProgressReport) 85 | } 86 | 87 | transaction := router.Group("/transaction") 88 | { 89 | transaction.POST("/create", budgeting.CreateTransaction) 90 | transaction.PUT("/update", budgeting.UpdateTransaction) 91 | transaction.GET("/get/:id", budgeting.GetTransaction) 92 | transaction.DELETE("/delete/:id",budgeting.DeleteTransaction) 93 | transaction.GET("/get",budgeting.ListTransactions) 94 | transaction.GET("/income",budgeting.Income) 95 | transaction.GET("/spending",budgeting.Spending) 96 | } 97 | 98 | notification := router.Group("/notification") 99 | { 100 | notification.GET("/get",budgeting.ListNotifications) 101 | } 102 | 103 | return router 104 | } 105 | -------------------------------------------------------------------------------- /Budgeting_service/storage/mongo/account.go: -------------------------------------------------------------------------------- 1 | package mongo 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | 8 | e "budgeting/extra" 9 | pb "budgeting/genprotos" 10 | m "budgeting/models" 11 | 12 | "go.mongodb.org/mongo-driver/bson" 13 | "go.mongodb.org/mongo-driver/mongo" 14 | "go.mongodb.org/mongo-driver/mongo/options" 15 | ) 16 | 17 | type Account struct { 18 | mongo *mongo.Collection 19 | } 20 | 21 | func NewAccount(db *mongo.Database) *Account { 22 | return &Account{mongo: db.Collection("account")} 23 | } 24 | 25 | func (s *Account) CreateAccount(req *pb.CreateAccountRequest) (*pb.CreateAccountResponse, error) { 26 | bsonAccount := e.AccountToBSON(req.Account) 27 | _, err := s.mongo.InsertOne(context.TODO(), bsonAccount) 28 | if err != nil { 29 | return nil, err 30 | } 31 | return &pb.CreateAccountResponse{}, nil 32 | } 33 | 34 | func (s *Account) UpdateAccount(req *pb.UpdateAccountRequest) (*pb.UpdateAccountResponse, error) { 35 | bsonAccount := e.AccountToBSON(req.Account) 36 | filter := bson.M{"_id": bsonAccount.ID} 37 | update := bson.M{"$set": bsonAccount} 38 | 39 | _, err := s.mongo.UpdateOne(context.TODO(), filter, update) 40 | if err != nil { 41 | return nil, err 42 | } 43 | 44 | return &pb.UpdateAccountResponse{}, nil 45 | } 46 | 47 | func (s *Account) DeleteAccount(req *pb.DeleteAccountRequest) (*pb.DeleteAccountResponse, error) { 48 | _, err := s.mongo.DeleteOne(context.TODO(), bson.M{"_id": req.Id}) 49 | if err != nil { 50 | return nil, err 51 | } 52 | return &pb.DeleteAccountResponse{}, nil 53 | } 54 | 55 | func (s *Account) GetAccount(req *pb.GetAccountRequest) (*pb.GetAccountResponse, error) { 56 | var bsonAccount m.Account 57 | err := s.mongo.FindOne(context.TODO(), bson.M{"_id": req.Id}).Decode(&bsonAccount) 58 | if err == mongo.ErrNoDocuments { 59 | return nil, errors.New("Account not found") 60 | } else if err != nil { 61 | return nil, err 62 | } 63 | return &pb.GetAccountResponse{Account: e.BsonToAccount(&bsonAccount)}, nil 64 | } 65 | 66 | func (s *Account) ListAccounts(req *pb.ListAccountsRequest) (*pb.ListAccountsResponse, error) { 67 | limit := req.Limit 68 | page := req.Page 69 | skip := (page - 1) * limit 70 | 71 | cursor, err := s.mongo.Find(context.TODO(), bson.M{}, options.Find().SetSkip(int64(skip)).SetLimit(int64(limit))) 72 | if err != nil { 73 | return nil, err 74 | } 75 | defer cursor.Close(context.TODO()) 76 | 77 | var accounts []*pb.Account 78 | for cursor.Next(context.TODO()) { 79 | var bsonAccount m.Account 80 | if err := cursor.Decode(&bsonAccount); err != nil { 81 | return nil, err 82 | } 83 | account := e.BsonToAccount(&bsonAccount) 84 | accounts = append(accounts, account) 85 | } 86 | 87 | if err := cursor.Err(); err != nil { 88 | return nil, err 89 | } 90 | 91 | return &pb.ListAccountsResponse{ 92 | Accounts: accounts, 93 | }, nil 94 | } 95 | 96 | func (s *Account) GetAmount(req *pb.GetAmountRequest) (*pb.GetAmountResponse, error) { 97 | filter := bson.M{"user_id": req.UserId} 98 | 99 | var account m.Account 100 | 101 | err := s.mongo.FindOne(context.TODO(), filter).Decode(&account) 102 | if err == mongo.ErrNoDocuments { 103 | return nil, errors.New("account not found") 104 | } else if err != nil { 105 | return nil, err 106 | } 107 | return &pb.GetAmountResponse{ 108 | Balance: account.Balance, 109 | }, nil 110 | } 111 | 112 | func (s *Account) UpdateAmount(req *pb.UpdateAmountRequest) (*pb.UpdateAmountResponse, error) { 113 | filter := bson.M{"user_id": req.UserId} 114 | update := bson.M{ 115 | "$set": bson.M{ 116 | "balance": req.Balance, 117 | }, 118 | } 119 | 120 | _, err := s.mongo.UpdateOne(context.TODO(), filter, update) 121 | if err != nil { 122 | if err == mongo.ErrNoDocuments { 123 | return nil, fmt.Errorf("account not found") 124 | } 125 | return nil, err 126 | } 127 | 128 | return &pb.UpdateAmountResponse{}, nil 129 | } 130 | -------------------------------------------------------------------------------- /Budgeting_service/storage/mongo/goal.go: -------------------------------------------------------------------------------- 1 | package mongo 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "time" 8 | 9 | e "budgeting/extra" 10 | pb "budgeting/genprotos" 11 | m "budgeting/models" 12 | 13 | "go.mongodb.org/mongo-driver/bson" 14 | "go.mongodb.org/mongo-driver/mongo" 15 | "go.mongodb.org/mongo-driver/mongo/options" 16 | ) 17 | 18 | type Goal struct { 19 | mongo *mongo.Collection 20 | } 21 | 22 | func NewGoal(db *mongo.Database) *Goal { 23 | return &Goal{mongo: db.Collection("goal")} 24 | } 25 | 26 | func (s *Goal) CreateGoal(req *pb.CreateGoalRequest) (*pb.CreateGoalResponse, error) { 27 | bsonGoal := e.GoalToBSON(req.Goal) 28 | _, err := s.mongo.InsertOne(context.TODO(), bsonGoal) 29 | if err != nil { 30 | return nil, err 31 | } 32 | return &pb.CreateGoalResponse{}, nil 33 | } 34 | 35 | func (s *Goal) UpdateGoal(req *pb.UpdateGoalRequest) (*pb.UpdateGoalResponse, error) { 36 | bsonGoal := e.GoalToBSON(req.Goal) 37 | filter := bson.M{"_id": bsonGoal.ID} 38 | update := bson.M{"$set": bsonGoal} 39 | 40 | _, err := s.mongo.UpdateOne(context.TODO(), filter, update) 41 | if err != nil { 42 | return nil, err 43 | } 44 | 45 | return &pb.UpdateGoalResponse{}, nil 46 | } 47 | 48 | func (s *Goal) DeleteGoal(req *pb.DeleteGoalRequest) (*pb.DeleteGoalResponse, error) { 49 | _, err := s.mongo.DeleteOne(context.TODO(), bson.M{"_id": req.Id}) 50 | if err != nil { 51 | return nil, err 52 | } 53 | return &pb.DeleteGoalResponse{}, nil 54 | } 55 | 56 | func (s *Goal) GetGoal(req *pb.GetGoalRequest) (*pb.GetGoalResponse, error) { 57 | var bsonGoal m.Goal 58 | err := s.mongo.FindOne(context.TODO(), bson.M{"_id": req.Id}).Decode(&bsonGoal) 59 | if err == mongo.ErrNoDocuments { 60 | return nil, errors.New("goal not found") 61 | } else if err != nil { 62 | return nil, err 63 | } 64 | return &pb.GetGoalResponse{Goal: e.BsonToGoal(&bsonGoal)}, nil 65 | } 66 | 67 | func (s *Goal) ListGoals(req *pb.ListGoalsRequest) (*pb.ListGoalsResponse, error) { 68 | limit := req.Limit 69 | page := req.Page 70 | skip := (page - 1) * limit 71 | 72 | cursor, err := s.mongo.Find(context.TODO(), bson.M{}, options.Find().SetSkip(int64(skip)).SetLimit(int64(limit))) 73 | if err != nil { 74 | return nil, err 75 | } 76 | defer cursor.Close(context.TODO()) 77 | 78 | var accounts []*pb.Goal 79 | for cursor.Next(context.TODO()) { 80 | var bsonAccount m.Goal 81 | if err := cursor.Decode(&bsonAccount); err != nil { 82 | return nil, err 83 | } 84 | account := e.BsonToGoal(&bsonAccount) 85 | accounts = append(accounts, account) 86 | } 87 | 88 | if err := cursor.Err(); err != nil { 89 | return nil, err 90 | } 91 | 92 | return &pb.ListGoalsResponse{ 93 | Goals: accounts, 94 | }, nil 95 | } 96 | 97 | func (s *Goal) GenerateGoalProgressReport(req *pb.GenerateGoalProgressReportRequest) (*pb.GenerateGoalProgressReportResponse, error) { 98 | var bsonGoal m.Goal 99 | err := s.mongo.FindOne(context.TODO(), bson.M{"_id": req.Id}).Decode(&bsonGoal) 100 | if err == mongo.ErrNoDocuments { 101 | return nil, fmt.Errorf("goal not found") 102 | } else if err != nil { 103 | return nil, err 104 | } 105 | goal := e.BsonToGoal(&bsonGoal) 106 | 107 | deadlineStr := goal.Deadline + "T00:00:00Z" 108 | deadlineTime, err := time.Parse(time.RFC3339, deadlineStr) 109 | if err != nil { 110 | return nil, fmt.Errorf("invalid deadline format") 111 | } 112 | 113 | remainAmount := goal.TargetAmount - goal.CurrentAmount 114 | if remainAmount < 0 { 115 | remainAmount = 0 116 | } 117 | 118 | status := "in_progress" 119 | 120 | if time.Now().After(deadlineTime) { 121 | status = "failed" 122 | } 123 | 124 | resp := &pb.GenerateGoalProgressReportResponse{ 125 | UserId: goal.UserId, 126 | Name: goal.Name, 127 | TargetAmount: goal.TargetAmount, 128 | CurrentAmount: goal.CurrentAmount, 129 | RemainAmount: remainAmount, 130 | Deadline: goal.Deadline, 131 | Status: status, 132 | } 133 | 134 | return resp, nil 135 | } 136 | 137 | -------------------------------------------------------------------------------- /Auth_service/api/token/token.go: -------------------------------------------------------------------------------- 1 | package token 2 | 3 | import ( 4 | "log" 5 | "log/slog" 6 | "net/http" 7 | "strings" 8 | "time" 9 | 10 | "github.com/golang-jwt/jwt" 11 | "github.com/spf13/cast" 12 | "auth/config" 13 | pb "auth/genprotos" 14 | ) 15 | 16 | type JWTHandler struct { 17 | Sub string 18 | Exp string 19 | Iat string 20 | Role string 21 | SigningKey string 22 | Token string 23 | Timeout int 24 | } 25 | 26 | type Tokens struct { 27 | AccessToken string 28 | RefreshToken string 29 | } 30 | 31 | var tokenKey = config.Load().TokenKey 32 | 33 | func GenereteJWTToken(user *pb.LoginUserResponse) *Tokens { 34 | accessToken := jwt.New(jwt.SigningMethodHS256) 35 | refreshToken := jwt.New(jwt.SigningMethodHS256) 36 | 37 | claims := accessToken.Claims.(jwt.MapClaims) 38 | claims["id"] = user.Id 39 | claims["email"] = user.Email 40 | claims["password_hash"] = user.PasswordHash 41 | claims["first_name"] = user.FirstName 42 | claims["last_name"] = user.LastName 43 | claims["role"] = user.Role 44 | claims["iat"] = time.Now().Unix() 45 | claims["exp"] = time.Now().Add(60 * time.Minute).Unix() 46 | access, err := accessToken.SignedString([]byte(tokenKey)) 47 | if err != nil { 48 | log.Fatal("error while genereting access token : ", err) 49 | } 50 | 51 | rftclaims := refreshToken.Claims.(jwt.MapClaims) 52 | rftclaims["id"] = user.Id 53 | rftclaims["email"] = user.Email 54 | claims["password_hash"] = user.PasswordHash 55 | claims["first_name"] = user.FirstName 56 | claims["last_name"] = user.LastName 57 | rftclaims["iat"] = time.Now().Unix() 58 | rftclaims["role"] = user.Role 59 | rftclaims["exp"] = time.Now().Add(24 * time.Hour).Unix() 60 | refresh, err := refreshToken.SignedString([]byte(tokenKey)) 61 | if err != nil { 62 | log.Fatal("error while genereting refresh token : ", err) 63 | } 64 | 65 | return &Tokens{ 66 | AccessToken: access, 67 | RefreshToken: refresh, 68 | } 69 | } 70 | 71 | func ExtractClaim(cfg *config.Config, tokenStr string) (jwt.MapClaims, error) { 72 | var ( 73 | token *jwt.Token 74 | err error 75 | ) 76 | 77 | keyFunc := func(token *jwt.Token) (interface{}, error) { 78 | return []byte(cfg.TokenKey), nil 79 | } 80 | 81 | token, err = jwt.Parse(tokenStr, keyFunc) 82 | if err != nil { 83 | return nil, err 84 | } 85 | 86 | claims, ok := token.Claims.(jwt.MapClaims) 87 | if !(ok && token.Valid) { 88 | return nil, err 89 | } 90 | 91 | return claims, nil 92 | } 93 | 94 | func (jwtHandler *JWTHandler) ExtractClaims() (jwt.MapClaims, error) { 95 | token, err := jwt.Parse(jwtHandler.Token, func(t *jwt.Token) (interface{}, error) { 96 | return []byte(jwtHandler.SigningKey), nil 97 | }) 98 | 99 | if err != nil { 100 | return nil, err 101 | } 102 | 103 | claims, ok := token.Claims.(jwt.MapClaims) 104 | if !(ok && token.Valid) { 105 | slog.Error("invalid jwt token") 106 | return nil, err 107 | } 108 | return claims, nil 109 | } 110 | 111 | func GetIdFromToken(r *http.Request, cfg *config.Config) (string, int) { 112 | var softToken string 113 | token := r.Header.Get("Authorization") 114 | 115 | if token == "" { 116 | return "unauthorized", http.StatusUnauthorized 117 | } else if strings.Contains(token, "Bearer") { 118 | softToken = strings.TrimPrefix(token, "Bearer ") 119 | } else { 120 | softToken = token 121 | } 122 | 123 | claims, err := ExtractClaim(cfg, softToken) 124 | if err != nil { 125 | return "unauthorized", http.StatusUnauthorized 126 | } 127 | 128 | return cast.ToString(claims["id"]), 0 129 | } 130 | 131 | func GetEmailFromToken(r *http.Request, cfg *config.Config) (string, int) { 132 | var softToken string 133 | token := r.Header.Get("Authorization") 134 | 135 | if token == "" { 136 | return "unauthorized", http.StatusUnauthorized 137 | } else if strings.Contains(token, "Bearer") { 138 | softToken = strings.TrimPrefix(token, "Bearer ") 139 | } else { 140 | softToken = token 141 | } 142 | 143 | claims, err := ExtractClaim(cfg, softToken) 144 | if err != nil { 145 | return "unauthorized", http.StatusUnauthorized 146 | } 147 | 148 | return cast.ToString(claims["email"]), 0 149 | } 150 | -------------------------------------------------------------------------------- /Api_Gateway/api/token/token.go: -------------------------------------------------------------------------------- 1 | package token 2 | 3 | import ( 4 | "log" 5 | "log/slog" 6 | "net/http" 7 | "strings" 8 | "time" 9 | 10 | "github.com/golang-jwt/jwt" 11 | "github.com/spf13/cast" 12 | "api/config" 13 | pb "api/genprotos/auth" 14 | ) 15 | 16 | type JWTHandler struct { 17 | Sub string 18 | Exp string 19 | Iat string 20 | Role string 21 | SigningKey string 22 | Token string 23 | Timeout int 24 | } 25 | 26 | type Tokens struct { 27 | AccessToken string 28 | RefreshToken string 29 | } 30 | 31 | var tokenKey = config.Load().TokenKey 32 | 33 | func GenereteJWTToken(user *pb.LoginUserResponse) *Tokens { 34 | accessToken := jwt.New(jwt.SigningMethodHS256) 35 | refreshToken := jwt.New(jwt.SigningMethodHS256) 36 | 37 | claims := accessToken.Claims.(jwt.MapClaims) 38 | claims["id"] = user.Id 39 | claims["email"] = user.Email 40 | claims["first_name"] = user.FirstName 41 | claims["last_name"] = user.LastName 42 | claims["password_hash"] = user.PasswordHash 43 | claims["role"] = user.Role 44 | claims["iat"] = time.Now().Unix() 45 | claims["exp"] = time.Now().Add(60 * time.Minute).Unix() 46 | access, err := accessToken.SignedString([]byte(tokenKey)) 47 | if err != nil { 48 | log.Fatal("error while genereting access token : ", err) 49 | } 50 | 51 | rftclaims := refreshToken.Claims.(jwt.MapClaims) 52 | rftclaims["id"] = user.Id 53 | rftclaims["email"] = user.Email 54 | rftclaims["first_name"] = user.FirstName 55 | rftclaims["last_name"] = user.LastName 56 | rftclaims["password_hash"] = user.PasswordHash 57 | rftclaims["role"] = user.Role 58 | rftclaims["iat"] = time.Now().Unix() 59 | rftclaims["exp"] = time.Now().Add(60 * time.Minute).Unix() 60 | refresh, err := refreshToken.SignedString([]byte(tokenKey)) 61 | if err != nil { 62 | log.Fatal("error while genereting refresh token : ", err) 63 | } 64 | 65 | return &Tokens{ 66 | AccessToken: access, 67 | RefreshToken: refresh, 68 | } 69 | } 70 | 71 | func ExtractClaim(cfg *config.Config, tokenStr string) (jwt.MapClaims, error) { 72 | var ( 73 | token *jwt.Token 74 | err error 75 | ) 76 | 77 | keyFunc := func(token *jwt.Token) (interface{}, error) { 78 | return []byte(cfg.TokenKey), nil 79 | } 80 | 81 | token, err = jwt.Parse(tokenStr, keyFunc) 82 | if err != nil { 83 | return nil, err 84 | } 85 | 86 | claims, ok := token.Claims.(jwt.MapClaims) 87 | if !(ok && token.Valid) { 88 | return nil, err 89 | } 90 | 91 | return claims, nil 92 | } 93 | 94 | func (jwtHandler *JWTHandler) ExtractClaims() (jwt.MapClaims, error) { 95 | token, err := jwt.Parse(jwtHandler.Token, func(t *jwt.Token) (interface{}, error) { 96 | return []byte(jwtHandler.SigningKey), nil 97 | }) 98 | 99 | if err != nil { 100 | return nil, err 101 | } 102 | 103 | claims, ok := token.Claims.(jwt.MapClaims) 104 | if !(ok && token.Valid) { 105 | slog.Error("invalid jwt token") 106 | return nil, err 107 | } 108 | return claims, nil 109 | } 110 | 111 | func GetIdFromToken(r *http.Request, cfg *config.Config) (string, int) { 112 | var softToken string 113 | token := r.Header.Get("Authorization") 114 | 115 | if token == "" { 116 | return "unauthorized", http.StatusUnauthorized 117 | } else if strings.Contains(token, "Bearer") { 118 | softToken = strings.TrimPrefix(token, "Bearer ") 119 | } else { 120 | softToken = token 121 | } 122 | 123 | claims, err := ExtractClaim(cfg, softToken) 124 | if err != nil { 125 | return "unauthorized", http.StatusUnauthorized 126 | } 127 | 128 | return cast.ToString(claims["id"]), 0 129 | } 130 | 131 | func GetEmailFromToken(r *http.Request, cfg *config.Config) (string, int) { 132 | var softToken string 133 | token := r.Header.Get("Authorization") 134 | 135 | if token == "" { 136 | return "unauthorized", http.StatusUnauthorized 137 | } else if strings.Contains(token, "Bearer") { 138 | softToken = strings.TrimPrefix(token, "Bearer ") 139 | } else { 140 | softToken = token 141 | } 142 | 143 | claims, err := ExtractClaim(cfg, softToken) 144 | if err != nil { 145 | return "unauthorized", http.StatusUnauthorized 146 | } 147 | 148 | return cast.ToString(claims["email"]), 0 149 | } 150 | -------------------------------------------------------------------------------- /Budgeting_service/service/goal.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | pb "budgeting/genprotos" 5 | "budgeting/storage" 6 | "context" 7 | "errors" 8 | "fmt" 9 | "time" 10 | 11 | "github.com/google/uuid" 12 | ) 13 | 14 | type GoalService struct { 15 | stg storage.StorageI 16 | pb.UnimplementedGoalServiceServer 17 | } 18 | 19 | func NewGoalService(stg *storage.StorageI) *GoalService { 20 | return &GoalService{stg: *stg} 21 | } 22 | 23 | func (s *GoalService) CreateGoal(c context.Context, req *pb.CreateGoalRequest) (*pb.CreateGoalResponse, error) { 24 | id := uuid.NewString() 25 | req.Goal.Id = id 26 | 27 | _, err := time.Parse("2006-01-02", req.Goal.Deadline) 28 | if err != nil { 29 | return nil, fmt.Errorf("invalid Deadline: must be a valid date in 'YYYY-MM-DD' format") 30 | } 31 | 32 | 33 | userBalanceResp, err := s.stg.Account().GetAmount(&pb.GetAmountRequest{UserId: req.Goal.UserId}) 34 | if err != nil { 35 | return nil, errors.New("user not found") 36 | } 37 | 38 | if req.Goal.TargetAmount <= userBalanceResp.Balance { 39 | return nil, errors.New("current amount greater than target amount") 40 | } 41 | 42 | req.Goal.Status = "in_progress" 43 | req.Goal.CurrentAmount = userBalanceResp.Balance 44 | 45 | _, err = s.stg.Goal().CreateGoal(req) 46 | if err != nil { 47 | return nil, fmt.Errorf("failed to create goal: %v", err) 48 | } 49 | 50 | return &pb.CreateGoalResponse{}, nil 51 | } 52 | 53 | 54 | func (s *GoalService) UpdateGoal(c context.Context, req *pb.UpdateGoalRequest) (*pb.UpdateGoalResponse, error) { 55 | _, err := s.stg.Goal().UpdateGoal(req) 56 | if err != nil { 57 | return nil, err 58 | } 59 | 60 | user_id, err := s.stg.Account().GetAmount(&pb.GetAmountRequest{UserId: req.Goal.UserId}) 61 | if err != nil { 62 | return nil, errors.New("user not found") 63 | } 64 | 65 | if req.Goal.TargetAmount <= user_id.Balance { 66 | return nil, errors.New("current amount greater than target amount") 67 | } 68 | req.Goal.Status = "in_progress" 69 | 70 | req.Goal.CurrentAmount = user_id.Balance 71 | if req.Goal.CurrentAmount >= req.Goal.TargetAmount { 72 | req.Goal.Status = "achieved" 73 | } 74 | deadlineStr := req.Goal.Deadline + "T00:00:00Z" 75 | deadlineTime, err := time.Parse(time.RFC3339, deadlineStr) 76 | if err != nil { 77 | return nil, fmt.Errorf("invalid deadline format") 78 | } 79 | if time.Now().After(deadlineTime) { 80 | req.Goal.Status = "failed" 81 | } 82 | 83 | return &pb.UpdateGoalResponse{}, nil 84 | } 85 | 86 | func (s *GoalService) DeleteGoal(c context.Context, req *pb.DeleteGoalRequest) (*pb.DeleteGoalResponse, error) { 87 | _, err := s.stg.Goal().DeleteGoal(req) 88 | if err != nil { 89 | return nil, err 90 | } 91 | return &pb.DeleteGoalResponse{}, nil 92 | } 93 | 94 | func (s *GoalService) GetGoal(c context.Context, req *pb.GetGoalRequest) (*pb.GetGoalResponse, error) { 95 | res, err := s.stg.Goal().GetGoal(req) 96 | if err != nil { 97 | return nil, err 98 | } 99 | 100 | accountres, err := s.stg.Account().GetAmount(&pb.GetAmountRequest{UserId: res.Goal.UserId}) 101 | if err != nil { 102 | return nil, errors.New("account not found") 103 | } 104 | 105 | res.Goal.CurrentAmount = accountres.Balance 106 | 107 | return res, nil 108 | } 109 | 110 | func (s *GoalService) ListGoals(c context.Context, req *pb.ListGoalsRequest) (*pb.ListGoalsResponse, error) { 111 | res, err := s.stg.Goal().ListGoals(req) 112 | if err != nil { 113 | return nil, err 114 | } 115 | 116 | for _, goal := range res.Goals { 117 | accountres, err := s.stg.Account().GetAmount(&pb.GetAmountRequest{UserId: goal.UserId}) 118 | if err != nil { 119 | return nil, errors.New("account not found") 120 | } 121 | 122 | goal.CurrentAmount = accountres.Balance 123 | } 124 | 125 | return res, nil 126 | } 127 | 128 | func (s *GoalService) GenerateGoalProgressReport(c context.Context, req *pb.GenerateGoalProgressReportRequest) (*pb.GenerateGoalProgressReportResponse, error) { 129 | res, err := s.stg.Goal().GenerateGoalProgressReport(req) 130 | if err != nil { 131 | return nil, err 132 | } 133 | 134 | accountres, err := s.stg.Account().GetAmount(&pb.GetAmountRequest{UserId: res.UserId}) 135 | if err != nil { 136 | return nil, errors.New("account not found") 137 | } 138 | 139 | res.CurrentAmount = accountres.Balance 140 | 141 | res.CurrentAmount = accountres.Balance 142 | res.RemainAmount = res.TargetAmount - res.CurrentAmount 143 | if res.CurrentAmount >= res.TargetAmount{ 144 | res.Status = "achieved" 145 | } 146 | 147 | return res, nil 148 | } 149 | -------------------------------------------------------------------------------- /Budgeting_service/storage/mongo/budget.go: -------------------------------------------------------------------------------- 1 | package mongo 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "time" 8 | 9 | e "budgeting/extra" 10 | pb "budgeting/genprotos" 11 | m "budgeting/models" 12 | 13 | "go.mongodb.org/mongo-driver/bson" 14 | "go.mongodb.org/mongo-driver/mongo" 15 | "go.mongodb.org/mongo-driver/mongo/options" 16 | ) 17 | 18 | type Budget struct { 19 | mongo *mongo.Collection 20 | } 21 | 22 | func NewBudget(db *mongo.Database) *Budget { 23 | return &Budget{mongo: db.Collection("budget")} 24 | } 25 | 26 | func (s *Budget) CreateBudget(req *pb.CreateBudgetRequest) (*pb.CreateBudgetResponse, error) { 27 | bsonBudget := e.BudgetToBSON(req.Budget) 28 | _, err := s.mongo.InsertOne(context.TODO(), bsonBudget) 29 | if err != nil { 30 | return nil, err 31 | } 32 | return &pb.CreateBudgetResponse{}, nil 33 | } 34 | 35 | func (s *Budget) UpdateBudget(req *pb.UpdateBudgetRequest) (*pb.UpdateBudgetResponse, error) { 36 | bsonBudget := e.BudgetToBSON(req.Budget) 37 | filter := bson.M{"_id": bsonBudget.ID} 38 | update := bson.M{"$set": bsonBudget} 39 | 40 | _, err := s.mongo.UpdateOne(context.TODO(), filter, update) 41 | if err != nil { 42 | return nil, err 43 | } 44 | 45 | return &pb.UpdateBudgetResponse{}, nil 46 | } 47 | 48 | func (s *Budget) DeleteBudget(req *pb.DeleteBudgetRequest) (*pb.DeleteBudgetResponse, error) { 49 | _, err := s.mongo.DeleteOne(context.TODO(), bson.M{"_id": req.Id}) 50 | if err != nil { 51 | return nil, err 52 | } 53 | return &pb.DeleteBudgetResponse{}, nil 54 | 55 | } 56 | 57 | func (s *Budget) GetBudget(req *pb.GetBudgetRequest) (*pb.GetBudgetResponse, error) { 58 | var bsonGoal m.Budget 59 | err := s.mongo.FindOne(context.TODO(), bson.M{"_id": req.Id}).Decode(&bsonGoal) 60 | if err == mongo.ErrNoDocuments { 61 | return nil, errors.New("budget not found") 62 | } else if err != nil { 63 | return nil, err 64 | } 65 | return &pb.GetBudgetResponse{Budget: e.BsonToBudget(&bsonGoal)}, nil 66 | } 67 | 68 | func (s *Budget) ListBudgets(req *pb.ListBudgetsRequest) (*pb.ListBudgetsResponse, error) { 69 | limit := req.Limit 70 | page := req.Page 71 | skip := (page - 1) * limit 72 | 73 | cursor, err := s.mongo.Find(context.TODO(), bson.M{}, options.Find().SetSkip(int64(skip)).SetLimit(int64(limit))) 74 | if err != nil { 75 | return nil, err 76 | } 77 | defer cursor.Close(context.TODO()) 78 | 79 | var accounts []*pb.Budget 80 | for cursor.Next(context.TODO()) { 81 | var bsonAccount m.Budget 82 | if err := cursor.Decode(&bsonAccount); err != nil { 83 | return nil, err 84 | } 85 | account := e.BsonToBudget(&bsonAccount) 86 | accounts = append(accounts, account) 87 | } 88 | 89 | if err := cursor.Err(); err != nil { 90 | return nil, err 91 | } 92 | 93 | return &pb.ListBudgetsResponse{ 94 | Budgets: accounts, 95 | }, nil 96 | } 97 | 98 | func (s *Budget) GenerateBudgetPerformanceReport(req *pb.GenerateBudgetPerformanceReportRequest) (*pb.GenerateBudgetPerformanceReportResponse, error) { 99 | var bsonBudget m.Budget 100 | err := s.mongo.FindOne(context.TODO(), bson.M{"_id": req.Id}).Decode(&bsonBudget) 101 | if err == mongo.ErrNoDocuments { 102 | return nil, fmt.Errorf("budget not found") 103 | } else if err != nil { 104 | return nil, err 105 | } 106 | budget := e.BsonToBudget(&bsonBudget) 107 | 108 | startTime, err := time.Parse(time.RFC3339, budget.StartDate+"T00:00:00Z") 109 | if err != nil { 110 | return nil, fmt.Errorf("invalid start date format") 111 | } 112 | 113 | endTime, err := time.Parse(time.RFC3339, budget.EndDate+"T23:59:59Z") 114 | if err != nil { 115 | return nil, fmt.Errorf("invalid end date format") 116 | } 117 | 118 | valid := false 119 | switch budget.Period { 120 | case "daily": 121 | valid = endTime.Sub(startTime).Hours() <= 24 122 | case "weekly": 123 | valid = endTime.Sub(startTime).Hours() <= 7*24 124 | case "monthly": 125 | valid = endTime.Sub(startTime).Hours() <= 31*24 126 | case "yearly": 127 | valid = endTime.Sub(startTime).Hours() <= 365*24 128 | default: 129 | return nil, fmt.Errorf("invalid period type") 130 | } 131 | 132 | if !valid { 133 | return nil, fmt.Errorf("start_date and end_date do not match the selected period") 134 | } 135 | 136 | spentamount := float32(0) 137 | 138 | resp := &pb.GenerateBudgetPerformanceReportResponse{ 139 | Id: budget.Id, 140 | UserId: budget.UserId, 141 | CategoryId: budget.CategoryId, 142 | Amount: budget.Amount, 143 | Period: budget.Period, 144 | StartDate: budget.StartDate, 145 | EndDate: budget.EndDate, 146 | SpentAmount: spentamount, 147 | } 148 | 149 | return resp, nil 150 | } 151 | -------------------------------------------------------------------------------- /Api_Gateway/api/handler/budgeting/account.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "api/api/token" 5 | "api/config" 6 | pb "api/genprotos/budgeting" 7 | "context" 8 | "net/http" 9 | "strconv" 10 | 11 | "github.com/gin-gonic/gin" 12 | ) 13 | 14 | // @Summary Create Account 15 | // @Description Create Account 16 | // @Tags Account 17 | // @Accept json 18 | // @Produce json 19 | // @Security BearerAuth 20 | // @Param Create body pb.CreateAccountRequest true "Create" 21 | // @Success 201 {object} string "Success" 22 | // @Failure 400 {string} string "Error" 23 | // @Router /account/create [post] 24 | func (h *BudgetingHandler) CreateAccount(ctx *gin.Context) { 25 | req := &pb.CreateAccountRequest{} 26 | if err := ctx.BindJSON(req); err != nil { 27 | ctx.JSON(http.StatusBadRequest, err.Error()) 28 | return 29 | } 30 | c := config.Load() 31 | id, _ := token.GetIdFromToken(ctx.Request, &c) 32 | req.Account.UserId = id 33 | 34 | _, err := h.Account.CreateAccount(context.Background(), req) 35 | if err != nil { 36 | ctx.JSON(http.StatusBadRequest, err.Error()) 37 | return 38 | } 39 | 40 | ctx.JSON(http.StatusCreated, gin.H{"message": "Account Create Successfully"}) 41 | } 42 | 43 | // @Summary Update Account 44 | // @Description Update Account 45 | // @Tags Account 46 | // @Accept json 47 | // @Produce json 48 | // @Security BearerAuth 49 | // @Param Update body pb.UpdateAccountRequest true "Update" 50 | // @Success 200 {object} string "Success" 51 | // @Failure 400 {string} string "Error" 52 | // @Router /account/update [put] 53 | func (h *BudgetingHandler) UpdateAccount(ctx *gin.Context) { 54 | req := &pb.UpdateAccountRequest{} 55 | if err := ctx.BindJSON(req); err != nil { 56 | ctx.JSON(http.StatusBadRequest, err.Error()) 57 | return 58 | } 59 | c := config.Load() 60 | id, _ := token.GetIdFromToken(ctx.Request, &c) 61 | req.Account.UserId = id 62 | 63 | _, err := h.Account.UpdateAccount(context.Background(), req) 64 | if err != nil { 65 | ctx.JSON(http.StatusBadRequest, err.Error()) 66 | return 67 | } 68 | 69 | ctx.JSON(http.StatusOK, gin.H{"message": "Account Updated Successfully"}) 70 | } 71 | 72 | // @Summary Delete Account 73 | // @Description Delete Account 74 | // @Tags Account 75 | // @Accept json 76 | // @Produce json 77 | // @Security BearerAuth 78 | // @Param id path string true "Account ID" 79 | // @Success 200 {object} string "Success" 80 | // @Failure 400 {string} string "Error" 81 | // @Router /account/delete/{id} [delete] 82 | func (h *BudgetingHandler) DeleteAccount(ctx *gin.Context) { 83 | id := ctx.Param("id") 84 | req := &pb.DeleteAccountRequest{Id: id} 85 | 86 | _, err := h.Account.DeleteAccount(context.Background(), req) 87 | if err != nil { 88 | ctx.JSON(http.StatusBadRequest, err.Error()) 89 | return 90 | } 91 | 92 | ctx.JSON(http.StatusOK, gin.H{"message": "Account Deleted Successfully"}) 93 | } 94 | 95 | // @Summary Get Account 96 | // @Description Get an existing Account record by ID 97 | // @Tags Account 98 | // @Accept json 99 | // @Produce json 100 | // @Security BearerAuth 101 | // @Param id path string true "Account ID" 102 | // @Success 200 {object} pb.GetAccountResponse 103 | // @Failure 400 {string} string "Error" 104 | // @Router /account/get/{id} [get] 105 | func (h *BudgetingHandler) GetAccount(ctx *gin.Context) { 106 | id := ctx.Param("id") 107 | req := &pb.GetAccountRequest{Id: id} 108 | 109 | res, err := h.Account.GetAccount(context.Background(), req) 110 | if err != nil { 111 | ctx.JSON(http.StatusBadRequest, err.Error()) 112 | return 113 | } 114 | 115 | ctx.JSON(http.StatusOK, res) 116 | } 117 | 118 | // @Summary ListAccounts 119 | // @Description ListAccounts 120 | // @Tags Account 121 | // @Accept json 122 | // @Produce json 123 | // @Security BearerAuth 124 | // @Param limit query int false "Limit" 125 | // @Param page query int false "Page" 126 | // @Success 200 {object} pb.ListAccountsResponse 127 | // @Failure 400 {string} string "Bad Request" 128 | // @Router /account/get [get] 129 | func (h *BudgetingHandler) ListAccounts(ctx *gin.Context) { 130 | defaultLimit := 10 131 | defaultPage := 1 132 | 133 | limitStr := ctx.Query("limit") 134 | pageStr := ctx.Query("page") 135 | 136 | limit, err := strconv.Atoi(limitStr) 137 | if err != nil || limit <= 0 { 138 | limit = defaultLimit 139 | } 140 | 141 | page, err := strconv.Atoi(pageStr) 142 | if err != nil || page <= 0 { 143 | page = defaultPage 144 | } 145 | 146 | req := &pb.ListAccountsRequest{ 147 | Limit: int32(limit), 148 | Page: int32(page), 149 | } 150 | 151 | res, err := h.Account.ListAccounts(context.Background(), req) 152 | if err != nil { 153 | ctx.JSON(http.StatusBadRequest, err.Error()) 154 | return 155 | } 156 | 157 | ctx.JSON(http.StatusOK, res) 158 | } 159 | -------------------------------------------------------------------------------- /Budgeting_service/storage/mongo/transaction.go: -------------------------------------------------------------------------------- 1 | package mongo 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | 7 | e "budgeting/extra" 8 | pb "budgeting/genprotos" 9 | m "budgeting/models" 10 | 11 | "go.mongodb.org/mongo-driver/bson" 12 | "go.mongodb.org/mongo-driver/mongo" 13 | "go.mongodb.org/mongo-driver/mongo/options" 14 | ) 15 | 16 | type Transaction struct { 17 | mongo *mongo.Collection 18 | } 19 | 20 | func NewTransaction(db *mongo.Database) *Transaction { 21 | return &Transaction{mongo: db.Collection("Transaction")} 22 | } 23 | 24 | func (s *Transaction) CreateTransaction(req *pb.CreateTransactionRequest) (*pb.CreateTransactionResponse, error) { 25 | bsonTransaction := e.TransactionToBSON(req.Transaction) 26 | _, err := s.mongo.InsertOne(context.TODO(), bsonTransaction) 27 | if err != nil { 28 | return nil, err 29 | } 30 | return &pb.CreateTransactionResponse{}, nil 31 | } 32 | 33 | func (s *Transaction) UpdateTransaction(req *pb.UpdateTransactionRequest) (*pb.UpdateTransactionResponse, error) { 34 | bsonTransaction := e.TransactionToBSON(req.Transaction) 35 | filter := bson.M{"_id": bsonTransaction.ID} 36 | update := bson.M{"$set": bsonTransaction} 37 | 38 | _, err := s.mongo.UpdateOne(context.TODO(), filter, update) 39 | if err != nil { 40 | return nil, err 41 | } 42 | 43 | return &pb.UpdateTransactionResponse{}, nil 44 | } 45 | 46 | func (s *Transaction) DeleteTransaction(req *pb.DeleteTransactionRequest) (*pb.DeleteTransactionResponse, error) { 47 | _, err := s.mongo.DeleteOne(context.TODO(), bson.M{"_id": req.Id}) 48 | if err != nil { 49 | return nil, err 50 | } 51 | return &pb.DeleteTransactionResponse{}, nil 52 | } 53 | 54 | func (s *Transaction) GetTransaction(req *pb.GetTransactionRequest) (*pb.GetTransactionResponse, error) { 55 | var bsonAccount m.Transaction 56 | err := s.mongo.FindOne(context.TODO(), bson.M{"_id": req.Id}).Decode(&bsonAccount) 57 | if err == mongo.ErrNoDocuments { 58 | return nil, errors.New("Transaction not found") 59 | } else if err != nil { 60 | return nil, err 61 | } 62 | return &pb.GetTransactionResponse{Transaction: e.BsonToTransaction(&bsonAccount)}, nil 63 | } 64 | 65 | func (s *Transaction) ListTransactions(req *pb.ListTransactionsRequest) (*pb.ListTransactionsResponse, error) { 66 | limit := req.Limit 67 | page := req.Page 68 | skip := (page - 1) * limit 69 | 70 | cursor, err := s.mongo.Find(context.TODO(), bson.M{}, options.Find().SetSkip(int64(skip)).SetLimit(int64(limit))) 71 | if err != nil { 72 | return nil, err 73 | } 74 | defer cursor.Close(context.TODO()) 75 | 76 | var accounts []*pb.Transaction 77 | for cursor.Next(context.TODO()) { 78 | var bsonAccount m.Transaction 79 | if err := cursor.Decode(&bsonAccount); err != nil { 80 | return nil, err 81 | } 82 | account := e.BsonToTransaction(&bsonAccount) 83 | accounts = append(accounts, account) 84 | } 85 | 86 | if err := cursor.Err(); err != nil { 87 | return nil, err 88 | } 89 | 90 | return &pb.ListTransactionsResponse{ 91 | Transactions: accounts, 92 | }, nil 93 | } 94 | 95 | func (s *Transaction) Spending(req *pb.SpendingRequest) (*pb.SpendingResponse, error) { 96 | filter := bson.M{"user_id": req.UserId, "type": "expense"} 97 | 98 | cursor, err := s.mongo.Find(context.TODO(), filter) 99 | if err != nil { 100 | return nil, err 101 | } 102 | defer cursor.Close(context.TODO()) 103 | 104 | var spendingCount int32 105 | var spendingMoney float32 106 | 107 | for cursor.Next(context.TODO()) { 108 | var bsonTransaction m.Transaction 109 | if err := cursor.Decode(&bsonTransaction); err != nil { 110 | return nil, err 111 | } 112 | 113 | spendingCount++ 114 | spendingMoney += bsonTransaction.Amount 115 | } 116 | 117 | if err := cursor.Err(); err != nil { 118 | return nil, err 119 | } 120 | 121 | return &pb.SpendingResponse{ 122 | SpendingCount: spendingCount, 123 | SpendingMoney: spendingMoney, 124 | }, nil 125 | } 126 | 127 | func (s *Transaction) Income(req *pb.IncomeRequest) (*pb.IncomeResponse, error) { 128 | filter := bson.M{"user_id": req.UserId, "type": "income"} 129 | 130 | cursor, err := s.mongo.Find(context.TODO(), filter) 131 | if err != nil { 132 | return nil, err 133 | } 134 | defer cursor.Close(context.TODO()) 135 | 136 | var incomeCount int32 137 | var incomeMoney float32 138 | 139 | for cursor.Next(context.TODO()) { 140 | var bsonTransaction m.Transaction 141 | if err := cursor.Decode(&bsonTransaction); err != nil { 142 | return nil, err 143 | } 144 | 145 | incomeCount++ 146 | incomeMoney += bsonTransaction.Amount 147 | } 148 | 149 | if err := cursor.Err(); err != nil { 150 | return nil, err 151 | } 152 | 153 | return &pb.IncomeResponse{ 154 | IncomeCount: incomeCount, 155 | IncomeMoney: incomeMoney, 156 | }, nil 157 | } 158 | -------------------------------------------------------------------------------- /Api_Gateway/api/handler/budgeting/category.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "api/api/token" 5 | "api/config" 6 | pb "api/genprotos/budgeting" 7 | "context" 8 | "net/http" 9 | "strconv" 10 | 11 | "github.com/gin-gonic/gin" 12 | ) 13 | 14 | // @Summary Create Category 15 | // @Description Create Category 16 | // @Tags Category 17 | // @Accept json 18 | // @Produce json 19 | // @Security BearerAuth 20 | // @Param Create body pb.CreateCategoryRequest true "Create" 21 | // @Success 201 {object} string "Success" 22 | // @Failure 400 {string} string "Error" 23 | // @Router /category/create [post] 24 | func (h *BudgetingHandler) CreateCategory(ctx *gin.Context) { 25 | req := &pb.CreateCategoryRequest{} 26 | if err := ctx.BindJSON(req); err != nil { 27 | ctx.JSON(http.StatusBadRequest, err.Error()) 28 | return 29 | } 30 | c := config.Load() 31 | id, _ := token.GetIdFromToken(ctx.Request, &c) 32 | req.Category.UserId = id 33 | 34 | _, err := h.Category.CreateCategory(context.Background(), req) 35 | if err != nil { 36 | ctx.JSON(http.StatusBadRequest, err.Error()) 37 | return 38 | } 39 | 40 | ctx.JSON(http.StatusCreated, gin.H{"message": "Category Create Successfully"}) 41 | } 42 | 43 | // @Summary Update Category 44 | // @Description Update Category 45 | // @Tags Category 46 | // @Accept json 47 | // @Produce json 48 | // @Security BearerAuth 49 | // @Param Update body pb.UpdateCategoryRequest true "Update" 50 | // @Success 200 {object} string "Success" 51 | // @Failure 400 {string} string "Error" 52 | // @Router /category/update [put] 53 | func (h *BudgetingHandler) UpdateCategory(ctx *gin.Context) { 54 | req := &pb.UpdateCategoryRequest{} 55 | if err := ctx.BindJSON(req); err != nil { 56 | ctx.JSON(http.StatusBadRequest, err.Error()) 57 | return 58 | } 59 | 60 | _, err := h.Category.UpdateCategory(context.Background(), req) 61 | if err != nil { 62 | ctx.JSON(http.StatusBadRequest, err.Error()) 63 | return 64 | } 65 | c := config.Load() 66 | id, _ := token.GetIdFromToken(ctx.Request, &c) 67 | req.Category.UserId = id 68 | 69 | ctx.JSON(http.StatusOK, gin.H{"message": "Category Updated Successfully"}) 70 | } 71 | 72 | // @Summary Delete Category 73 | // @Description Delete Category 74 | // @Tags Category 75 | // @Accept json 76 | // @Produce json 77 | // @Security BearerAuth 78 | // @Param id path string true "Category ID" 79 | // @Success 200 {object} string "Success" 80 | // @Failure 400 {string} string "Error" 81 | // @Router /category/delete/{id} [delete] 82 | func (h *BudgetingHandler) DeleteCategory(ctx *gin.Context) { 83 | id := ctx.Param("id") 84 | req := &pb.DeleteCategoryRequest{Id: id} 85 | 86 | _, err := h.Category.DeleteCategory(context.Background(), req) 87 | if err != nil { 88 | ctx.JSON(http.StatusBadRequest, err.Error()) 89 | return 90 | } 91 | 92 | ctx.JSON(http.StatusOK, gin.H{"message": "Category Deleted Successfully"}) 93 | } 94 | 95 | // @Summary Get Category 96 | // @Description Get an existing Category record by ID 97 | // @Tags Category 98 | // @Accept json 99 | // @Produce json 100 | // @Security BearerAuth 101 | // @Param id path string true "Category ID" 102 | // @Success 200 {object} pb.GetCategoryResponse 103 | // @Failure 400 {string} string "Error" 104 | // @Router /category/get/{id} [get] 105 | func (h *BudgetingHandler) GetCategory(ctx *gin.Context) { 106 | id := ctx.Param("id") 107 | req := &pb.GetCategoryRequest{Id: id} 108 | 109 | res, err := h.Category.GetCategory(context.Background(), req) 110 | if err != nil { 111 | ctx.JSON(http.StatusBadRequest, err.Error()) 112 | return 113 | } 114 | 115 | ctx.JSON(http.StatusOK, res) 116 | } 117 | 118 | // @Summary ListCategories 119 | // @Description ListCategories 120 | // @Tags Category 121 | // @Accept json 122 | // @Produce json 123 | // @Security BearerAuth 124 | // @Param limit query int false "Limit" 125 | // @Param page query int false "Page" 126 | // @Success 200 {object} pb.ListCategoriesResponse 127 | // @Failure 400 {string} string "Bad Request" 128 | // @Router /category/get [get] 129 | func (h *BudgetingHandler) ListCategories(ctx *gin.Context) { 130 | defaultLimit := 10 131 | defaultPage := 1 132 | 133 | limitStr := ctx.Query("limit") 134 | pageStr := ctx.Query("page") 135 | 136 | limit, err := strconv.Atoi(limitStr) 137 | if err != nil || limit <= 0 { 138 | limit = defaultLimit 139 | } 140 | 141 | page, err := strconv.Atoi(pageStr) 142 | if err != nil || page <= 0 { 143 | page = defaultPage 144 | } 145 | 146 | req := &pb.ListCategoriesRequest{ 147 | Limit: int32(limit), 148 | Page: int32(page), 149 | } 150 | 151 | res, err := h.Category.ListCategories(context.Background(), req) 152 | if err != nil { 153 | ctx.JSON(http.StatusBadRequest, err.Error()) 154 | return 155 | } 156 | 157 | ctx.JSON(http.StatusOK, res) 158 | } 159 | -------------------------------------------------------------------------------- /Budgeting_service/service/budget.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | pb "budgeting/genprotos" 5 | "budgeting/storage" 6 | "context" 7 | "errors" 8 | "fmt" 9 | "time" 10 | 11 | "github.com/google/uuid" 12 | ) 13 | 14 | type BudgetService struct { 15 | stg storage.StorageI 16 | pb.UnimplementedBudgetServiceServer 17 | } 18 | 19 | func NewBudgetService(stg *storage.StorageI) *BudgetService { 20 | return &BudgetService{stg: *stg} 21 | } 22 | 23 | func (s *BudgetService) CreateBudget(c context.Context, req *pb.CreateBudgetRequest) (*pb.CreateBudgetResponse, error) { 24 | id := uuid.NewString() 25 | req.Budget.Id = id 26 | 27 | if _, err := uuid.Parse(req.Budget.UserId); err != nil { 28 | return nil, fmt.Errorf("invalid UserId: must be a valid UUID") 29 | } 30 | 31 | if _, err := uuid.Parse(req.Budget.CategoryId); err != nil { 32 | return nil, fmt.Errorf("invalid CategoryId: must be a valid UUID") 33 | } 34 | 35 | if req.Budget.Period != "daily" && req.Budget.Period != "weekly" && req.Budget.Period != "monthly" && req.Budget.Period != "yearly" { 36 | return nil, fmt.Errorf("invalid Budget Period: must be 'daily' or 'weekly' or 'monthly' or 'yearly'") 37 | } 38 | 39 | _, err := time.Parse("2006-01-02", req.Budget.EndDate) 40 | if err != nil { 41 | return nil, fmt.Errorf("invalid EndDate: must be a valid date in 'YYYY-MM-DD' format") 42 | } 43 | 44 | _, err = time.Parse("2006-01-02", req.Budget.StartDate) 45 | if err != nil { 46 | return nil, fmt.Errorf("invalid StartDate: must be a valid date in 'YYYY-MM--DD' format") 47 | } 48 | 49 | _, err = s.stg.Budget().CreateBudget(req) 50 | if err != nil { 51 | return nil, err 52 | } 53 | return &pb.CreateBudgetResponse{}, nil 54 | } 55 | 56 | func (s *BudgetService) UpdateBudget(c context.Context, req *pb.UpdateBudgetRequest) (*pb.UpdateBudgetResponse, error) { 57 | if _, err := uuid.Parse(req.Budget.UserId); err != nil { 58 | return nil, fmt.Errorf("invalid UserId: must be a valid UUID") 59 | } 60 | 61 | if _, err := uuid.Parse(req.Budget.CategoryId); err != nil { 62 | return nil, fmt.Errorf("invalid CategoryId: must be a valid UUID") 63 | } 64 | 65 | if req.Budget.Period != "daily" && req.Budget.Period != "weekly" && req.Budget.Period != "monthly" && req.Budget.Period != "yearly" { 66 | return nil, fmt.Errorf("invalid Budget Period: must be 'daily' or 'weekly' or 'monthly' or 'yearly'") 67 | } 68 | 69 | _, err := time.Parse("2006-01-02", req.Budget.EndDate) 70 | if err != nil { 71 | return nil, fmt.Errorf("invalid EndDate: must be a valid date in 'YYYY-MM-DD' format") 72 | } 73 | 74 | _, err = time.Parse("2006-01-02", req.Budget.StartDate) 75 | if err != nil { 76 | return nil, fmt.Errorf("invalid StartDate: must be a valid date in 'YYYY-MM--DD' format") 77 | } 78 | 79 | _, err = s.stg.Budget().UpdateBudget(req) 80 | if err != nil { 81 | return nil, err 82 | } 83 | return &pb.UpdateBudgetResponse{}, nil 84 | } 85 | 86 | func (s *BudgetService) DeleteBudget(c context.Context, req *pb.DeleteBudgetRequest) (*pb.DeleteBudgetResponse, error) { 87 | _, err := s.stg.Budget().DeleteBudget(req) 88 | if err != nil { 89 | return nil, err 90 | } 91 | return &pb.DeleteBudgetResponse{}, nil 92 | } 93 | 94 | func (s *BudgetService) GetBudget(c context.Context, req *pb.GetBudgetRequest) (*pb.GetBudgetResponse, error) { 95 | res, err := s.stg.Budget().GetBudget(req) 96 | if err != nil { 97 | return nil, err 98 | } 99 | return res, nil 100 | } 101 | 102 | func (s *BudgetService) ListBudgets(c context.Context, req *pb.ListBudgetsRequest) (*pb.ListBudgetsResponse, error) { 103 | res, err := s.stg.Budget().ListBudgets(req) 104 | if err != nil { 105 | return nil, err 106 | } 107 | return res, nil 108 | } 109 | 110 | func (s *BudgetService) GenerateBudgetPerformanceReport(c context.Context, req *pb.GenerateBudgetPerformanceReportRequest) (*pb.GenerateBudgetPerformanceReportResponse, error) { 111 | res, err := s.stg.Budget().GenerateBudgetPerformanceReport(req) 112 | if err != nil { 113 | return nil, err 114 | } 115 | 116 | transactionres, err := s.stg.Transaction().ListTransactions(&pb.ListTransactionsRequest{}) 117 | if err != nil { 118 | return nil, errors.New("failed to retrieve transactions") 119 | } 120 | 121 | var spentAmount float32 = 0 122 | for _, transaction := range transactionres.Transactions { 123 | if transaction.Type == "expense" { 124 | spentAmount += transaction.Amount 125 | } 126 | } 127 | 128 | resp := &pb.GenerateBudgetPerformanceReportResponse{ 129 | Id: res.Id, 130 | UserId: res.UserId, 131 | CategoryId: res.CategoryId, 132 | Amount: res.Amount, 133 | Period: res.Period, 134 | StartDate: res.StartDate, 135 | EndDate: res.EndDate, 136 | SpentAmount: spentAmount, 137 | } 138 | 139 | if spentAmount >= res.Amount { 140 | message := fmt.Sprintf("You have exceeded your budget by spending %.2f sum.", spentAmount) 141 | notificationReq := &pb.CreateNotificationRequest{ 142 | Notification: &pb.Notification{ 143 | Id: uuid.NewString(), 144 | UserId: res.UserId, 145 | Message: message, 146 | }, 147 | } 148 | _, err = s.stg.Notification().CreateNotification(&pb.CreateNotificationRequest{Notification: notificationReq.Notification}) 149 | if err != nil { 150 | return nil, errors.New("notification not created") 151 | } 152 | } 153 | 154 | return resp, nil 155 | } 156 | -------------------------------------------------------------------------------- /Api_Gateway/api/handler/budgeting/goal.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "api/api/token" 5 | "api/config" 6 | pb "api/genprotos/budgeting" 7 | "context" 8 | "net/http" 9 | "strconv" 10 | 11 | "github.com/gin-gonic/gin" 12 | ) 13 | 14 | // @Summary Create Goal 15 | // @Description Create Goal 16 | // @Tags Goal 17 | // @Accept json 18 | // @Produce json 19 | // @Security BearerAuth 20 | // @Param Create body pb.CreateGoalRequest true "Create" 21 | // @Success 201 {object} string "Success" 22 | // @Failure 400 {string} string "Error" 23 | // @Router /goal/create [post] 24 | func (h *BudgetingHandler) CreateGoal(ctx *gin.Context) { 25 | 26 | req := &pb.CreateGoalRequest{} 27 | if err := ctx.BindJSON(req); err != nil { 28 | ctx.JSON(http.StatusBadRequest, err.Error()) 29 | return 30 | } 31 | c := config.Load() 32 | id, _ := token.GetIdFromToken(ctx.Request, &c) 33 | req.Goal.UserId = id 34 | 35 | _, err := h.Goal.CreateGoal(context.Background(), req) 36 | if err != nil { 37 | ctx.JSON(http.StatusBadRequest, err.Error()) 38 | return 39 | } 40 | 41 | ctx.JSON(http.StatusCreated, gin.H{"message": "Goal Create Successfully"}) 42 | } 43 | 44 | // @Summary Update Goal 45 | // @Description Update Goal 46 | // @Tags Goal 47 | // @Accept json 48 | // @Produce json 49 | // @Security BearerAuth 50 | // @Param Update body pb.UpdateGoalRequest true "Update" 51 | // @Success 200 {object} string "Success" 52 | // @Failure 400 {string} string "Error" 53 | // @Router /goal/update [put] 54 | func (h *BudgetingHandler) UpdateGoal(ctx *gin.Context) { 55 | req := &pb.UpdateGoalRequest{} 56 | if err := ctx.BindJSON(req); err != nil { 57 | ctx.JSON(http.StatusBadRequest, err.Error()) 58 | return 59 | } 60 | c := config.Load() 61 | id, _ := token.GetIdFromToken(ctx.Request, &c) 62 | req.Goal.UserId = id 63 | _, err := h.Goal.UpdateGoal(context.Background(), req) 64 | if err != nil { 65 | ctx.JSON(http.StatusBadRequest, err.Error()) 66 | return 67 | } 68 | 69 | ctx.JSON(http.StatusOK, gin.H{"message": "Goal Updated Successfully"}) 70 | } 71 | 72 | // @Summary Delete Goal 73 | // @Description Delete Goal 74 | // @Tags Goal 75 | // @Accept json 76 | // @Produce json 77 | // @Security BearerAuth 78 | // @Param id path string true "Goal ID" 79 | // @Success 200 {object} string "Success" 80 | // @Failure 400 {string} string "Error" 81 | // @Router /goal/delete/{id} [delete] 82 | func (h *BudgetingHandler) DeleteGoal(ctx *gin.Context) { 83 | id := ctx.Param("id") 84 | req := &pb.DeleteGoalRequest{Id: id} 85 | 86 | _, err := h.Goal.DeleteGoal(context.Background(), req) 87 | if err != nil { 88 | ctx.JSON(http.StatusBadRequest, err.Error()) 89 | return 90 | } 91 | 92 | ctx.JSON(http.StatusOK, gin.H{"message": "Goal Deleted Successfully"}) 93 | } 94 | 95 | // @Summary Get Goal 96 | // @Description Get an existing Goal record by ID 97 | // @Tags Goal 98 | // @Accept json 99 | // @Produce json 100 | // @Security BearerAuth 101 | // @Param id path string true "Goal ID" 102 | // @Success 200 {object} pb.GetGoalResponse 103 | // @Failure 400 {string} string "Error" 104 | // @Router /goal/get/{id} [get] 105 | func (h *BudgetingHandler) GetGoal(ctx *gin.Context) { 106 | id := ctx.Param("id") 107 | 108 | req := &pb.GetGoalRequest{Id: id} 109 | res, err := h.Goal.GetGoal(context.Background(), req) 110 | if err != nil { 111 | ctx.JSON(http.StatusBadRequest, gin.H{"error": "goal not found"}) 112 | return 113 | } 114 | ctx.JSON(http.StatusOK, res) 115 | } 116 | 117 | // @Summary List Goals 118 | // @Description List all goals with pagination 119 | // @Tags Goal 120 | // @Accept json 121 | // @Produce json 122 | // @Security BearerAuth 123 | // @Param limit query int false "Limit" 124 | // @Param page query int false "Page" 125 | // @Success 200 {object} pb.ListGoalsResponse 126 | // @Failure 400 {string} string "Bad Request" 127 | // @Router /goal/get [get] 128 | func (h *BudgetingHandler) ListGoals(ctx *gin.Context) { 129 | defaultLimit := 10 130 | defaultPage := 1 131 | 132 | limitStr := ctx.Query("limit") 133 | pageStr := ctx.Query("page") 134 | 135 | limit, err := strconv.Atoi(limitStr) 136 | if err != nil || limit <= 0 { 137 | limit = defaultLimit 138 | } 139 | 140 | page, err := strconv.Atoi(pageStr) 141 | if err != nil || page <= 0 { 142 | page = defaultPage 143 | } 144 | 145 | req := &pb.ListGoalsRequest{ 146 | Limit: int32(limit), 147 | Page: int32(page), 148 | } 149 | 150 | res, err := h.Goal.ListGoals(context.Background(), req) 151 | if err != nil { 152 | ctx.JSON(http.StatusBadRequest, gin.H{"error": "Failed to list goals"}) 153 | return 154 | } 155 | 156 | ctx.JSON(http.StatusOK, res) 157 | } 158 | 159 | // @Summary Generate Goal Progress Report 160 | // @Description Generate a progress report for a specific goal by ID 161 | // @Tags Goal 162 | // @Accept json 163 | // @Produce json 164 | // @Security BearerAuth 165 | // @Param id path string true "Goal ID" 166 | // @Success 200 {object} pb.GenerateGoalProgressReportResponse 167 | // @Failure 400 {string} string "Error" 168 | // @Router /goal/getprogress/{id} [get] 169 | func (h *BudgetingHandler) GenerateGoalProgressReport(ctx *gin.Context) { 170 | id := ctx.Param("id") 171 | req := &pb.GenerateGoalProgressReportRequest{Id: id} 172 | 173 | res, err := h.Goal.GenerateGoalProgressReport(context.Background(), req) 174 | if err != nil { 175 | ctx.JSON(http.StatusBadRequest, gin.H{"error": "Failed to generate goal progress report"}) 176 | return 177 | } 178 | ctx.JSON(http.StatusOK, res) 179 | } 180 | -------------------------------------------------------------------------------- /Budgeting_service/storage/testing/category_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | pb "budgeting/genprotos" 8 | m "budgeting/models" 9 | "budgeting/storage/mongo" 10 | 11 | "github.com/stretchr/testify/assert" 12 | "go.mongodb.org/mongo-driver/bson" 13 | "go.mongodb.org/mongo-driver/bson/primitive" 14 | "go.mongodb.org/mongo-driver/mongo/integration/mtest" 15 | ) 16 | 17 | func TestCreateCategory(t *testing.T) { 18 | mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock)) 19 | 20 | mt.Run("CreateCategory_Success", func(mt *mtest.T) { 21 | mt.AddMockResponses(mtest.CreateSuccessResponse()) 22 | 23 | budgetService := mongo.NewCategory(mt.DB) 24 | 25 | req := &pb.CreateCategoryRequest{ 26 | Category: &pb.Category{ 27 | Id: primitive.NewObjectID().Hex(), 28 | UserId: "user123", 29 | Name: "category123", 30 | Type: "income", 31 | }, 32 | } 33 | 34 | resp, err := budgetService.CreateCategory(req) 35 | 36 | assert.NoError(t, err) 37 | assert.NotNil(t, resp) 38 | assert.Equal(t, &pb.CreateCategoryResponse{}, resp) 39 | }) 40 | 41 | } 42 | 43 | func TestUpdateCategory(t *testing.T) { 44 | mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock)) 45 | 46 | mt.Run("UpdateCategory_Success", func(mt *mtest.T) { 47 | mt.AddMockResponses(mtest.CreateSuccessResponse()) 48 | 49 | budgetService := mongo.NewCategory(mt.DB) 50 | 51 | req := &pb.UpdateCategoryRequest{ 52 | Category: &pb.Category{ 53 | Id: primitive.NewObjectID().Hex(), 54 | UserId: "user123", 55 | Name: "category123", 56 | Type: "income", 57 | }, 58 | } 59 | 60 | resp, err := budgetService.UpdateCategory(req) 61 | 62 | assert.NoError(t, err) 63 | assert.NotNil(t, resp) 64 | assert.Equal(t, &pb.UpdateCategoryResponse{}, resp) 65 | }) 66 | } 67 | 68 | func TestGetCategory(t *testing.T) { 69 | mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock)) 70 | 71 | mt.Run("GetCategory_Success", func(mt *mtest.T) { 72 | expectedAccount := m.Category{ 73 | ID: "example-id", 74 | UserID: "user123", 75 | Name: "category123", 76 | Type: "monthly", 77 | } 78 | 79 | mt.AddMockResponses(mtest.CreateCursorResponse(1, "test.category", mtest.FirstBatch, bson.D{ 80 | {Key: "_id", Value: expectedAccount.ID}, 81 | {Key: "user_id", Value: expectedAccount.UserID}, 82 | {Key: "name", Value: expectedAccount.Name}, 83 | {Key: "type", Value: expectedAccount.Type}, 84 | })) 85 | 86 | accountService := mongo.NewCategory(mt.DB) 87 | 88 | req := &pb.GetCategoryRequest{Id: expectedAccount.ID} 89 | 90 | resp, err := accountService.GetCategory(req) 91 | 92 | assert.NoError(t, err) 93 | assert.NotNil(t, resp) 94 | assert.Equal(t, &pb.GetCategoryResponse{ 95 | Category: &pb.Category{ 96 | Id: expectedAccount.ID, 97 | UserId: expectedAccount.UserID, 98 | Name: expectedAccount.Name, 99 | Type: expectedAccount.Type, 100 | }, 101 | }, resp) 102 | }) 103 | 104 | mt.Run("GetCategory_NotFound", func(mt *mtest.T) { 105 | mt.AddMockResponses(mtest.CreateCursorResponse(0, "test.category", mtest.FirstBatch)) 106 | 107 | categoryService := mongo.NewCategory(mt.DB) 108 | 109 | req := &pb.GetCategoryRequest{Id: "nonexistent-id"} 110 | 111 | resp, err := categoryService.GetCategory(req) 112 | 113 | assert.Error(t, err) 114 | assert.Nil(t, resp) 115 | assert.Equal(t, errors.New("category not found"), err) 116 | }) 117 | 118 | mt.Run("GetCategory_Error", func(mt *mtest.T) { 119 | mt.AddMockResponses(mtest.CreateCommandErrorResponse(mtest.CommandError{ 120 | Code: 11000, 121 | Message: "internal server error", 122 | })) 123 | 124 | accountService := mongo.NewCategory(mt.DB) 125 | 126 | req := &pb.GetCategoryRequest{Id: "example-id"} 127 | 128 | resp, err := accountService.GetCategory(req) 129 | 130 | assert.Error(t, err) 131 | assert.Nil(t, resp) 132 | }) 133 | } 134 | 135 | func TestDeleteCategory(t *testing.T) { 136 | mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock)) 137 | 138 | mt.Run("DeleteCategory_Success", func(mt *mtest.T) { 139 | mt.AddMockResponses(mtest.CreateSuccessResponse()) 140 | 141 | budgetService := mongo.NewCategory(mt.DB) 142 | 143 | req := &pb.DeleteCategoryRequest{Id: "example-id"} 144 | 145 | resp, err := budgetService.DeleteCategory(req) 146 | 147 | assert.NoError(t, err) 148 | assert.NotNil(t, resp) 149 | assert.IsType(t, &pb.DeleteCategoryResponse{}, resp) 150 | }) 151 | 152 | mt.Run("DeleteCategory_NotFound", func(mt *mtest.T) { 153 | mt.AddMockResponses(mtest.CreateSuccessResponse()) 154 | 155 | budgetService := mongo.NewCategory(mt.DB) 156 | 157 | req := &pb.DeleteCategoryRequest{Id: "nonexistent-id"} 158 | 159 | resp, err := budgetService.DeleteCategory(req) 160 | 161 | assert.NoError(t, err) 162 | assert.NotNil(t, resp) 163 | assert.IsType(t, &pb.DeleteCategoryResponse{}, resp) 164 | }) 165 | 166 | mt.Run("DeleteCategory_Error", func(mt *mtest.T) { 167 | mt.AddMockResponses(mtest.CreateCommandErrorResponse(mtest.CommandError{ 168 | Code: 11000, 169 | Message: "internal server error", 170 | })) 171 | 172 | budgetService := mongo.NewCategory(mt.DB) 173 | 174 | req := &pb.DeleteCategoryRequest{Id: "example-id"} 175 | 176 | resp, err := budgetService.DeleteCategory(req) 177 | 178 | assert.Error(t, err) 179 | assert.Nil(t, resp) 180 | assert.Contains(t, err.Error(), "internal server error") 181 | }) 182 | } 183 | -------------------------------------------------------------------------------- /Api_Gateway/api/handler/budgeting/budget.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "api/api/token" 5 | "api/config" 6 | pb "api/genprotos/budgeting" 7 | "context" 8 | "net/http" 9 | "strconv" 10 | 11 | "github.com/gin-gonic/gin" 12 | ) 13 | 14 | // @Summary Create Budget 15 | // @Description Create Budget 16 | // @Tags Budget 17 | // @Accept json 18 | // @Produce json 19 | // @Security BearerAuth 20 | // @Param Create body pb.CreateBudgetRequest true "Create" 21 | // @Success 201 {object} string "Success" 22 | // @Failure 400 {string} string "Error" 23 | // @Router /budget/create [post] 24 | func (h *BudgetingHandler) CreateBudget(ctx *gin.Context) { 25 | req := &pb.CreateBudgetRequest{} 26 | if err := ctx.BindJSON(req); err != nil { 27 | ctx.JSON(http.StatusBadRequest, err.Error()) 28 | return 29 | } 30 | c := config.Load() 31 | id, _ := token.GetIdFromToken(ctx.Request, &c) 32 | req.Budget.UserId = id 33 | _, err := h.Budget.CreateBudget(context.Background(), req) 34 | if err != nil { 35 | ctx.JSON(http.StatusBadRequest, err.Error()) 36 | return 37 | } 38 | 39 | ctx.JSON(http.StatusCreated, gin.H{"message": "Budget Create Successfully"}) 40 | } 41 | 42 | // @Summary Update Budget 43 | // @Description Update Budget 44 | // @Tags Budget 45 | // @Accept json 46 | // @Produce json 47 | // @Security BearerAuth 48 | // @Param Update body pb.UpdateBudgetRequest true "Update" 49 | // @Success 200 {object} string "Success" 50 | // @Failure 400 {string} string "Error" 51 | // @Router /budget/update [put] 52 | func (h *BudgetingHandler) UpdateBudget(ctx *gin.Context) { 53 | req := &pb.UpdateBudgetRequest{} 54 | if err := ctx.BindJSON(req); err != nil { 55 | ctx.JSON(http.StatusBadRequest, err.Error()) 56 | return 57 | } 58 | c := config.Load() 59 | id, _ := token.GetIdFromToken(ctx.Request, &c) 60 | req.Budget.UserId = id 61 | 62 | _, err := h.Budget.UpdateBudget(context.Background(), req) 63 | if err != nil { 64 | ctx.JSON(http.StatusBadRequest, err.Error()) 65 | return 66 | } 67 | 68 | ctx.JSON(http.StatusOK, gin.H{"message": "Budget Updated Successfully"}) 69 | } 70 | 71 | // @Summary Delete Budget 72 | // @Description Delete Budget 73 | // @Tags Budget 74 | // @Accept json 75 | // @Produce json 76 | // @Security BearerAuth 77 | // @Param id path string true "Budget ID" 78 | // @Success 200 {object} string "Success" 79 | // @Failure 400 {string} string "Error" 80 | // @Router /budget/delete/{id} [delete] 81 | func (h *BudgetingHandler) DeleteBudget(ctx *gin.Context) { 82 | id := ctx.Param("id") 83 | req := &pb.DeleteBudgetRequest{Id: id} 84 | 85 | _, err := h.Budget.DeleteBudget(context.Background(), req) 86 | if err != nil { 87 | ctx.JSON(http.StatusBadRequest, err.Error()) 88 | return 89 | } 90 | 91 | ctx.JSON(http.StatusOK, gin.H{"message": "Budget Deleted Successfully"}) 92 | } 93 | 94 | // @Summary Get Budget 95 | // @Description Get an existing Budget record by ID 96 | // @Tags Budget 97 | // @Accept json 98 | // @Produce json 99 | // @Security BearerAuth 100 | // @Param id path string true "Budget ID" 101 | // @Success 200 {object} pb.GetBudgetResponse 102 | // @Failure 400 {string} string "Error" 103 | // @Router /budget/get/{id} [get] 104 | func (h *BudgetingHandler) GetBudget(ctx *gin.Context) { 105 | id := ctx.Param("id") 106 | req := &pb.GetBudgetRequest{Id: id} 107 | 108 | res, err := h.Budget.GetBudget(context.Background(), req) 109 | if err != nil { 110 | ctx.JSON(http.StatusBadRequest, err.Error()) 111 | return 112 | } 113 | 114 | ctx.JSON(http.StatusOK, res) 115 | } 116 | 117 | // @Summary ListBudgets 118 | // @Description ListBudgets 119 | // @Tags Budget 120 | // @Accept json 121 | // @Produce json 122 | // @Security BearerAuth 123 | // @Param limit query int false "Limit" 124 | // @Param page query int false "Page" 125 | // @Success 200 {object} pb.ListBudgetsResponse 126 | // @Failure 400 {string} string "Bad Request" 127 | // @Router /budget/get [get] 128 | func (h *BudgetingHandler) ListBudgets(ctx *gin.Context) { 129 | defaultLimit := 10 130 | defaultPage := 1 131 | 132 | limitStr := ctx.Query("limit") 133 | pageStr := ctx.Query("page") 134 | 135 | limit, err := strconv.Atoi(limitStr) 136 | if err != nil || limit <= 0 { 137 | limit = defaultLimit 138 | } 139 | 140 | page, err := strconv.Atoi(pageStr) 141 | if err != nil || page <= 0 { 142 | page = defaultPage 143 | } 144 | 145 | req := &pb.ListBudgetsRequest{ 146 | Limit: int32(limit), 147 | Page: int32(page), 148 | } 149 | 150 | res, err := h.Budget.ListBudgets(context.Background(), req) 151 | if err != nil { 152 | ctx.JSON(http.StatusBadRequest, err.Error()) 153 | return 154 | } 155 | 156 | ctx.JSON(http.StatusOK, res) 157 | } 158 | 159 | // @Summary Generate Budget Performance Report 160 | // @Description Generate a performance report for a specific budget. If the spent amount exceeds the budget amount, a notification will be created. 161 | // @Tags Budget 162 | // @Accept json 163 | // @Produce json 164 | // @Param id path string true "Budget ID" 165 | // @Security BearerAuth 166 | // @Success 200 {object} pb.GenerateBudgetPerformanceReportResponse 167 | // @Failure 400 {string} string "Failed to generate budget performance report" 168 | // @Failure 500 {string} string "Notification not created" 169 | // @Router /budget/{id}/performance-report [get] 170 | func (h *BudgetingHandler) GenerateBudgetPerformanceReport(ctx *gin.Context) { 171 | id := ctx.Param("id") 172 | req := &pb.GenerateBudgetPerformanceReportRequest{Id: id} 173 | 174 | res, err := h.Budget.GenerateBudgetPerformanceReport(context.Background(), req) 175 | if err != nil { 176 | ctx.JSON(400, gin.H{"error": "Failed to generate budget performance report"}) 177 | return 178 | } 179 | ctx.JSON(http.StatusOK, res) 180 | } 181 | -------------------------------------------------------------------------------- /Budgeting_service/storage/testing/account_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | pb "budgeting/genprotos" 8 | m "budgeting/models" 9 | "budgeting/storage/mongo" 10 | 11 | "github.com/stretchr/testify/assert" 12 | "go.mongodb.org/mongo-driver/bson" 13 | "go.mongodb.org/mongo-driver/bson/primitive" 14 | "go.mongodb.org/mongo-driver/mongo/integration/mtest" 15 | ) 16 | 17 | func TestCreateAccount(t *testing.T) { 18 | mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock)) 19 | 20 | mt.Run("CreateAccount_Success", func(mt *mtest.T) { 21 | mt.AddMockResponses(mtest.CreateSuccessResponse()) 22 | 23 | budgetService := mongo.NewAccount(mt.DB) 24 | 25 | req := &pb.CreateAccountRequest{ 26 | Account: &pb.Account{ 27 | Id: primitive.NewObjectID().Hex(), 28 | UserId: "user123", 29 | Name: "category123", 30 | Currency: "monthly", 31 | Balance: 100.0, 32 | Type: "income", 33 | }, 34 | } 35 | 36 | resp, err := budgetService.CreateAccount(req) 37 | 38 | assert.NoError(t, err) 39 | assert.NotNil(t, resp) 40 | assert.Equal(t, &pb.CreateAccountResponse{}, resp) 41 | }) 42 | 43 | } 44 | 45 | func TestUpdateAccount(t *testing.T) { 46 | mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock)) 47 | 48 | mt.Run("UpdateAccount_Success", func(mt *mtest.T) { 49 | mt.AddMockResponses(mtest.CreateSuccessResponse()) 50 | 51 | budgetService := mongo.NewAccount(mt.DB) 52 | 53 | req := &pb.UpdateAccountRequest{ 54 | Account: &pb.Account{ 55 | Id: primitive.NewObjectID().Hex(), 56 | UserId: "user123", 57 | Name: "category123", 58 | Currency: "monthly", 59 | Balance: 100.0, 60 | Type: "income", 61 | }, 62 | } 63 | 64 | resp, err := budgetService.UpdateAccount(req) 65 | 66 | assert.NoError(t, err) 67 | assert.NotNil(t, resp) 68 | assert.Equal(t, &pb.UpdateAccountResponse{}, resp) 69 | }) 70 | } 71 | 72 | func TestGetAccount(t *testing.T) { 73 | mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock)) 74 | 75 | mt.Run("GetAccount_Success", func(mt *mtest.T) { 76 | expectedAccount := m.Account{ 77 | ID: "example-id", 78 | UserID: "user123", 79 | Name: "category123", 80 | Type: "monthly", 81 | Balance: 100.0, 82 | Currency: "2024-01-01", 83 | } 84 | 85 | mt.AddMockResponses(mtest.CreateCursorResponse(1, "test.account", mtest.FirstBatch, bson.D{ 86 | {Key: "_id", Value: expectedAccount.ID}, 87 | {Key: "user_id", Value: expectedAccount.UserID}, 88 | {Key: "name", Value: expectedAccount.Name}, 89 | {Key: "type", Value: expectedAccount.Type}, 90 | {Key: "balance", Value: expectedAccount.Balance}, 91 | {Key: "currency", Value: expectedAccount.Currency}, 92 | })) 93 | 94 | accountService := mongo.NewAccount(mt.DB) 95 | 96 | req := &pb.GetAccountRequest{Id: expectedAccount.ID} 97 | 98 | resp, err := accountService.GetAccount(req) 99 | 100 | assert.NoError(t, err) 101 | assert.NotNil(t, resp) 102 | assert.Equal(t, &pb.GetAccountResponse{ 103 | Account: &pb.Account{ 104 | Id: expectedAccount.ID, 105 | UserId: expectedAccount.UserID, 106 | Name: expectedAccount.Name, 107 | Type: expectedAccount.Type, 108 | Balance: expectedAccount.Balance, 109 | Currency: expectedAccount.Currency, 110 | }, 111 | }, resp) 112 | }) 113 | 114 | mt.Run("GetAccount_NotFound", func(mt *mtest.T) { 115 | mt.AddMockResponses(mtest.CreateCursorResponse(0, "test.account", mtest.FirstBatch)) 116 | 117 | accountService := mongo.NewAccount(mt.DB) 118 | 119 | req := &pb.GetAccountRequest{Id: "nonexistent-id"} 120 | 121 | resp, err := accountService.GetAccount(req) 122 | 123 | assert.Error(t, err) 124 | assert.Nil(t, resp) 125 | assert.Equal(t, errors.New("Account not found"), err) 126 | }) 127 | 128 | mt.Run("GetAccount_Error", func(mt *mtest.T) { 129 | mt.AddMockResponses(mtest.CreateCommandErrorResponse(mtest.CommandError{ 130 | Code: 11000, 131 | Message: "internal server error", 132 | })) 133 | 134 | accountService := mongo.NewAccount(mt.DB) 135 | 136 | req := &pb.GetAccountRequest{Id: "example-id"} 137 | 138 | resp, err := accountService.GetAccount(req) 139 | 140 | assert.Error(t, err) 141 | assert.Nil(t, resp) 142 | }) 143 | } 144 | 145 | func TestDeleteAccount(t *testing.T) { 146 | mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock)) 147 | 148 | mt.Run("DeleteAccount_Success", func(mt *mtest.T) { 149 | mt.AddMockResponses(mtest.CreateSuccessResponse()) 150 | 151 | budgetService := mongo.NewAccount(mt.DB) 152 | 153 | req := &pb.DeleteAccountRequest{Id: "example-id"} 154 | 155 | resp, err := budgetService.DeleteAccount(req) 156 | 157 | assert.NoError(t, err) 158 | assert.NotNil(t, resp) 159 | assert.IsType(t, &pb.DeleteAccountResponse{}, resp) 160 | }) 161 | 162 | mt.Run("DeleteAccount_NotFound", func(mt *mtest.T) { 163 | mt.AddMockResponses(mtest.CreateSuccessResponse()) 164 | 165 | budgetService := mongo.NewAccount(mt.DB) 166 | 167 | req := &pb.DeleteAccountRequest{Id: "nonexistent-id"} 168 | 169 | resp, err := budgetService.DeleteAccount(req) 170 | 171 | assert.NoError(t, err) 172 | assert.NotNil(t, resp) 173 | assert.IsType(t, &pb.DeleteAccountResponse{}, resp) 174 | }) 175 | 176 | mt.Run("DeleteAccount_Error", func(mt *mtest.T) { 177 | mt.AddMockResponses(mtest.CreateCommandErrorResponse(mtest.CommandError{ 178 | Code: 11000, 179 | Message: "internal server error", 180 | })) 181 | 182 | budgetService := mongo.NewAccount(mt.DB) 183 | 184 | req := &pb.DeleteAccountRequest{Id: "example-id"} 185 | 186 | resp, err := budgetService.DeleteAccount(req) 187 | 188 | assert.Error(t, err) 189 | assert.Nil(t, resp) 190 | assert.Contains(t, err.Error(), "internal server error") 191 | }) 192 | } 193 | -------------------------------------------------------------------------------- /Budgeting_service/storage/testing/goal_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | pb "budgeting/genprotos" 8 | m "budgeting/models" 9 | "budgeting/storage/mongo" 10 | 11 | "github.com/stretchr/testify/assert" 12 | "go.mongodb.org/mongo-driver/bson" 13 | "go.mongodb.org/mongo-driver/bson/primitive" 14 | "go.mongodb.org/mongo-driver/mongo/integration/mtest" 15 | ) 16 | 17 | func TestCreateGoal(t *testing.T) { 18 | mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock)) 19 | 20 | mt.Run("CreateGoal_Success", func(mt *mtest.T) { 21 | mt.AddMockResponses(mtest.CreateSuccessResponse()) 22 | 23 | budgetService := mongo.NewGoal(mt.DB) 24 | 25 | req := &pb.CreateGoalRequest{ 26 | Goal: &pb.Goal{ 27 | Id: primitive.NewObjectID().Hex(), 28 | UserId: "user123", 29 | CurrentAmount: 10000, 30 | TargetAmount: 100000, 31 | Status: "in_progress", 32 | Deadline: "2024-01-31", 33 | }, 34 | } 35 | 36 | resp, err := budgetService.CreateGoal(req) 37 | 38 | assert.NoError(t, err) 39 | assert.NotNil(t, resp) 40 | assert.Equal(t, &pb.CreateGoalResponse{}, resp) 41 | }) 42 | 43 | } 44 | 45 | func TestUpdateGoal(t *testing.T) { 46 | mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock)) 47 | 48 | mt.Run("UpdateGoal_Success", func(mt *mtest.T) { 49 | mt.AddMockResponses(mtest.CreateSuccessResponse()) 50 | 51 | budgetService := mongo.NewGoal(mt.DB) 52 | 53 | req := &pb.UpdateGoalRequest{ 54 | Goal: &pb.Goal{ 55 | Id: primitive.NewObjectID().Hex(), 56 | UserId: "user123", 57 | CurrentAmount: 10000, 58 | TargetAmount: 100000, 59 | Status: "in_progress", 60 | Deadline: "2024-01-31", 61 | }, 62 | } 63 | 64 | resp, err := budgetService.UpdateGoal(req) 65 | 66 | assert.NoError(t, err) 67 | assert.NotNil(t, resp) 68 | assert.Equal(t, &pb.UpdateGoalResponse{}, resp) 69 | }) 70 | } 71 | 72 | func TestGetGoal(t *testing.T) { 73 | mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock)) 74 | 75 | mt.Run("GetGoal_Success", func(mt *mtest.T) { 76 | expectedAccount := m.Goal{ 77 | ID: "example-id", 78 | UserID: "user123", 79 | Name: "category123", 80 | TargetAmount: 100000, 81 | CurrentAmount: 100.0, 82 | Deadline: "2024-01-01", 83 | Status: "nma gap", 84 | } 85 | 86 | mt.AddMockResponses(mtest.CreateCursorResponse(1, "test.goal", mtest.FirstBatch, bson.D{ 87 | {Key: "_id", Value: expectedAccount.ID}, 88 | {Key: "user_id", Value: expectedAccount.UserID}, 89 | {Key: "name", Value: expectedAccount.Name}, 90 | {Key: "target_amount", Value: expectedAccount.TargetAmount}, 91 | {Key: "current_amount", Value: expectedAccount.CurrentAmount}, 92 | {Key: "deadline", Value: expectedAccount.Deadline}, 93 | {Key: "status", Value: expectedAccount.Status}, 94 | })) 95 | 96 | accountService := mongo.NewGoal(mt.DB) 97 | 98 | req := &pb.GetGoalRequest{Id: expectedAccount.ID} 99 | 100 | resp, err := accountService.GetGoal(req) 101 | 102 | assert.NoError(t, err) 103 | assert.NotNil(t, resp) 104 | assert.Equal(t, &pb.GetGoalResponse{ 105 | Goal: &pb.Goal{ 106 | Id: expectedAccount.ID, 107 | UserId: expectedAccount.UserID, 108 | Name: expectedAccount.Name, 109 | TargetAmount: expectedAccount.TargetAmount, 110 | CurrentAmount: expectedAccount.CurrentAmount, 111 | Deadline: expectedAccount.Deadline, 112 | Status: expectedAccount.Status, 113 | }, 114 | }, resp) 115 | }) 116 | 117 | mt.Run("GetGoal_NotFound", func(mt *mtest.T) { 118 | mt.AddMockResponses(mtest.CreateCursorResponse(0, "test.goal", mtest.FirstBatch)) 119 | 120 | accountService := mongo.NewGoal(mt.DB) 121 | 122 | req := &pb.GetGoalRequest{Id: "nonexistent-id"} 123 | 124 | resp, err := accountService.GetGoal(req) 125 | 126 | assert.Error(t, err) 127 | assert.Nil(t, resp) 128 | assert.Equal(t, errors.New("goal not found"), err) 129 | }) 130 | 131 | mt.Run("GetGoal_Error", func(mt *mtest.T) { 132 | mt.AddMockResponses(mtest.CreateCommandErrorResponse(mtest.CommandError{ 133 | Code: 11000, 134 | Message: "internal server error", 135 | })) 136 | 137 | accountService := mongo.NewGoal(mt.DB) 138 | 139 | req := &pb.GetGoalRequest{Id: "example-id"} 140 | 141 | resp, err := accountService.GetGoal(req) 142 | 143 | assert.Error(t, err) 144 | assert.Nil(t, resp) 145 | }) 146 | } 147 | 148 | 149 | func TestDeleteGoal(t *testing.T) { 150 | mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock)) 151 | 152 | mt.Run("DeleteGoal_Success", func(mt *mtest.T) { 153 | mt.AddMockResponses(mtest.CreateSuccessResponse()) 154 | 155 | GoalService := mongo.NewGoal(mt.DB) 156 | 157 | req := &pb.DeleteGoalRequest{Id: "example-id"} 158 | 159 | resp, err := GoalService.DeleteGoal(req) 160 | 161 | assert.NoError(t, err) 162 | assert.NotNil(t, resp) 163 | assert.IsType(t, &pb.DeleteGoalResponse{}, resp) 164 | }) 165 | 166 | mt.Run("DeleteGoal_NotFound", func(mt *mtest.T) { 167 | mt.AddMockResponses(mtest.CreateSuccessResponse()) 168 | 169 | GoalService := mongo.NewGoal(mt.DB) 170 | 171 | req := &pb.DeleteGoalRequest{Id: "nonexistent-id"} 172 | 173 | resp, err := GoalService.DeleteGoal(req) 174 | 175 | assert.NoError(t, err) 176 | assert.NotNil(t, resp) 177 | assert.IsType(t, &pb.DeleteGoalResponse{}, resp) 178 | }) 179 | 180 | mt.Run("DeleteGoal_Error", func(mt *mtest.T) { 181 | mt.AddMockResponses(mtest.CreateCommandErrorResponse(mtest.CommandError{ 182 | Code: 11000, 183 | Message: "internal server error", 184 | })) 185 | 186 | GoalService := mongo.NewGoal(mt.DB) 187 | 188 | req := &pb.DeleteGoalRequest{Id: "example-id"} 189 | 190 | resp, err := GoalService.DeleteGoal(req) 191 | 192 | assert.Error(t, err) 193 | assert.Nil(t, resp) 194 | assert.Contains(t, err.Error(), "internal server error") 195 | }) 196 | } 197 | -------------------------------------------------------------------------------- /Budgeting_service/storage/testing/budget_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | pb "budgeting/genprotos" 8 | "budgeting/storage/mongo" 9 | m "budgeting/models" 10 | 11 | "github.com/stretchr/testify/assert" 12 | "go.mongodb.org/mongo-driver/bson" 13 | "go.mongodb.org/mongo-driver/bson/primitive" 14 | "go.mongodb.org/mongo-driver/mongo/integration/mtest" 15 | ) 16 | 17 | func TestCreateBudget(t *testing.T) { 18 | mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock)) 19 | 20 | mt.Run("CreateBudget_Success", func(mt *mtest.T) { 21 | mt.AddMockResponses(mtest.CreateSuccessResponse()) 22 | 23 | budgetService := mongo.NewBudget(mt.DB) 24 | 25 | req := &pb.CreateBudgetRequest{ 26 | Budget: &pb.Budget{ 27 | Id: primitive.NewObjectID().Hex(), 28 | UserId: "user123", 29 | CategoryId: "category123", 30 | Period: "monthly", 31 | Amount: 100.0, 32 | StartDate: "2024-01-01", 33 | EndDate: "2024-01-31", 34 | }, 35 | } 36 | 37 | resp, err := budgetService.CreateBudget(req) 38 | 39 | assert.NoError(t, err) 40 | assert.NotNil(t, resp) 41 | assert.Equal(t, &pb.CreateBudgetResponse{}, resp) 42 | }) 43 | } 44 | 45 | func TestUpdateBudget(t *testing.T) { 46 | mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock)) 47 | 48 | mt.Run("UpdateBudget_Success", func(mt *mtest.T) { 49 | mt.AddMockResponses(mtest.CreateSuccessResponse()) 50 | 51 | budgetService := mongo.NewBudget(mt.DB) 52 | 53 | req := &pb.UpdateBudgetRequest{ 54 | Budget: &pb.Budget{ 55 | Id: primitive.NewObjectID().Hex(), 56 | UserId: "user123", 57 | CategoryId: "category123", 58 | Period: "monthly", 59 | Amount: 150.0, 60 | StartDate: "2024-01-01", 61 | EndDate: "2024-01-31", 62 | }, 63 | } 64 | 65 | resp, err := budgetService.UpdateBudget(req) 66 | 67 | assert.NoError(t, err) 68 | assert.NotNil(t, resp) 69 | assert.Equal(t, &pb.UpdateBudgetResponse{}, resp) 70 | }) 71 | } 72 | 73 | func TestGetBudget(t *testing.T) { 74 | mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock)) 75 | 76 | mt.Run("GetBudget_Success", func(mt *mtest.T) { 77 | expectedBudget := m.Budget{ 78 | ID: "example-id", 79 | UserID: "user123", 80 | CategoryID: "category123", 81 | Period: "monthly", 82 | Amount: 100.0, 83 | StartDate: "2024-01-01", 84 | EndDate: "2024-01-31", 85 | } 86 | 87 | mt.AddMockResponses(mtest.CreateCursorResponse(1, "test.budget", mtest.FirstBatch, bson.D{ 88 | {Key: "_id", Value: expectedBudget.ID}, 89 | {Key: "user_id", Value: expectedBudget.UserID}, 90 | {Key: "category_id", Value: expectedBudget.CategoryID}, 91 | {Key: "period", Value: expectedBudget.Period}, 92 | {Key: "amount", Value: expectedBudget.Amount}, 93 | {Key: "start_date", Value: expectedBudget.StartDate}, 94 | {Key: "end_date", Value: expectedBudget.EndDate}, 95 | })) 96 | 97 | budgetService := mongo.NewBudget(mt.DB) 98 | 99 | req := &pb.GetBudgetRequest{Id: expectedBudget.ID} 100 | 101 | resp, err := budgetService.GetBudget(req) 102 | 103 | assert.NoError(t, err) 104 | assert.NotNil(t, resp) 105 | assert.Equal(t, &pb.GetBudgetResponse{ 106 | Budget: &pb.Budget{ 107 | Id: expectedBudget.ID, 108 | UserId: expectedBudget.UserID, 109 | CategoryId: expectedBudget.CategoryID, 110 | Period: expectedBudget.Period, 111 | Amount: expectedBudget.Amount, 112 | StartDate: expectedBudget.StartDate, 113 | EndDate: expectedBudget.EndDate, 114 | }, 115 | }, resp) 116 | }) 117 | 118 | mt.Run("GetBudget_NotFound", func(mt *mtest.T) { 119 | mt.AddMockResponses(mtest.CreateCursorResponse(0, "test.budget", mtest.FirstBatch)) 120 | 121 | budgetService := mongo.NewBudget(mt.DB) 122 | 123 | req := &pb.GetBudgetRequest{Id: "nonexistent-id"} 124 | 125 | resp, err := budgetService.GetBudget(req) 126 | 127 | assert.Error(t, err) 128 | assert.Nil(t, resp) 129 | assert.Equal(t, errors.New("budget not found"), err) 130 | }) 131 | 132 | mt.Run("GetBudget_Error", func(mt *mtest.T) { 133 | mt.AddMockResponses(mtest.CreateCommandErrorResponse(mtest.CommandError{ 134 | Code: 11000, 135 | Message: "internal server error", 136 | })) 137 | 138 | budgetService := mongo.NewBudget(mt.DB) 139 | 140 | req := &pb.GetBudgetRequest{Id: "example-id"} 141 | 142 | resp, err := budgetService.GetBudget(req) 143 | 144 | assert.Error(t, err) 145 | assert.Nil(t, resp) 146 | }) 147 | } 148 | 149 | 150 | func TestDeleteBudget(t *testing.T) { 151 | mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock)) 152 | 153 | mt.Run("DeleteBudget_Success", func(mt *mtest.T) { 154 | mt.AddMockResponses(mtest.CreateSuccessResponse()) 155 | 156 | budgetService := mongo.NewBudget(mt.DB) 157 | 158 | req := &pb.DeleteBudgetRequest{Id: "example-id"} 159 | 160 | resp, err := budgetService.DeleteBudget(req) 161 | 162 | assert.NoError(t, err) 163 | assert.NotNil(t, resp) 164 | assert.IsType(t, &pb.DeleteBudgetResponse{}, resp) 165 | }) 166 | 167 | mt.Run("DeleteBudget_NotFound", func(mt *mtest.T) { 168 | mt.AddMockResponses(mtest.CreateSuccessResponse()) 169 | 170 | budgetService := mongo.NewBudget(mt.DB) 171 | 172 | req := &pb.DeleteBudgetRequest{Id: "nonexistent-id"} 173 | 174 | resp, err := budgetService.DeleteBudget(req) 175 | 176 | assert.NoError(t, err) 177 | assert.NotNil(t, resp) 178 | assert.IsType(t, &pb.DeleteBudgetResponse{}, resp) 179 | }) 180 | 181 | mt.Run("DeleteBudget_Error", func(mt *mtest.T) { 182 | mt.AddMockResponses(mtest.CreateCommandErrorResponse(mtest.CommandError{ 183 | Code: 11000, 184 | Message: "internal server error", 185 | })) 186 | 187 | budgetService := mongo.NewBudget(mt.DB) 188 | 189 | req := &pb.DeleteBudgetRequest{Id: "example-id"} 190 | 191 | resp, err := budgetService.DeleteBudget(req) 192 | 193 | assert.Error(t, err) 194 | assert.Nil(t, resp) 195 | assert.Contains(t, err.Error(), "internal server error") 196 | }) 197 | } 198 | -------------------------------------------------------------------------------- /Auth_service/README.md: -------------------------------------------------------------------------------- 1 | # Auth 2 | 3 | 4 | 5 | ## Getting started 6 | 7 | To make it easy for you to get started with GitLab, here's a list of recommended next steps. 8 | 9 | Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)! 10 | 11 | ## Add your files 12 | 13 | - [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files 14 | - [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command: 15 | 16 | ``` 17 | cd existing_repo 18 | git remote add origin https://gitlab.com/food-delivery8350430/auth.git 19 | git branch -M main 20 | git push -uf origin main 21 | ``` 22 | 23 | ## Integrate with your tools 24 | 25 | - [ ] [Set up project integrations](https://gitlab.com/food-delivery8350430/auth/-/settings/integrations) 26 | 27 | ## Collaborate with your team 28 | 29 | - [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/) 30 | - [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html) 31 | - [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically) 32 | - [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/) 33 | - [ ] [Set auto-merge](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html) 34 | 35 | ## Test and Deploy 36 | 37 | Use the built-in continuous integration in GitLab. 38 | 39 | - [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html) 40 | - [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing (SAST)](https://docs.gitlab.com/ee/user/application_security/sast/) 41 | - [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html) 42 | - [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/) 43 | - [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html) 44 | 45 | *** 46 | 47 | # Editing this README 48 | 49 | When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thanks to [makeareadme.com](https://www.makeareadme.com/) for this template. 50 | 51 | ## Suggestions for a good README 52 | 53 | Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information. 54 | 55 | ## Name 56 | Choose a self-explaining name for your project. 57 | 58 | ## Description 59 | Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors. 60 | 61 | ## Badges 62 | On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge. 63 | 64 | ## Visuals 65 | Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method. 66 | 67 | ## Installation 68 | Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection. 69 | 70 | ## Usage 71 | Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README. 72 | 73 | ## Support 74 | Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc. 75 | 76 | ## Roadmap 77 | If you have ideas for releases in the future, it is a good idea to list them in the README. 78 | 79 | ## Contributing 80 | State if you are open to contributions and what your requirements are for accepting them. 81 | 82 | For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self. 83 | 84 | You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser. 85 | 86 | ## Authors and acknowledgment 87 | Show your appreciation to those who have contributed to the project. 88 | 89 | ## License 90 | For open source projects, say how it is licensed. 91 | 92 | ## Project status 93 | If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers. 94 | -------------------------------------------------------------------------------- /Budgeting_service/genprotos/notification_grpc.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT. 2 | // versions: 3 | // - protoc-gen-go-grpc v1.2.0 4 | // - protoc v3.12.4 5 | // source: notification.proto 6 | 7 | package genprotos 8 | 9 | import ( 10 | context "context" 11 | grpc "google.golang.org/grpc" 12 | codes "google.golang.org/grpc/codes" 13 | status "google.golang.org/grpc/status" 14 | ) 15 | 16 | // This is a compile-time assertion to ensure that this generated file 17 | // is compatible with the grpc package it is being compiled against. 18 | // Requires gRPC-Go v1.32.0 or later. 19 | const _ = grpc.SupportPackageIsVersion7 20 | 21 | // NotificationServiceClient is the client API for NotificationService service. 22 | // 23 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. 24 | type NotificationServiceClient interface { 25 | CreateNotification(ctx context.Context, in *CreateNotificationRequest, opts ...grpc.CallOption) (*CreateNotificationResponse, error) 26 | GetNotification(ctx context.Context, in *GetNotificationRequest, opts ...grpc.CallOption) (*GetNotificationResponse, error) 27 | } 28 | 29 | type notificationServiceClient struct { 30 | cc grpc.ClientConnInterface 31 | } 32 | 33 | func NewNotificationServiceClient(cc grpc.ClientConnInterface) NotificationServiceClient { 34 | return ¬ificationServiceClient{cc} 35 | } 36 | 37 | func (c *notificationServiceClient) CreateNotification(ctx context.Context, in *CreateNotificationRequest, opts ...grpc.CallOption) (*CreateNotificationResponse, error) { 38 | out := new(CreateNotificationResponse) 39 | err := c.cc.Invoke(ctx, "/notification.NotificationService/CreateNotification", in, out, opts...) 40 | if err != nil { 41 | return nil, err 42 | } 43 | return out, nil 44 | } 45 | 46 | func (c *notificationServiceClient) GetNotification(ctx context.Context, in *GetNotificationRequest, opts ...grpc.CallOption) (*GetNotificationResponse, error) { 47 | out := new(GetNotificationResponse) 48 | err := c.cc.Invoke(ctx, "/notification.NotificationService/GetNotification", in, out, opts...) 49 | if err != nil { 50 | return nil, err 51 | } 52 | return out, nil 53 | } 54 | 55 | // NotificationServiceServer is the server API for NotificationService service. 56 | // All implementations must embed UnimplementedNotificationServiceServer 57 | // for forward compatibility 58 | type NotificationServiceServer interface { 59 | CreateNotification(context.Context, *CreateNotificationRequest) (*CreateNotificationResponse, error) 60 | GetNotification(context.Context, *GetNotificationRequest) (*GetNotificationResponse, error) 61 | mustEmbedUnimplementedNotificationServiceServer() 62 | } 63 | 64 | // UnimplementedNotificationServiceServer must be embedded to have forward compatible implementations. 65 | type UnimplementedNotificationServiceServer struct { 66 | } 67 | 68 | func (UnimplementedNotificationServiceServer) CreateNotification(context.Context, *CreateNotificationRequest) (*CreateNotificationResponse, error) { 69 | return nil, status.Errorf(codes.Unimplemented, "method CreateNotification not implemented") 70 | } 71 | func (UnimplementedNotificationServiceServer) GetNotification(context.Context, *GetNotificationRequest) (*GetNotificationResponse, error) { 72 | return nil, status.Errorf(codes.Unimplemented, "method GetNotification not implemented") 73 | } 74 | func (UnimplementedNotificationServiceServer) mustEmbedUnimplementedNotificationServiceServer() {} 75 | 76 | // UnsafeNotificationServiceServer may be embedded to opt out of forward compatibility for this service. 77 | // Use of this interface is not recommended, as added methods to NotificationServiceServer will 78 | // result in compilation errors. 79 | type UnsafeNotificationServiceServer interface { 80 | mustEmbedUnimplementedNotificationServiceServer() 81 | } 82 | 83 | func RegisterNotificationServiceServer(s grpc.ServiceRegistrar, srv NotificationServiceServer) { 84 | s.RegisterService(&NotificationService_ServiceDesc, srv) 85 | } 86 | 87 | func _NotificationService_CreateNotification_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 88 | in := new(CreateNotificationRequest) 89 | if err := dec(in); err != nil { 90 | return nil, err 91 | } 92 | if interceptor == nil { 93 | return srv.(NotificationServiceServer).CreateNotification(ctx, in) 94 | } 95 | info := &grpc.UnaryServerInfo{ 96 | Server: srv, 97 | FullMethod: "/notification.NotificationService/CreateNotification", 98 | } 99 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 100 | return srv.(NotificationServiceServer).CreateNotification(ctx, req.(*CreateNotificationRequest)) 101 | } 102 | return interceptor(ctx, in, info, handler) 103 | } 104 | 105 | func _NotificationService_GetNotification_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 106 | in := new(GetNotificationRequest) 107 | if err := dec(in); err != nil { 108 | return nil, err 109 | } 110 | if interceptor == nil { 111 | return srv.(NotificationServiceServer).GetNotification(ctx, in) 112 | } 113 | info := &grpc.UnaryServerInfo{ 114 | Server: srv, 115 | FullMethod: "/notification.NotificationService/GetNotification", 116 | } 117 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 118 | return srv.(NotificationServiceServer).GetNotification(ctx, req.(*GetNotificationRequest)) 119 | } 120 | return interceptor(ctx, in, info, handler) 121 | } 122 | 123 | // NotificationService_ServiceDesc is the grpc.ServiceDesc for NotificationService service. 124 | // It's only intended for direct use with grpc.RegisterService, 125 | // and not to be introspected or modified (even as a copy) 126 | var NotificationService_ServiceDesc = grpc.ServiceDesc{ 127 | ServiceName: "notification.NotificationService", 128 | HandlerType: (*NotificationServiceServer)(nil), 129 | Methods: []grpc.MethodDesc{ 130 | { 131 | MethodName: "CreateNotification", 132 | Handler: _NotificationService_CreateNotification_Handler, 133 | }, 134 | { 135 | MethodName: "GetNotification", 136 | Handler: _NotificationService_GetNotification_Handler, 137 | }, 138 | }, 139 | Streams: []grpc.StreamDesc{}, 140 | Metadata: "notification.proto", 141 | } 142 | -------------------------------------------------------------------------------- /Api_Gateway/genprotos/budgeting/notification_grpc.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT. 2 | // versions: 3 | // - protoc-gen-go-grpc v1.2.0 4 | // - protoc v3.12.4 5 | // source: notification.proto 6 | 7 | package genprotos 8 | 9 | import ( 10 | context "context" 11 | grpc "google.golang.org/grpc" 12 | codes "google.golang.org/grpc/codes" 13 | status "google.golang.org/grpc/status" 14 | ) 15 | 16 | // This is a compile-time assertion to ensure that this generated file 17 | // is compatible with the grpc package it is being compiled against. 18 | // Requires gRPC-Go v1.32.0 or later. 19 | const _ = grpc.SupportPackageIsVersion7 20 | 21 | // NotificationServiceClient is the client API for NotificationService service. 22 | // 23 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. 24 | type NotificationServiceClient interface { 25 | CreateNotification(ctx context.Context, in *CreateNotificationRequest, opts ...grpc.CallOption) (*CreateNotificationResponse, error) 26 | GetNotification(ctx context.Context, in *GetNotificationRequest, opts ...grpc.CallOption) (*GetNotificationResponse, error) 27 | } 28 | 29 | type notificationServiceClient struct { 30 | cc grpc.ClientConnInterface 31 | } 32 | 33 | func NewNotificationServiceClient(cc grpc.ClientConnInterface) NotificationServiceClient { 34 | return ¬ificationServiceClient{cc} 35 | } 36 | 37 | func (c *notificationServiceClient) CreateNotification(ctx context.Context, in *CreateNotificationRequest, opts ...grpc.CallOption) (*CreateNotificationResponse, error) { 38 | out := new(CreateNotificationResponse) 39 | err := c.cc.Invoke(ctx, "/notification.NotificationService/CreateNotification", in, out, opts...) 40 | if err != nil { 41 | return nil, err 42 | } 43 | return out, nil 44 | } 45 | 46 | func (c *notificationServiceClient) GetNotification(ctx context.Context, in *GetNotificationRequest, opts ...grpc.CallOption) (*GetNotificationResponse, error) { 47 | out := new(GetNotificationResponse) 48 | err := c.cc.Invoke(ctx, "/notification.NotificationService/GetNotification", in, out, opts...) 49 | if err != nil { 50 | return nil, err 51 | } 52 | return out, nil 53 | } 54 | 55 | // NotificationServiceServer is the server API for NotificationService service. 56 | // All implementations must embed UnimplementedNotificationServiceServer 57 | // for forward compatibility 58 | type NotificationServiceServer interface { 59 | CreateNotification(context.Context, *CreateNotificationRequest) (*CreateNotificationResponse, error) 60 | GetNotification(context.Context, *GetNotificationRequest) (*GetNotificationResponse, error) 61 | mustEmbedUnimplementedNotificationServiceServer() 62 | } 63 | 64 | // UnimplementedNotificationServiceServer must be embedded to have forward compatible implementations. 65 | type UnimplementedNotificationServiceServer struct { 66 | } 67 | 68 | func (UnimplementedNotificationServiceServer) CreateNotification(context.Context, *CreateNotificationRequest) (*CreateNotificationResponse, error) { 69 | return nil, status.Errorf(codes.Unimplemented, "method CreateNotification not implemented") 70 | } 71 | func (UnimplementedNotificationServiceServer) GetNotification(context.Context, *GetNotificationRequest) (*GetNotificationResponse, error) { 72 | return nil, status.Errorf(codes.Unimplemented, "method GetNotification not implemented") 73 | } 74 | func (UnimplementedNotificationServiceServer) mustEmbedUnimplementedNotificationServiceServer() {} 75 | 76 | // UnsafeNotificationServiceServer may be embedded to opt out of forward compatibility for this service. 77 | // Use of this interface is not recommended, as added methods to NotificationServiceServer will 78 | // result in compilation errors. 79 | type UnsafeNotificationServiceServer interface { 80 | mustEmbedUnimplementedNotificationServiceServer() 81 | } 82 | 83 | func RegisterNotificationServiceServer(s grpc.ServiceRegistrar, srv NotificationServiceServer) { 84 | s.RegisterService(&NotificationService_ServiceDesc, srv) 85 | } 86 | 87 | func _NotificationService_CreateNotification_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 88 | in := new(CreateNotificationRequest) 89 | if err := dec(in); err != nil { 90 | return nil, err 91 | } 92 | if interceptor == nil { 93 | return srv.(NotificationServiceServer).CreateNotification(ctx, in) 94 | } 95 | info := &grpc.UnaryServerInfo{ 96 | Server: srv, 97 | FullMethod: "/notification.NotificationService/CreateNotification", 98 | } 99 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 100 | return srv.(NotificationServiceServer).CreateNotification(ctx, req.(*CreateNotificationRequest)) 101 | } 102 | return interceptor(ctx, in, info, handler) 103 | } 104 | 105 | func _NotificationService_GetNotification_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 106 | in := new(GetNotificationRequest) 107 | if err := dec(in); err != nil { 108 | return nil, err 109 | } 110 | if interceptor == nil { 111 | return srv.(NotificationServiceServer).GetNotification(ctx, in) 112 | } 113 | info := &grpc.UnaryServerInfo{ 114 | Server: srv, 115 | FullMethod: "/notification.NotificationService/GetNotification", 116 | } 117 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 118 | return srv.(NotificationServiceServer).GetNotification(ctx, req.(*GetNotificationRequest)) 119 | } 120 | return interceptor(ctx, in, info, handler) 121 | } 122 | 123 | // NotificationService_ServiceDesc is the grpc.ServiceDesc for NotificationService service. 124 | // It's only intended for direct use with grpc.RegisterService, 125 | // and not to be introspected or modified (even as a copy) 126 | var NotificationService_ServiceDesc = grpc.ServiceDesc{ 127 | ServiceName: "notification.NotificationService", 128 | HandlerType: (*NotificationServiceServer)(nil), 129 | Methods: []grpc.MethodDesc{ 130 | { 131 | MethodName: "CreateNotification", 132 | Handler: _NotificationService_CreateNotification_Handler, 133 | }, 134 | { 135 | MethodName: "GetNotification", 136 | Handler: _NotificationService_GetNotification_Handler, 137 | }, 138 | }, 139 | Streams: []grpc.StreamDesc{}, 140 | Metadata: "notification.proto", 141 | } 142 | -------------------------------------------------------------------------------- /Budgeting_service/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= 4 | github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 5 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 6 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 7 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 8 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 9 | github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= 10 | github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= 11 | github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= 12 | github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= 13 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 14 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 15 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 16 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 17 | github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= 18 | github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= 19 | github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= 20 | github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= 21 | github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= 22 | github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= 23 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA= 24 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= 25 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 26 | go.mongodb.org/mongo-driver v1.16.1 h1:rIVLL3q0IHM39dvE+z2ulZLp9ENZKThVfuvN/IiN4l8= 27 | go.mongodb.org/mongo-driver v1.16.1/go.mod h1:oB6AhJQvFQL4LEHyXi6aJzQJtBiTQHiAd83l0GdFaiw= 28 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 29 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 30 | golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= 31 | golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= 32 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 33 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 34 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 35 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 36 | golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= 37 | golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= 38 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 39 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 40 | golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= 41 | golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 42 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 43 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 44 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 45 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 46 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 47 | golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= 48 | golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 49 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 50 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 51 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 52 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 53 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 54 | golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= 55 | golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= 56 | golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 57 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 58 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 59 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 60 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 61 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 h1:Zy9XzmMEflZ/MAaA7vNcoebnRAld7FsPW1EeBB7V0m8= 62 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= 63 | google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= 64 | google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= 65 | google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= 66 | google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= 67 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 68 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 69 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 70 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 71 | -------------------------------------------------------------------------------- /Budgeting_service/storage/testing/transaction_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | pb "budgeting/genprotos" 8 | m "budgeting/models" 9 | "budgeting/storage/mongo" 10 | 11 | "github.com/stretchr/testify/assert" 12 | "go.mongodb.org/mongo-driver/bson" 13 | "go.mongodb.org/mongo-driver/bson/primitive" 14 | "go.mongodb.org/mongo-driver/mongo/integration/mtest" 15 | ) 16 | 17 | func TestCreateTransaction(t *testing.T) { 18 | mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock)) 19 | 20 | mt.Run("CreateTransaction_Success", func(mt *mtest.T) { 21 | mt.AddMockResponses(mtest.CreateSuccessResponse()) 22 | 23 | budgetService := mongo.NewTransaction(mt.DB) 24 | 25 | req := &pb.CreateTransactionRequest{ 26 | Transaction: &pb.Transaction{ 27 | Id: primitive.NewObjectID().Hex(), 28 | UserId: "user123", 29 | CategoryId: "category123", 30 | AccountId: "monthly", 31 | Amount: 100.0, 32 | Type: "income", 33 | Description: "farqi yo", 34 | Date: "2024-01-31", 35 | }, 36 | } 37 | 38 | resp, err := budgetService.CreateTransaction(req) 39 | 40 | assert.NoError(t, err) 41 | assert.NotNil(t, resp) 42 | assert.Equal(t, &pb.CreateTransactionResponse{}, resp) 43 | }) 44 | 45 | } 46 | 47 | func TestUpdateTransaction(t *testing.T) { 48 | mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock)) 49 | 50 | mt.Run("UpdateTransaction_Success", func(mt *mtest.T) { 51 | mt.AddMockResponses(mtest.CreateSuccessResponse()) 52 | 53 | budgetService := mongo.NewTransaction(mt.DB) 54 | 55 | req := &pb.UpdateTransactionRequest{ 56 | Transaction: &pb.Transaction{ 57 | Id: primitive.NewObjectID().Hex(), 58 | UserId: "user123", 59 | CategoryId: "category123", 60 | AccountId: "monthly", 61 | Amount: 100.0, 62 | Type: "income", 63 | Description: "farqi yo", 64 | Date: "2024-01-31", 65 | }, 66 | } 67 | 68 | resp, err := budgetService.UpdateTransaction(req) 69 | 70 | assert.NoError(t, err) 71 | assert.NotNil(t, resp) 72 | assert.Equal(t, &pb.UpdateTransactionResponse{}, resp) 73 | }) 74 | } 75 | 76 | func TestGetTransaction(t *testing.T) { 77 | mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock)) 78 | 79 | mt.Run("GetTransaction_Success", func(mt *mtest.T) { 80 | expectedTransaction := m.Transaction{ 81 | ID: "example-id", 82 | UserID: "user123", 83 | CategoryID: "category123", 84 | AccountID: "1", 85 | Amount: 100.0, 86 | Type: "income", 87 | Date: "2024-01-31", 88 | Description: "farqi yo", 89 | } 90 | 91 | mt.AddMockResponses(mtest.CreateCursorResponse(1, "test.transaction", mtest.FirstBatch, bson.D{ 92 | {Key: "_id", Value: expectedTransaction.ID}, 93 | {Key: "user_id", Value: expectedTransaction.UserID}, 94 | {Key: "category_id", Value: expectedTransaction.CategoryID}, 95 | {Key: "account_id", Value: expectedTransaction.AccountID}, 96 | {Key: "amount", Value: expectedTransaction.Amount}, 97 | {Key: "type", Value: expectedTransaction.Type}, 98 | {Key: "date", Value: expectedTransaction.Date}, 99 | {Key: "description", Value: expectedTransaction.Description}, 100 | })) 101 | 102 | transactionService := mongo.NewTransaction(mt.DB) 103 | 104 | req := &pb.GetTransactionRequest{Id: expectedTransaction.ID} 105 | 106 | resp, err := transactionService.GetTransaction(req) 107 | 108 | assert.NoError(t, err) 109 | assert.NotNil(t, resp) 110 | assert.Equal(t, &pb.GetTransactionResponse{ 111 | Transaction: &pb.Transaction{ 112 | Id: expectedTransaction.ID, 113 | UserId: expectedTransaction.UserID, 114 | CategoryId: expectedTransaction.CategoryID, 115 | AccountId: expectedTransaction.AccountID, 116 | Amount: expectedTransaction.Amount, 117 | Type: expectedTransaction.Type, 118 | Description: expectedTransaction.Description, 119 | Date: expectedTransaction.Date, 120 | }, 121 | }, resp) 122 | }) 123 | 124 | // The rest of your test cases remain unchanged 125 | mt.Run("GetTransaction_NotFound", func(mt *mtest.T) { 126 | mt.AddMockResponses(mtest.CreateCursorResponse(0, "test.transaction", mtest.FirstBatch)) 127 | 128 | transactionService := mongo.NewTransaction(mt.DB) 129 | 130 | req := &pb.GetTransactionRequest{Id: "nonexistent-id"} 131 | 132 | resp, err := transactionService.GetTransaction(req) 133 | 134 | assert.Error(t, err) 135 | assert.Nil(t, resp) 136 | assert.Equal(t, errors.New("Transaction not found"), err) 137 | }) 138 | 139 | mt.Run("GetTransaction_Error", func(mt *mtest.T) { 140 | mt.AddMockResponses(mtest.CreateCommandErrorResponse(mtest.CommandError{ 141 | Code: 11000, 142 | Message: "internal server error", 143 | })) 144 | 145 | transactionService := mongo.NewTransaction(mt.DB) 146 | 147 | req := &pb.GetTransactionRequest{Id: "example-id"} 148 | 149 | resp, err := transactionService.GetTransaction(req) 150 | 151 | assert.Error(t, err) 152 | assert.Nil(t, resp) 153 | }) 154 | } 155 | 156 | 157 | func TestDeleteTransaction(t *testing.T) { 158 | mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock)) 159 | 160 | mt.Run("DeleteTransaction_Success", func(mt *mtest.T) { 161 | mt.AddMockResponses(mtest.CreateSuccessResponse()) 162 | 163 | TransactionService := mongo.NewTransaction(mt.DB) 164 | 165 | req := &pb.DeleteTransactionRequest{Id: "example-id"} 166 | 167 | resp, err := TransactionService.DeleteTransaction(req) 168 | 169 | assert.NoError(t, err) 170 | assert.NotNil(t, resp) 171 | assert.IsType(t, &pb.DeleteTransactionResponse{}, resp) 172 | }) 173 | 174 | mt.Run("DeleteTransaction_NotFound", func(mt *mtest.T) { 175 | mt.AddMockResponses(mtest.CreateSuccessResponse()) 176 | 177 | TransactionService := mongo.NewTransaction(mt.DB) 178 | 179 | req := &pb.DeleteTransactionRequest{Id: "nonexistent-id"} 180 | 181 | resp, err := TransactionService.DeleteTransaction(req) 182 | 183 | assert.NoError(t, err) 184 | assert.NotNil(t, resp) 185 | assert.IsType(t, &pb.DeleteTransactionResponse{}, resp) 186 | }) 187 | 188 | mt.Run("DeleteTransaction_Error", func(mt *mtest.T) { 189 | mt.AddMockResponses(mtest.CreateCommandErrorResponse(mtest.CommandError{ 190 | Code: 11000, 191 | Message: "internal server error", 192 | })) 193 | 194 | TransactionService := mongo.NewTransaction(mt.DB) 195 | 196 | req := &pb.DeleteTransactionRequest{Id: "example-id"} 197 | 198 | resp, err := TransactionService.DeleteTransaction(req) 199 | 200 | assert.Error(t, err) 201 | assert.Nil(t, resp) 202 | assert.Contains(t, err.Error(), "internal server error") 203 | }) 204 | } 205 | -------------------------------------------------------------------------------- /Auth_service/storage/postgres/user.go: -------------------------------------------------------------------------------- 1 | package postgres 2 | 3 | import ( 4 | "database/sql" 5 | "errors" 6 | "fmt" 7 | "regexp" 8 | "strings" 9 | "time" 10 | 11 | pb "auth/genprotos" 12 | 13 | "golang.org/x/crypto/bcrypt" 14 | ) 15 | 16 | type UserStorage struct { 17 | db *sql.DB 18 | } 19 | 20 | func NewUserStorage(db *sql.DB) *UserStorage { 21 | return &UserStorage{db: db} 22 | } 23 | 24 | const emailRegex = `^[a-zA-Z0-9._]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$` 25 | 26 | func isValidEmail(email string) bool { 27 | re := regexp.MustCompile(emailRegex) 28 | return re.MatchString(email) 29 | } 30 | func (u *UserStorage) RegisterUser(user *pb.RegisterUserRequest) (*pb.RegisterUserResponse, error) { 31 | if !isValidEmail(user.Email) { 32 | return nil, errors.New("invalid email format") 33 | } 34 | hashedPassword, err := bcrypt.GenerateFromPassword([]byte(user.PasswordHash), bcrypt.DefaultCost) 35 | if err != nil { 36 | return nil, err 37 | } 38 | query := `INSERT INTO users (email, password_hash, first_name, last_name,role) VALUES ($1, $2, $3, $4, $5)` 39 | _, err = u.db.Exec(query, user.Email, hashedPassword, user.FirstName, user.LastName,user.Role) 40 | if err != nil { 41 | return nil, err 42 | } 43 | return &pb.RegisterUserResponse{}, nil 44 | } 45 | 46 | func (u *UserStorage) LoginUser(user *pb.LoginUserRequest) (*pb.LoginUserResponse, error) { 47 | query := ` 48 | SELECT id, email, password_hash, first_name, last_name, role FROM users WHERE email = $1 AND deleted_at = 0 49 | ` 50 | row := u.db.QueryRow(query, user.Email) 51 | res := pb.LoginUserResponse{} 52 | err := row.Scan( 53 | &res.Id, 54 | &res.Email, 55 | &res.PasswordHash, 56 | &res.FirstName, 57 | &res.LastName, 58 | &res.Role, 59 | ) 60 | if err != nil { 61 | if err == sql.ErrNoRows { 62 | return nil, fmt.Errorf("invalid email or password") 63 | } 64 | return nil, err 65 | } 66 | 67 | err = bcrypt.CompareHashAndPassword([]byte(res.PasswordHash), []byte(user.PasswordHash)) 68 | if err != nil { 69 | return nil, fmt.Errorf("invalid email or password") 70 | } 71 | return &res, nil 72 | } 73 | 74 | func (u *UserStorage) GetByIdUser(id *pb.GetByIdUserRequest) (*pb.GetByIdUserResponse, error) { 75 | query := ` 76 | SELECT id, email, first_name, last_name FROM users 77 | WHERE id = $1 AND deleted_at = 0 78 | ` 79 | row := u.db.QueryRow(query, id.Id) 80 | 81 | user := pb.GetByIdUserResponse{} 82 | err := row.Scan( 83 | &user.Id, 84 | &user.Email, 85 | &user.FirstName, 86 | &user.LastName, 87 | ) 88 | if err != nil { 89 | if err == sql.ErrNoRows { 90 | return nil, errors.New("user not found") 91 | } 92 | return nil, err 93 | } 94 | 95 | return &user, nil 96 | } 97 | 98 | func (u *UserStorage) UpdateUser(req *pb.UpdateUserRequest) (*pb.UpdateUserResponse, error) { 99 | query := `UPDATE users SET ` 100 | var condition []string 101 | var args []interface{} 102 | 103 | if req.Email != "" && req.Email != "string" { 104 | condition = append(condition, fmt.Sprintf("email = $%d", len(args)+1)) 105 | args = append(args, req.Email) 106 | } 107 | if req.FirstName != "" && req.FirstName != "string" { 108 | condition = append(condition, fmt.Sprintf("first_name = $%d", len(args)+1)) 109 | args = append(args, req.FirstName) 110 | } 111 | 112 | if req.LastName != "" && req.LastName != "string" { 113 | condition = append(condition, fmt.Sprintf("last_name = $%d", len(args)+1)) 114 | args = append(args, req.LastName) 115 | } 116 | 117 | if len(condition) == 0 { 118 | return nil, errors.New("nothing to update") 119 | } 120 | 121 | query += strings.Join(condition, ", ") 122 | query += fmt.Sprintf(" WHERE id = $%d RETURNING id, email, first_name, last_name", len(args)+1) 123 | args = append(args, req.Id) 124 | 125 | res := pb.UpdateUserResponse{} 126 | row := u.db.QueryRow(query, args...) 127 | 128 | err := row.Scan(&res.Id, &res.Email, &res.FirstName, &res.LastName) 129 | if err != nil { 130 | return nil, err 131 | } 132 | return &res, nil 133 | } 134 | 135 | func (u *UserStorage) DeleteUser(id *pb.DeleteUserRequest) (*pb.DeleteUserResponse, error) { 136 | query := ` 137 | UPDATE users 138 | SET deleted_at = $2 139 | WHERE id = $1 AND deleted_at = 0 140 | ` 141 | _, err := u.db.Exec(query, id.Id, time.Now().Unix()) 142 | if err != nil { 143 | return nil, err 144 | } 145 | return &pb.DeleteUserResponse{}, nil 146 | } 147 | 148 | func (u *UserStorage) ChangePassword(password *pb.ChangePasswordRequest) (*pb.ChangePasswordResponse, error) { 149 | var currentHashedPassword string 150 | query := ` 151 | SELECT password_hash 152 | FROM users 153 | WHERE id = $1 AND deleted_at = 0 154 | ` 155 | err := u.db.QueryRow(query, password.Id).Scan(¤tHashedPassword) 156 | if err != nil { 157 | if err == sql.ErrNoRows { 158 | return nil, fmt.Errorf("user not found") 159 | } 160 | return nil, err 161 | } 162 | 163 | err = bcrypt.CompareHashAndPassword([]byte(currentHashedPassword), []byte(password.CurrentPassword)) 164 | if err != nil { 165 | return nil, fmt.Errorf("invalid current password") 166 | } 167 | 168 | hashedNewPassword, err := bcrypt.GenerateFromPassword([]byte(password.NewPassword), bcrypt.DefaultCost) 169 | if err != nil { 170 | return nil, fmt.Errorf("failed to hash new password") 171 | } 172 | 173 | updateQuery := ` 174 | UPDATE users 175 | SET password_hash = $2, updated_at = $3 176 | WHERE id = $1 AND deleted_at = 0 177 | ` 178 | _, err = u.db.Exec(updateQuery, password.Id, hashedNewPassword, time.Now()) 179 | if err != nil { 180 | return nil, err 181 | } 182 | 183 | return &pb.ChangePasswordResponse{}, nil 184 | } 185 | 186 | func (p *UserStorage) ForgotPassword(forgotPass *pb.ForgotPasswordRequest) (*pb.ForgotPasswordResponse, error) { 187 | return &pb.ForgotPasswordResponse{}, nil 188 | } 189 | 190 | func (s *UserStorage) GetUserByEmail(email string) (*pb.UpdateUserResponse, error) { 191 | var user pb.UpdateUserResponse 192 | query := "SELECT id, email, first_name, last_name FROM users WHERE email = $1" 193 | row := s.db.QueryRow(query, email) 194 | err := row.Scan(&user.Id, &user.Email, &user.FirstName, &user.LastName) 195 | if err != nil { 196 | if err == sql.ErrNoRows { 197 | return nil, nil 198 | } 199 | return nil, err 200 | } 201 | return &user, nil 202 | } 203 | 204 | 205 | func (p *UserStorage) ResetPassword(resetPass *pb.ResetPasswordRequest) (*pb.ResetPasswordResponse, error) { 206 | hashedPassword, err := bcrypt.GenerateFromPassword([]byte(resetPass.PasswordHash), bcrypt.DefaultCost) 207 | if err != nil { 208 | return nil, err 209 | } 210 | 211 | query := ` 212 | UPDATE users 213 | SET password_hash = $2, updated_at = $3 214 | WHERE id = $1 AND deleted_at = 0 215 | ` 216 | _, err = p.db.Exec(query, resetPass.Id, string(hashedPassword), time.Now()) 217 | if err != nil { 218 | return nil, err 219 | } 220 | 221 | return &pb.ResetPasswordResponse{}, nil 222 | } -------------------------------------------------------------------------------- /Api_Gateway/api/handler/budgeting/transaction.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "api/api/token" 5 | "api/config" 6 | pb "api/genprotos/budgeting" 7 | "context" 8 | "net/http" 9 | "strconv" 10 | 11 | "github.com/gin-gonic/gin" 12 | ) 13 | 14 | // @Summary Create Transaction 15 | // @Description Create Transaction 16 | // @Tags Transaction 17 | // @Accept json 18 | // @Produce json 19 | // @Security BearerAuth 20 | // @Param Create body pb.CreateTransactionRequest true "Create" 21 | // @Success 201 {object} string "Success" 22 | // @Failure 400 {string} string "Error" 23 | // @Router /transaction/create [post] 24 | func (h *BudgetingHandler) CreateTransaction(ctx *gin.Context) { 25 | req := &pb.CreateTransactionRequest{} 26 | if err := ctx.BindJSON(req); err != nil { 27 | ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 28 | return 29 | } 30 | c := config.Load() 31 | id, _ := token.GetIdFromToken(ctx.Request, &c) 32 | req.Transaction.UserId = id 33 | _, err := h.Transaction.CreateTransaction(context.Background(), req) 34 | if err != nil { 35 | ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Transaction not created", "details": err.Error()}) 36 | return 37 | } 38 | 39 | ctx.JSON(http.StatusCreated, gin.H{"message": "Transaction Created Successfully"}) 40 | } 41 | 42 | // @Summary Update Transaction 43 | // @Description Update Transaction 44 | // @Tags Transaction 45 | // @Accept json 46 | // @Produce json 47 | // @Security BearerAuth 48 | // @Param Update body pb.UpdateTransactionRequest true "Update" 49 | // @Success 200 {object} string "Success" 50 | // @Failure 400 {string} string "Error" 51 | // @Router /transaction/update [put] 52 | func (h *BudgetingHandler) UpdateTransaction(ctx *gin.Context) { 53 | req := &pb.UpdateTransactionRequest{} 54 | if err := ctx.BindJSON(req); err != nil { 55 | ctx.JSON(http.StatusBadRequest, err.Error()) 56 | return 57 | } 58 | c := config.Load() 59 | id, _ := token.GetIdFromToken(ctx.Request, &c) 60 | req.Transaction.UserId = id 61 | _, err := h.Transaction.UpdateTransaction(context.Background(), req) 62 | if err != nil { 63 | ctx.JSON(http.StatusBadRequest, err.Error()) 64 | return 65 | } 66 | 67 | ctx.JSON(http.StatusOK, gin.H{"message": "Transaction Updated Successfully"}) 68 | } 69 | 70 | // @Summary Delete Transaction 71 | // @Description Delete a transaction and revert the user's balance accordingly 72 | // @Tags Transaction 73 | // @Accept json 74 | // @Produce json 75 | // @Security BearerAuth 76 | // @Param id path string true "Transaction ID" 77 | // @Success 200 {object} string "Success" 78 | // @Failure 400 {string} string "Error" 79 | // @Router /transaction/delete/{id} [delete] 80 | func (h *BudgetingHandler) DeleteTransaction(ctx *gin.Context) { 81 | id := ctx.Param("id") 82 | _, err := h.Transaction.DeleteTransaction(context.Background(), &pb.DeleteTransactionRequest{Id: id}) 83 | if err != nil { 84 | ctx.JSON(http.StatusBadRequest, err.Error()) 85 | return 86 | } 87 | 88 | ctx.JSON(http.StatusOK, gin.H{"message": "Transaction Deleted Successfully"}) 89 | } 90 | 91 | // @Summary Get Transaction 92 | // @Description Get an existing Transaction record by ID 93 | // @Tags Transaction 94 | // @Accept json 95 | // @Produce json 96 | // @Security BearerAuth 97 | // @Param id path string true "Transaction ID" 98 | // @Success 200 {object} pb.GetTransactionResponse 99 | // @Failure 400 {string} string "Error" 100 | // @Router /transaction/get/{id} [get] 101 | func (h *BudgetingHandler) GetTransaction(ctx *gin.Context) { 102 | id := ctx.Param("id") 103 | req := &pb.GetTransactionRequest{Id: id} 104 | 105 | res, err := h.Transaction.GetTransaction(context.Background(), req) 106 | if err != nil { 107 | ctx.JSON(http.StatusBadRequest, err.Error()) 108 | return 109 | } 110 | 111 | ctx.JSON(http.StatusOK, res) 112 | } 113 | 114 | // @Summary ListTransactions 115 | // @Description ListTransactions 116 | // @Tags Transaction 117 | // @Accept json 118 | // @Produce json 119 | // @Security BearerAuth 120 | // @Param limit query int false "Limit" 121 | // @Param page query int false "Page" 122 | // @Success 200 {object} pb.ListTransactionsResponse 123 | // @Failure 400 {string} string "Bad Request" 124 | // @Router /transaction/get [get] 125 | func (h *BudgetingHandler) ListTransactions(ctx *gin.Context) { 126 | defaultLimit := 10 127 | defaultPage := 1 128 | 129 | limitStr := ctx.Query("limit") 130 | pageStr := ctx.Query("page") 131 | 132 | limit, err := strconv.Atoi(limitStr) 133 | if err != nil || limit <= 0 { 134 | limit = defaultLimit 135 | } 136 | 137 | page, err := strconv.Atoi(pageStr) 138 | if err != nil || page <= 0 { 139 | page = defaultPage 140 | } 141 | 142 | req := &pb.ListTransactionsRequest{ 143 | Limit: int32(limit), 144 | Page: int32(page), 145 | } 146 | 147 | res, err := h.Transaction.ListTransactions(context.Background(), req) 148 | if err != nil { 149 | ctx.JSON(http.StatusBadRequest, err.Error()) 150 | return 151 | } 152 | 153 | ctx.JSON(http.StatusOK, res) 154 | } 155 | 156 | // Spending godoc 157 | // @Summary Get spending details 158 | // @Description Get the count of spending transactions and the total amount spent. 159 | // @Tags Transaction 160 | // @Accept json 161 | // @Produce json 162 | // @Param user_id query string true "User ID" 163 | // @Security BearerAuth 164 | // @Success 200 {object} pb.SpendingResponse 165 | // @Failure 400 {string} string "Bad Request" 166 | // @Failure 404 {string} string "User Not Found" 167 | // @Router /transaction/spending [get] 168 | func (h *BudgetingHandler) Spending(ctx *gin.Context) { 169 | userId := ctx.Query("user_id") 170 | if userId == "" { 171 | ctx.JSON(http.StatusBadRequest, "User ID is required") 172 | return 173 | } 174 | 175 | req := &pb.SpendingRequest{ 176 | UserId: userId, 177 | } 178 | 179 | res, err := h.Transaction.Spending(context.Background(), req) 180 | if err != nil { 181 | ctx.JSON(http.StatusBadRequest, err.Error()) 182 | return 183 | } 184 | 185 | ctx.JSON(http.StatusOK, res) 186 | } 187 | 188 | // Income godoc 189 | // @Summary Get income details 190 | // @Description Get the count of income transactions and the total amount received. 191 | // @Tags Transaction 192 | // @Accept json 193 | // @Produce json 194 | // @Param user_id query string true "User ID" 195 | // @Success 200 {object} pb.IncomeResponse 196 | // @Security BearerAuth 197 | // @Failure 400 {string} string "Bad Request" 198 | // @Failure 404 {string} string "User Not Found" 199 | // @Router /transaction/income [get] 200 | func (h *BudgetingHandler) Income(ctx *gin.Context) { 201 | userId := ctx.Query("user_id") 202 | if userId == "" { 203 | ctx.JSON(http.StatusBadRequest, "User ID is required") 204 | return 205 | } 206 | 207 | req := &pb.IncomeRequest{ 208 | UserId: userId, 209 | } 210 | 211 | res, err := h.Transaction.Income(context.Background(), req) 212 | if err != nil { 213 | ctx.JSON(http.StatusBadRequest, err.Error()) 214 | return 215 | } 216 | 217 | ctx.JSON(http.StatusOK, res) 218 | } 219 | -------------------------------------------------------------------------------- /Budgeting_service/service/transaction.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | pb "budgeting/genprotos" 5 | "budgeting/storage" 6 | "context" 7 | "errors" 8 | "fmt" 9 | 10 | "github.com/google/uuid" 11 | ) 12 | 13 | type TransactionService struct { 14 | stg storage.StorageI 15 | pb.UnimplementedTransactionServiceServer 16 | } 17 | 18 | func NewTransactionService(stg *storage.StorageI) *TransactionService { 19 | return &TransactionService{stg: *stg} 20 | } 21 | 22 | func (s *TransactionService) CreateTransaction(c context.Context, req *pb.CreateTransactionRequest) (*pb.CreateTransactionResponse, error) { 23 | id := uuid.NewString() 24 | req.Transaction.Id = id 25 | _, err := s.stg.Transaction().CreateTransaction(req) 26 | if err != nil { 27 | return nil, err 28 | } 29 | if _, err := uuid.Parse(req.Transaction.UserId); err != nil { 30 | return nil, fmt.Errorf("invalid UserId: must be a valid UUID") 31 | } 32 | 33 | if _, err := uuid.Parse(req.Transaction.AccountId); err != nil { 34 | return nil, fmt.Errorf("invalid AccountId: must be a valid UUID") 35 | } 36 | 37 | if _, err := uuid.Parse(req.Transaction.CategoryId); err != nil { 38 | return nil, fmt.Errorf("invalid CategoryId: must be a valid UUID") 39 | } 40 | 41 | if req.Transaction.Type != "expense" && req.Transaction.Type != "income" { 42 | return nil, fmt.Errorf("invalid Transaction Type: must be either 'expense' or 'income'") 43 | } 44 | 45 | Amountres, err := s.stg.Account().GetAmount(&pb.GetAmountRequest{UserId: req.Transaction.UserId}) 46 | if err != nil { 47 | return nil, err 48 | } 49 | 50 | if Amountres.Balance < req.Transaction.Amount && req.Transaction.Type == "expense" { 51 | return nil, errors.New("your balance is not more than the requested amount") 52 | } 53 | amount := Amountres.Balance 54 | if req.Transaction.Type == "expense" { 55 | amount -= req.Transaction.Amount 56 | } else if req.Transaction.Type == "income" { 57 | amount += req.Transaction.Amount 58 | } 59 | 60 | _, err = s.stg.Account().UpdateAmount(&pb.UpdateAmountRequest{UserId: req.Transaction.UserId, Balance: amount}) 61 | if err != nil { 62 | return nil, errors.New("failed to update amount") 63 | } 64 | message := fmt.Sprintf("%f sum has been %s your account", req.Transaction.Amount, req.Transaction.Type) 65 | 66 | notification := &pb.CreateNotificationRequest{} 67 | notification.Notification = &pb.Notification{ 68 | Id: uuid.NewString(), 69 | UserId: req.Transaction.UserId, 70 | Message: message, 71 | } 72 | 73 | _, err = s.stg.Notification().CreateNotification(&pb.CreateNotificationRequest{Notification: notification.Notification}) 74 | if err != nil { 75 | return nil, errors.New("notification not created") 76 | } 77 | 78 | return &pb.CreateTransactionResponse{}, nil 79 | } 80 | 81 | func (s *TransactionService) UpdateTransaction(c context.Context, req *pb.UpdateTransactionRequest) (*pb.UpdateTransactionResponse, error) { 82 | _, err := s.stg.Transaction().UpdateTransaction(req) 83 | if err != nil { 84 | return nil, err 85 | } 86 | 87 | if _, err := uuid.Parse(req.Transaction.UserId); err != nil { 88 | return nil, fmt.Errorf("invalid UserId: must be a valid UUID") 89 | } 90 | 91 | if _, err := uuid.Parse(req.Transaction.AccountId); err != nil { 92 | return nil, fmt.Errorf("invalid AccountId: must be a valid UUID") 93 | } 94 | 95 | if _, err := uuid.Parse(req.Transaction.CategoryId); err != nil { 96 | return nil, fmt.Errorf("invalid CategoryId: must be a valid UUID") 97 | } 98 | 99 | if req.Transaction.Type != "expense" && req.Transaction.Type != "income" { 100 | return nil, fmt.Errorf("invalid Transaction Type: must be either 'expense' or 'income'") 101 | } 102 | 103 | Amountres, err := s.stg.Account().GetAmount(&pb.GetAmountRequest{UserId: req.Transaction.UserId}) 104 | if err != nil { 105 | return nil, errors.New("user not found") 106 | } 107 | 108 | Transactionres, err := s.stg.Transaction().GetTransaction(&pb.GetTransactionRequest{Id: req.Transaction.Id}) 109 | if err != nil { 110 | return nil, errors.New("transaction not found") 111 | } 112 | 113 | amount := Amountres.Balance 114 | if Transactionres.Transaction.Type == "expense" { 115 | amount += Transactionres.Transaction.Amount 116 | } else if Transactionres.Transaction.Type == "income" { 117 | amount -= Transactionres.Transaction.Amount 118 | } 119 | 120 | if req.Transaction.Type == "expense" { 121 | amount -= req.Transaction.Amount 122 | } else if req.Transaction.Type == "income" { 123 | amount += req.Transaction.Amount 124 | } 125 | 126 | _, err = s.stg.Account().UpdateAmount(&pb.UpdateAmountRequest{UserId: req.Transaction.UserId, Balance: amount}) 127 | if err != nil { 128 | return nil, errors.New("failed to update amount") 129 | } 130 | 131 | _, err = s.stg.Transaction().UpdateTransaction(&pb.UpdateTransactionRequest{Transaction: req.Transaction}) 132 | if err != nil { 133 | return nil, errors.New("failed to update transaction") 134 | } 135 | message := fmt.Sprintf("%f sum has been updated in your account", req.Transaction.Amount) 136 | notification := &pb.CreateNotificationRequest{} 137 | notification.Notification = &pb.Notification{ 138 | Id: uuid.NewString(), 139 | UserId: req.Transaction.UserId, 140 | Message: message, 141 | } 142 | 143 | _, err = s.stg.Notification().CreateNotification(&pb.CreateNotificationRequest{Notification: notification.Notification}) 144 | if err != nil { 145 | return nil, errors.New("notification not created") 146 | } 147 | 148 | return &pb.UpdateTransactionResponse{}, nil 149 | } 150 | 151 | func (s *TransactionService) DeleteTransaction(c context.Context, req *pb.DeleteTransactionRequest) (*pb.DeleteTransactionResponse, error) { 152 | 153 | gettr, err := s.stg.Transaction().GetTransaction(&pb.GetTransactionRequest{Id: req.Id}) 154 | if err != nil { 155 | return nil, errors.New("transaction not found") 156 | } 157 | Amountres, err := s.stg.Account().GetAmount(&pb.GetAmountRequest{UserId: gettr.Transaction.UserId}) 158 | if err != nil { 159 | return nil, errors.New("user not found") 160 | } 161 | 162 | amount := Amountres.Balance 163 | if gettr.Transaction.Type == "expense" { 164 | amount += gettr.Transaction.Amount 165 | } else if gettr.Transaction.Type == "income" { 166 | amount -= gettr.Transaction.Amount 167 | } 168 | 169 | _, err = s.stg.Account().UpdateAmount(&pb.UpdateAmountRequest{UserId: gettr.Transaction.UserId, Balance: amount}) 170 | if err != nil { 171 | return nil, errors.New("failed to update amount") 172 | } 173 | 174 | message := fmt.Sprintf("%f sum has been reverted from your account due to transaction deletion", gettr.Transaction.Amount) 175 | notification := &pb.CreateNotificationRequest{} 176 | notification.Notification = &pb.Notification{ 177 | Id: uuid.NewString(), 178 | UserId: gettr.Transaction.UserId, 179 | Message: message, 180 | } 181 | 182 | _, err = s.stg.Notification().CreateNotification(&pb.CreateNotificationRequest{Notification: notification.Notification}) 183 | if err != nil { 184 | return nil, errors.New("notification not created") 185 | } 186 | 187 | _, err = s.stg.Transaction().DeleteTransaction(req) 188 | if err != nil { 189 | return nil, err 190 | } 191 | 192 | return &pb.DeleteTransactionResponse{}, nil 193 | } 194 | 195 | func (s *TransactionService) GetTransaction(c context.Context, req *pb.GetTransactionRequest) (*pb.GetTransactionResponse, error) { 196 | res, err := s.stg.Transaction().GetTransaction(req) 197 | if err != nil { 198 | return nil, err 199 | } 200 | return res, nil 201 | } 202 | 203 | func (s *TransactionService) ListTransactions(c context.Context, req *pb.ListTransactionsRequest) (*pb.ListTransactionsResponse, error) { 204 | res, err := s.stg.Transaction().ListTransactions(req) 205 | if err != nil { 206 | return nil, err 207 | } 208 | return res, nil 209 | } 210 | 211 | func (s *TransactionService) Spending(c context.Context, req *pb.SpendingRequest) (*pb.SpendingResponse, error) { 212 | res, err := s.stg.Transaction().Spending(req) 213 | if err != nil { 214 | return nil, err 215 | } 216 | return res, nil 217 | } 218 | 219 | func (s *TransactionService) Income(c context.Context, req *pb.IncomeRequest) (*pb.IncomeResponse, error) { 220 | res, err := s.stg.Transaction().Income(req) 221 | if err != nil { 222 | return nil, err 223 | } 224 | return res, nil 225 | } 226 | --------------------------------------------------------------------------------