├── 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 |
--------------------------------------------------------------------------------