From d203b54253ee78dcb0b8e6a3d2055c40d71c0953 Mon Sep 17 00:00:00 2001 From: Hieu Vu Date: Fri, 12 May 2023 18:26:57 +0700 Subject: [PATCH 1/3] feat: add migration for icy txn and reports views --- .../20230512091353-drop-old-icy-txn.sql | 72 +++++++++ .../schemas/20230512091526-new-icy-txn.sql | 27 ++++ .../schemas/20230512110852-report-views.sql | 143 ++++++++++++++++++ 3 files changed, 242 insertions(+) create mode 100644 migrations/schemas/20230512091353-drop-old-icy-txn.sql create mode 100644 migrations/schemas/20230512091526-new-icy-txn.sql create mode 100644 migrations/schemas/20230512110852-report-views.sql diff --git a/migrations/schemas/20230512091353-drop-old-icy-txn.sql b/migrations/schemas/20230512091353-drop-old-icy-txn.sql new file mode 100644 index 000000000..d22451300 --- /dev/null +++ b/migrations/schemas/20230512091353-drop-old-icy-txn.sql @@ -0,0 +1,72 @@ +-- +migrate Up + +DROP VIEW "vw_icy_treasury_funds"; + +DROP TABLE "icy_transactions_out"; + +DROP TABLE "icy_transactions_in"; + +DROP TABLE "icy_treasury_categories"; + +-- +migrate Down + +CREATE TABLE "icy_treasury_categories" ( + "id" uuid NOT NULL DEFAULT uuid(), + "created_at" timestamp(8) DEFAULT NOW(), + "deleted_at" timestamp(8), + "name" text, + "category_manager_id" uuid, + CONSTRAINT "icy_treasury_categories_category_manager_id_fkey" FOREIGN KEY ("category_manager_id") REFERENCES "employees" ("id"), + PRIMARY KEY ("id") +); + +CREATE TABLE "icy_transactions_out" ( + "id" uuid NOT NULL DEFAULT uuid(), + "created_at" timestamp(8) DEFAULT NOW(), + "deleted_at" timestamp(8), + "amount" text, + "description" text, + "category_id" uuid, + "to_employee_id" uuid NOT NULL, + "approver_id" uuid, + CONSTRAINT "icy_transactions_category_id_fkey" FOREIGN KEY ("category_id") REFERENCES "icy_treasury_categories" ("id"), + CONSTRAINT "icy_transactions_to_employee_id_fkey" FOREIGN KEY ("to_employee_id") REFERENCES "employees" ("id"), + CONSTRAINT "icy_transactions_approver_id_fkey" FOREIGN KEY ("approver_id") REFERENCES "employees" ("id"), + PRIMARY KEY ("id") +); + +CREATE TABLE "icy_transactions_in" ( + "id" uuid NOT NULL DEFAULT uuid(), + "created_at" timestamp(8) DEFAULT NOW(), + "deleted_at" timestamp(8), + "date" timestamp(8) DEFAULT NOW(), + "description" text, + "amount" text, + "category_id" uuid, + CONSTRAINT "icy_transactions_category_id_fkey" FOREIGN KEY ("category_id") REFERENCES "icy_treasury_categories" ("id"), + PRIMARY KEY ("id") +); + +CREATE VIEW "vw_icy_treasury_funds" AS +SELECT + t1.category_id, + t2.total_in - t1.total_out AS balance +FROM + ( + SELECT + category_id, + SUM(amount :: NUMERIC) AS total_out + FROM + icy_transactions_out ito + GROUP BY + category_id + ) t1 + JOIN ( + SELECT + category_id, + SUM(amount :: NUMERIC) AS total_in + FROM + icy_transactions_in iti + GROUP BY + category_id + ) t2 ON t1.category_id = t2.category_id; diff --git a/migrations/schemas/20230512091526-new-icy-txn.sql b/migrations/schemas/20230512091526-new-icy-txn.sql new file mode 100644 index 000000000..d634501df --- /dev/null +++ b/migrations/schemas/20230512091526-new-icy-txn.sql @@ -0,0 +1,27 @@ +-- +migrate Up + +CREATE TYPE "enum_icy_txn_category" AS ENUM ('learning', 'community', 'delivery', 'tooling'); + +CREATE TABLE IF NOT EXISTS "icy_transactions" ( + "id" uuid PRIMARY KEY DEFAULT uuid(), + "created_at" timestamptz NOT NULL DEFAULT now(), + "updated_at" timestamptz NOT NULL DEFAULT now(), + "deleted_at" timestamptz, + "txn_time" timestamptz NOT NULL DEFAULT now(), + "src_employee_id" uuid REFERENCES employees ("id"), + "dest_employee_id" uuid REFERENCES employees ("id"), + "category" enum_icy_txn_category NOT NULL, + "amount" numeric NOT NULL DEFAULT 0, + "note" text +); + +ALTER TABLE + audiences +ADD + column unsub_at TIMESTAMP default null; + +-- +migrate Down + +DROP TABLE icy_transactions; + +DROP TYPE IF EXISTS "enum_team"; diff --git a/migrations/schemas/20230512110852-report-views.sql b/migrations/schemas/20230512110852-report-views.sql new file mode 100644 index 000000000..0ca8cb100 --- /dev/null +++ b/migrations/schemas/20230512110852-report-views.sql @@ -0,0 +1,143 @@ +-- +migrate Up + +CREATE VIEW "vw_idle_employees" AS +SELECT + DISTINCT emp.* +FROM + employees emp + JOIN project_members pj ON pj.employee_id = emp.id + AND emp.working_status <> 'left' +WHERE + deployment_type <> 'shadow' + OR rate = 0; + +CREATE VIEW "vw_subscribers_last_7days" AS +SELECT + * +FROM + audiences +WHERE now() :: date - created_at :: date <= 7; + +CREATE OR REPLACE VIEW "vw_icy_earning_by_team_monthly" AS +WITH weekly_earning AS ( + SELECT + date_trunc('month', txn_time) AS "month", + date_trunc('week', txn_time) AS "week", + category, + SUM(amount) AS "amount" + FROM + icy_transactions + GROUP BY + date_trunc('month', txn_time), + date_trunc('week', txn_time), + category + ORDER BY + date_trunc('week', txn_time) DESC, + SUM(amount) DESC +) +SELECT + to_char(date_trunc('month', txn_time), 'yyyy-mm') as "period", + t.category as "team", + SUM(t.amount) as "amount", + AVG(w.amount) AS "avg_earning_weekly" +FROM + icy_transactions t + LEFT JOIN weekly_earning w ON w.category = t.category + AND w.month = date_trunc('month', txn_time) +GROUP BY + date_trunc('month', txn_time), + t.category +ORDER BY + date_trunc('month', txn_time) DESC, + SUM(t.amount) DESC; + +CREATE OR REPLACE VIEW "vw_icy_earning_by_team_all_time" AS WITH monthly_earning AS ( + SELECT + date_trunc('month', txn_time) AS "month", + category, + SUM(amount) AS "amount" + FROM + icy_transactions + GROUP BY + date_trunc('month', txn_time), + category + ORDER BY + date_trunc('month', txn_time) DESC, + SUM(amount) DESC +), +weekly_earning AS ( + SELECT + date_trunc('month', txn_time) AS "month", + date_trunc('week', txn_time) AS "week", + category, + SUM(amount) AS "amount" + FROM + icy_transactions + GROUP BY + date_trunc('month', txn_time), + date_trunc('week', txn_time), + category + ORDER BY + date_trunc('week', txn_time) DESC, + SUM(amount) DESC +) +SELECT + m.category AS "team", + SUM(t.amount) AS "amount", + AVG(m.amount) AS "avg_earning_monthy", + AVG(w.amount) AS "avg_earning_weekly" +FROM + icy_transactions t + LEFT JOIN monthly_earning m ON m.category = t.category + LEFT JOIN weekly_earning w ON w.category = t.category + AND w.month = m.month +GROUP BY + m.category +ORDER BY + SUM(t.amount) DESC; + +CREATE OR REPLACE VIEW "vw_icy_earning_by_team_weekly" AS +SELECT + to_char(date_trunc('week', txn_time), 'yyyy-mm-dd') AS "period", + category AS "team", + SUM(t.amount) AS "amount" +FROM + icy_transactions t +GROUP BY + date_trunc('week', txn_time), + category +ORDER BY + date_trunc('week', txn_time) DESC, + SUM(t.amount) DESC; + +CREATE OR REPLACE VIEW "vw_icy_employee_dashboard" AS +SELECT + t.dest_employee_id as employee_id, + e.full_name, + e.team_email, + e.personal_email, + SUM(t.amount) AS "total_earned" +FROM + icy_transactions t + LEFT JOIN employees e ON e.id = t.dest_employee_id +GROUP BY + t.dest_employee_id, + e.full_name, + e.team_email, + e.personal_email +ORDER BY + SUM(t.amount) DESC; + +-- +migrate Down + +DROP VIEW IF EXISTS "vw_subscribers_last_7days"; + +DROP VIEW IF EXISTS "vw_icy_earning_by_team_weekly"; + +DROP VIEW IF EXISTS "vw_icy_earning_by_team_monthly"; + +DROP VIEW IF EXISTS "vw_icy_earning_by_team_all_time"; + +DROP VIEW IF EXISTS "vw_icy_employee_dashboard"; + +DROP VIEW IF EXISTS "vw_idle_employees"; From ac483172e391a071b3fdfc18faa8d3c95374d68d Mon Sep 17 00:00:00 2001 From: trkhoi Date: Mon, 15 May 2023 09:33:22 +0700 Subject: [PATCH 2/3] feat: cronjob sync icy tx fix: test feat: cronjob sync icy tx fix: cmt fix: cmt fix: cmt --- docs/docs.go | 41 +++++++++ docs/swagger.json | 41 +++++++++ docs/swagger.yaml | 27 ++++++ ...13041758-create_icy_transactions_table.sql | 15 +++ pkg/config/config.go | 8 ++ pkg/handler/handler.go | 3 + pkg/handler/vault/interface.go | 7 ++ pkg/handler/vault/vault.go | 91 +++++++++++++++++++ pkg/model/icy_transaction.go | 11 +++ pkg/model/mochi.go | 28 ++++++ pkg/routes/v1.go | 1 + pkg/routes/v1_test.go | 6 ++ pkg/service/mochi/mochi.go | 54 +++++++++++ pkg/service/service.go | 3 + pkg/store/icytransaction/icy_transaction.go | 17 ++++ pkg/store/icytransaction/interface.go | 11 +++ pkg/store/store.go | 3 + pkg/utils/timeutil/util.go | 46 ++++++++++ 18 files changed, 413 insertions(+) create mode 100644 migrations/schemas/20230513041758-create_icy_transactions_table.sql create mode 100644 pkg/handler/vault/interface.go create mode 100644 pkg/handler/vault/vault.go create mode 100644 pkg/model/icy_transaction.go create mode 100644 pkg/model/mochi.go create mode 100644 pkg/service/mochi/mochi.go create mode 100644 pkg/store/icytransaction/icy_transaction.go create mode 100644 pkg/store/icytransaction/interface.go diff --git a/docs/docs.go b/docs/docs.go index b2c0cc6ca..433c2e0ed 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -521,6 +521,47 @@ const docTemplate = `{ } } }, + "/cron-jobs/store-vault-transaction": { + "post": { + "description": "Store vault tx as icy tx from Mochi service", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Vault" + ], + "summary": "Store vault tx as icy tx from Mochi service", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/view.MessageResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/view.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/view.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/view.ErrorResponse" + } + } + } + } + }, "/cron-jobs/sync-project-member-status": { "put": { "description": "Sync project member status", diff --git a/docs/swagger.json b/docs/swagger.json index 5081c8890..e4782b405 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -513,6 +513,47 @@ } } }, + "/cron-jobs/store-vault-transaction": { + "post": { + "description": "Store vault tx as icy tx from Mochi service", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Vault" + ], + "summary": "Store vault tx as icy tx from Mochi service", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/view.MessageResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/view.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/view.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/view.ErrorResponse" + } + } + } + } + }, "/cron-jobs/sync-project-member-status": { "put": { "description": "Sync project member status", diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 9f5d5540d..eda9fd9f9 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -4526,6 +4526,33 @@ paths: summary: Update client by id tags: - Client + /cron-jobs/store-vault-transaction: + post: + consumes: + - application/json + description: Store vault tx as icy tx from Mochi service + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/view.MessageResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/view.ErrorResponse' + "404": + description: Not Found + schema: + $ref: '#/definitions/view.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/view.ErrorResponse' + summary: Store vault tx as icy tx from Mochi service + tags: + - Vault /cron-jobs/sync-project-member-status: put: consumes: diff --git a/migrations/schemas/20230513041758-create_icy_transactions_table.sql b/migrations/schemas/20230513041758-create_icy_transactions_table.sql new file mode 100644 index 000000000..ff802cd62 --- /dev/null +++ b/migrations/schemas/20230513041758-create_icy_transactions_table.sql @@ -0,0 +1,15 @@ +-- +migrate Up +CREATE TABLE IF NOT EXISTS icy_transactions ( + id uuid PRIMARY KEY DEFAULT (uuid()), + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + deleted_at TIMESTAMPTZ, + vault TEXT NOT NULL, + amount TEXT NOT NULL, + token TEXT NOT NULL, + sender_discord_id TEXT NOT NULL, + recipient_address TEXT NOT NULL, + recipient_discord_id TEXT NOT NULL +); +-- +migrate Down +DROP TABLE IF EXISTS icy_transactions; \ No newline at end of file diff --git a/pkg/config/config.go b/pkg/config/config.go index 1f2353890..ede3541ea 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -24,6 +24,7 @@ type Config struct { Discord Discord Basecamp Basecamp CurrencyLayer CurrencyLayer + Mochi Mochi Invoice Invoice Sendgrid Sendgrid @@ -77,6 +78,10 @@ type Vault struct { Path string } +type Mochi struct { + BaseURL string +} + type Notion struct { Secret string Databases NotionDatabase @@ -227,6 +232,9 @@ func Generate(v ENV) *Config { Sendgrid: Sendgrid{ APIKey: v.GetString("SENDGRID_API_KEY"), }, + Mochi: Mochi{ + BaseURL: v.GetString("MOCHI_BASE_URL"), + }, } } diff --git a/pkg/handler/handler.go b/pkg/handler/handler.go index dcd490006..bb5ce2967 100644 --- a/pkg/handler/handler.go +++ b/pkg/handler/handler.go @@ -24,6 +24,7 @@ import ( "github.com/dwarvesf/fortress-api/pkg/handler/project" "github.com/dwarvesf/fortress-api/pkg/handler/survey" "github.com/dwarvesf/fortress-api/pkg/handler/valuation" + "github.com/dwarvesf/fortress-api/pkg/handler/vault" "github.com/dwarvesf/fortress-api/pkg/handler/webhook" "github.com/dwarvesf/fortress-api/pkg/logger" "github.com/dwarvesf/fortress-api/pkg/service" @@ -53,6 +54,7 @@ type Handler struct { Survey survey.IHandler Valuation valuation.IHandler Webhook webhook.IHandler + Vault vault.IHandler } func New(store *store.Store, repo store.DBRepo, service *service.Service, ctrl *controller.Controller, worker *worker.Worker, logger logger.Logger, cfg *config.Config) *Handler { @@ -78,5 +80,6 @@ func New(store *store.Store, repo store.DBRepo, service *service.Service, ctrl * Survey: survey.New(store, repo, service, logger, cfg), Valuation: valuation.New(store, repo, service, logger, cfg), Webhook: webhook.New(ctrl, store, repo, service, logger, cfg), + Vault: vault.New(store, repo, service, logger, cfg), } } diff --git a/pkg/handler/vault/interface.go b/pkg/handler/vault/interface.go new file mode 100644 index 000000000..a53efde73 --- /dev/null +++ b/pkg/handler/vault/interface.go @@ -0,0 +1,7 @@ +package vault + +import "github.com/gin-gonic/gin" + +type IHandler interface { + StoreVaultTransaction(c *gin.Context) +} diff --git a/pkg/handler/vault/vault.go b/pkg/handler/vault/vault.go new file mode 100644 index 000000000..fa52d018f --- /dev/null +++ b/pkg/handler/vault/vault.go @@ -0,0 +1,91 @@ +package vault + +import ( + "net/http" + "time" + + "github.com/gin-gonic/gin" + + "github.com/dwarvesf/fortress-api/pkg/config" + "github.com/dwarvesf/fortress-api/pkg/logger" + "github.com/dwarvesf/fortress-api/pkg/model" + "github.com/dwarvesf/fortress-api/pkg/service" + "github.com/dwarvesf/fortress-api/pkg/store" + "github.com/dwarvesf/fortress-api/pkg/utils/timeutil" + "github.com/dwarvesf/fortress-api/pkg/view" +) + +type handler struct { + store *store.Store + service *service.Service + logger logger.Logger + repo store.DBRepo + config *config.Config +} + +// New returns a handler +func New(store *store.Store, repo store.DBRepo, service *service.Service, logger logger.Logger, cfg *config.Config) IHandler { + return &handler{ + store: store, + repo: repo, + service: service, + logger: logger, + config: cfg, + } +} + +// StoreVaultTransaction godoc +// @Summary Store vault tx as icy tx from Mochi service +// @Description Store vault tx as icy tx from Mochi service +// @Tags Vault +// @Accept json +// @Produce json +// @Success 200 {object} view.MessageResponse +// @Failure 400 {object} view.ErrorResponse +// @Failure 404 {object} view.ErrorResponse +// @Failure 500 {object} view.ErrorResponse +// @Router /cron-jobs/store-vault-transaction [post] +func (h *handler) StoreVaultTransaction(c *gin.Context) { + l := h.logger.Fields(logger.Fields{ + "handler": "vault", + "method": "StoreVaultTransaction", + }) + + // currently support + supportedVaults := []string{"18", "19", "20"} + + startOfTheWeek := timeutil.FormatDateForCurl(timeutil.GetStartDayOfWeek(time.Now().Local()).Format(time.RFC3339)) + endOfTheWeek := timeutil.FormatDateForCurl(timeutil.GetEndDayOfWeek(time.Now().Local()).Format(time.RFC3339)) + + for _, vaultId := range supportedVaults { + req := &model.VaultTransactionRequest{ + VaultId: vaultId, + StartTime: startOfTheWeek, + EndTime: endOfTheWeek, + } + res, err := h.service.Mochi.GetVaultTransaction(req) + if err != nil { + l.Error(err, "GetVaultTransaction failed") + c.JSON(http.StatusInternalServerError, view.CreateResponse[any](nil, nil, err, nil, "")) + return + } + + for _, transaction := range res.Data { + err := h.store.IcyTransaction.Create(h.repo.DB(), &model.IcyTransaction{ + Vault: transaction.VaultName, + Amount: transaction.Amount, + Token: transaction.Token, + SenderDiscordId: transaction.Sender, + RecipientAddress: transaction.ToAddress, + RecipientDiscordId: transaction.Target, + }) + if err != nil { + l.Error(err, "Create IcyTransaction failed") + c.JSON(http.StatusInternalServerError, view.CreateResponse[any](nil, nil, err, nil, "")) + return + } + } + } + + c.JSON(http.StatusOK, view.CreateResponse[any](nil, nil, nil, nil, "ok")) +} diff --git a/pkg/model/icy_transaction.go b/pkg/model/icy_transaction.go new file mode 100644 index 000000000..5444d76e7 --- /dev/null +++ b/pkg/model/icy_transaction.go @@ -0,0 +1,11 @@ +package model + +type IcyTransaction struct { + BaseModel + Vault string `json:"vault"` + Amount string `json:"amount"` + Token string `json:"token"` + SenderDiscordId string `json:"sender_discord_id"` + RecipientAddress string `json:"recipient_address"` + RecipientDiscordId string `json:"recipient_discord_id"` +} diff --git a/pkg/model/mochi.go b/pkg/model/mochi.go new file mode 100644 index 000000000..40be1e472 --- /dev/null +++ b/pkg/model/mochi.go @@ -0,0 +1,28 @@ +package model + +type VaultTransactionRequest struct { + VaultId string `json:"vault_id"` + StartTime string `json:"start_time"` + EndTime string `json:"end_time"` +} + +type VaultTransactionResponse struct { + Data []VaultTransaction `json:"data"` +} + +type VaultTransaction struct { + Id int64 `json:"id"` + GuildId string `json:"guild_id"` + VaultId int64 `json:"vault_id"` + VaultName string `json:"vault_name"` + Action string `json:"action"` + FromAddress string `json:"from_address"` + ToAddress string `json:"to_address"` + Target string `json:"target"` + Sender string `json:"sender"` + Amount string `json:"amount"` + Token string `json:"token"` + Threshold string `json:"threshold"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` +} diff --git a/pkg/routes/v1.go b/pkg/routes/v1.go index 726789118..976534042 100644 --- a/pkg/routes/v1.go +++ b/pkg/routes/v1.go @@ -25,6 +25,7 @@ func loadV1Routes(r *gin.Engine, h *handler.Handler, repo store.DBRepo, s *store cronjob.POST("/sync-discord-info", amw.WithAuth, pmw.WithPerm(model.PermissionCronjobExecute), h.Discord.SyncDiscordInfo) cronjob.POST("/sync-monthly-accounting-todo", amw.WithAuth, pmw.WithPerm(model.PermissionCronjobExecute), h.Accounting.CreateAccountingTodo) cronjob.PUT("/sync-project-member-status", amw.WithAuth, pmw.WithPerm(model.PermissionCronjobExecute), h.Project.SyncProjectMemberStatus) + cronjob.POST("/store-vault-transaction", amw.WithAuth, pmw.WithPerm(model.PermissionCronjobExecute), h.Vault.StoreVaultTransaction) } ///////////////// diff --git a/pkg/routes/v1_test.go b/pkg/routes/v1_test.go index 46f7dd03f..8149caec1 100644 --- a/pkg/routes/v1_test.go +++ b/pkg/routes/v1_test.go @@ -603,6 +603,12 @@ func Test_loadV1Routes(t *testing.T) { Handler: "github.com/dwarvesf/fortress-api/pkg/handler/project.IHandler.SyncProjectMemberStatus-fm", }, }, + "/cronjobs/store-vault-transaction": { + "POST": { + Method: "POST", + Handler: "github.com/dwarvesf/fortress-api/pkg/handler/vault.IHandler.StoreVaultTransaction-fm", + }, + }, "/webhooks/n8n": { "POST": { Method: "POST", diff --git a/pkg/service/mochi/mochi.go b/pkg/service/mochi/mochi.go new file mode 100644 index 000000000..8718b2339 --- /dev/null +++ b/pkg/service/mochi/mochi.go @@ -0,0 +1,54 @@ +package mochi + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + + "github.com/dwarvesf/fortress-api/pkg/config" + "github.com/dwarvesf/fortress-api/pkg/logger" + "github.com/dwarvesf/fortress-api/pkg/model" +) + +type IService interface { + GetVaultTransaction(req *model.VaultTransactionRequest) (*model.VaultTransactionResponse, error) +} + +type mochiClient struct { + cfg *config.Config + l logger.Logger +} + +func New(cfg *config.Config, l logger.Logger) IService { + return &mochiClient{ + cfg: cfg, + l: l, + } +} + +func (m *mochiClient) GetVaultTransaction(req *model.VaultTransactionRequest) (*model.VaultTransactionResponse, error) { + var client = &http.Client{} + request, err := http.NewRequest("GET", fmt.Sprintf("%s/api/v1/vault/%s/transaction?start_time=%s&end_time=%s", m.cfg.Mochi.BaseURL, req.VaultId, req.StartTime, req.EndTime), nil) + if err != nil { + return nil, err + } + + response, err := client.Do(request) + if err != nil { + return nil, err + } + + defer response.Body.Close() + resBody, err := ioutil.ReadAll(response.Body) + if err != nil { + return nil, err + } + + res := &model.VaultTransactionResponse{} + err = json.Unmarshal(resBody, res) + if err != nil { + return nil, err + } + return res, nil +} diff --git a/pkg/service/service.go b/pkg/service/service.go index e7104fc40..6c1f306c9 100644 --- a/pkg/service/service.go +++ b/pkg/service/service.go @@ -17,6 +17,7 @@ import ( googleauth "github.com/dwarvesf/fortress-api/pkg/service/google" "github.com/dwarvesf/fortress-api/pkg/service/googledrive" "github.com/dwarvesf/fortress-api/pkg/service/googlemail" + "github.com/dwarvesf/fortress-api/pkg/service/mochi" "github.com/dwarvesf/fortress-api/pkg/service/notion" "github.com/dwarvesf/fortress-api/pkg/service/sendgrid" "github.com/dwarvesf/fortress-api/pkg/service/wise" @@ -34,6 +35,7 @@ type Service struct { Notion notion.IService Sendgrid sendgrid.Service Wise wise.IService + Mochi mochi.IService } func New(cfg *config.Config, store *store.Store, repo store.DBRepo) *Service { @@ -102,5 +104,6 @@ func New(cfg *config.Config, store *store.Store, repo store.DBRepo) *Service { logger.L, ), Basecamp: basecamp.NewService(store, repo, cfg, &bc, logger.L), + Mochi: mochi.New(cfg, logger.L), } } diff --git a/pkg/store/icytransaction/icy_transaction.go b/pkg/store/icytransaction/icy_transaction.go new file mode 100644 index 000000000..d62383a31 --- /dev/null +++ b/pkg/store/icytransaction/icy_transaction.go @@ -0,0 +1,17 @@ +package icytransaction + +import ( + "gorm.io/gorm" + + "github.com/dwarvesf/fortress-api/pkg/model" +) + +type store struct{} + +func New() IStore { + return &store{} +} + +func (s *store) Create(db *gorm.DB, model *model.IcyTransaction) error { + return db.Create(model).Error +} diff --git a/pkg/store/icytransaction/interface.go b/pkg/store/icytransaction/interface.go new file mode 100644 index 000000000..67177453e --- /dev/null +++ b/pkg/store/icytransaction/interface.go @@ -0,0 +1,11 @@ +package icytransaction + +import ( + "gorm.io/gorm" + + "github.com/dwarvesf/fortress-api/pkg/model" +) + +type IStore interface { + Create(db *gorm.DB, model *model.IcyTransaction) error +} diff --git a/pkg/store/store.go b/pkg/store/store.go index ce86ccc73..8ff351b17 100644 --- a/pkg/store/store.go +++ b/pkg/store/store.go @@ -36,6 +36,7 @@ import ( "github.com/dwarvesf/fortress-api/pkg/store/employeestack" "github.com/dwarvesf/fortress-api/pkg/store/expense" "github.com/dwarvesf/fortress-api/pkg/store/feedbackevent" + "github.com/dwarvesf/fortress-api/pkg/store/icytransaction" "github.com/dwarvesf/fortress-api/pkg/store/invoice" "github.com/dwarvesf/fortress-api/pkg/store/invoicenumbercaching" "github.com/dwarvesf/fortress-api/pkg/store/operationalservice" @@ -128,6 +129,7 @@ type Store struct { WorkUnitStack workunitstack.IStore OperationalService operationalservice.IStore DiscordLogTemplate discordtemplate.IStore + IcyTransaction icytransaction.IStore } func New() *Store { @@ -194,5 +196,6 @@ func New() *Store { WorkUnitStack: workunitstack.New(), OperationalService: operationalservice.New(), DiscordLogTemplate: discordtemplate.New(), + IcyTransaction: icytransaction.New(), } } diff --git a/pkg/utils/timeutil/util.go b/pkg/utils/timeutil/util.go index 15b014f06..38e9b0880 100644 --- a/pkg/utils/timeutil/util.go +++ b/pkg/utils/timeutil/util.go @@ -188,3 +188,49 @@ func CountWeekendDays(from, to time.Time) int { } return weekend } + +// GetStartDayOfWeek get monday 00:00:00 +// Example: today: 2023-05-12 07:34:21 +// return 2023-05-08 00:00:00 +// weekday value +// sunday = 0 +// moday = 1 +// tuesday = 2 +// ... +func GetStartDayOfWeek(tm time.Time) time.Time { + weekday := time.Duration(tm.Weekday()) + if weekday == 0 { + weekday = 7 + } + year, month, day := tm.Date() + currentStartDay := time.Date(year, month, day, 0, 0, 0, 0, time.Local) // return 2023-05-12 00:00:00 + + return currentStartDay.Add(-1 * (weekday - 1) * 24 * time.Hour) +} + +// GetEndDayOfWeek get sunday 23:59:59 +// Example: today: 2023-05-12 07:34:21 +// return 2023-05-14 23:59:59 +// weekday value +// sunday = 0 +// moday = 1 +// tuesday = 2 +// ... +func GetEndDayOfWeek(tm time.Time) time.Time { + weekday := time.Duration(tm.Weekday()) + if weekday == 0 { + weekday = 7 + } + year, month, day := tm.Date() + currentEndDay := time.Date(year, month, day, 23, 59, 59, 0, time.Local) // return 2023-05-12 00:00:00 + return currentEndDay.Add((7 - weekday) * 24 * time.Hour) +} + +// in curl special character need to be convert to ascii +// Example: curl --request GET \ +// --url 'http://localhost:8200/api/v1/vault/55/transaction?start_time=2023-05-08T00%3A00%3A00%2B07%3A00&end_time=2023-05-14T23%3A59%3A59%2B07%3A00' +func FormatDateForCurl(isoTime string) string { + isoTime = strings.ReplaceAll(isoTime, ":", "%3A") + isoTime = strings.ReplaceAll(isoTime, "+", "%2B") + return isoTime +} From 434b16c6893e39bcbe628c82fb728df97e346237 Mon Sep 17 00:00:00 2001 From: trkhoi Date: Mon, 15 May 2023 14:58:35 +0700 Subject: [PATCH 3/3] fix: add empty line --- .../schemas/20230513041758-create_icy_transactions_table.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/migrations/schemas/20230513041758-create_icy_transactions_table.sql b/migrations/schemas/20230513041758-create_icy_transactions_table.sql index ff802cd62..bdd6f215c 100644 --- a/migrations/schemas/20230513041758-create_icy_transactions_table.sql +++ b/migrations/schemas/20230513041758-create_icy_transactions_table.sql @@ -12,4 +12,4 @@ CREATE TABLE IF NOT EXISTS icy_transactions ( recipient_discord_id TEXT NOT NULL ); -- +migrate Down -DROP TABLE IF EXISTS icy_transactions; \ No newline at end of file +DROP TABLE IF EXISTS icy_transactions;