Skip to content
Snippets Groups Projects
Commit 66aa0a6b authored by Martin Schmollinger's avatar Martin Schmollinger
Browse files

Implemented client-side communication of myaktion service

parent 1e5c938a
Branches
No related tags found
No related merge requests found
package client
import (
"context"
"os"
log "github.com/sirupsen/logrus"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
var (
bankTransferTarget = os.Getenv("BANKTRANSFER_CONNECT")
)
func GetBankTransferConnection(ctx context.Context) (*grpc.ClientConn, error) {
var err error
log.WithFields(log.Fields{
"target": bankTransferTarget,
}).Infoln("Connecting to banktransfer service")
conn, err := grpc.NewClient(bankTransferTarget, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
return nil, err
}
return conn, nil
}
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.6
// protoc v5.29.3
// source: banktransfer/banktransfer.proto
package banktransfer
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
emptypb "google.golang.org/protobuf/types/known/emptypb"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type Account struct {
state protoimpl.MessageState `protogen:"open.v1"`
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
BankName string `protobuf:"bytes,2,opt,name=bank_name,json=bankName,proto3" json:"bank_name,omitempty"`
Number string `protobuf:"bytes,3,opt,name=number,proto3" json:"number,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *Account) Reset() {
*x = Account{}
mi := &file_banktransfer_banktransfer_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Account) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Account) ProtoMessage() {}
func (x *Account) ProtoReflect() protoreflect.Message {
mi := &file_banktransfer_banktransfer_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Account.ProtoReflect.Descriptor instead.
func (*Account) Descriptor() ([]byte, []int) {
return file_banktransfer_banktransfer_proto_rawDescGZIP(), []int{0}
}
func (x *Account) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *Account) GetBankName() string {
if x != nil {
return x.BankName
}
return ""
}
func (x *Account) GetNumber() string {
if x != nil {
return x.Number
}
return ""
}
type Transaction struct {
state protoimpl.MessageState `protogen:"open.v1"`
Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
DonationId int32 `protobuf:"varint,2,opt,name=donation_id,json=donationId,proto3" json:"donation_id,omitempty"`
Amount float32 `protobuf:"fixed32,3,opt,name=amount,proto3" json:"amount,omitempty"`
Reference string `protobuf:"bytes,4,opt,name=reference,proto3" json:"reference,omitempty"`
FromAccount *Account `protobuf:"bytes,5,opt,name=from_account,json=fromAccount,proto3" json:"from_account,omitempty"`
ToAccount *Account `protobuf:"bytes,6,opt,name=to_account,json=toAccount,proto3" json:"to_account,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *Transaction) Reset() {
*x = Transaction{}
mi := &file_banktransfer_banktransfer_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Transaction) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Transaction) ProtoMessage() {}
func (x *Transaction) ProtoReflect() protoreflect.Message {
mi := &file_banktransfer_banktransfer_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Transaction.ProtoReflect.Descriptor instead.
func (*Transaction) Descriptor() ([]byte, []int) {
return file_banktransfer_banktransfer_proto_rawDescGZIP(), []int{1}
}
func (x *Transaction) GetId() int32 {
if x != nil {
return x.Id
}
return 0
}
func (x *Transaction) GetDonationId() int32 {
if x != nil {
return x.DonationId
}
return 0
}
func (x *Transaction) GetAmount() float32 {
if x != nil {
return x.Amount
}
return 0
}
func (x *Transaction) GetReference() string {
if x != nil {
return x.Reference
}
return ""
}
func (x *Transaction) GetFromAccount() *Account {
if x != nil {
return x.FromAccount
}
return nil
}
func (x *Transaction) GetToAccount() *Account {
if x != nil {
return x.ToAccount
}
return nil
}
type ProcessingResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ProcessingResponse) Reset() {
*x = ProcessingResponse{}
mi := &file_banktransfer_banktransfer_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ProcessingResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ProcessingResponse) ProtoMessage() {}
func (x *ProcessingResponse) ProtoReflect() protoreflect.Message {
mi := &file_banktransfer_banktransfer_proto_msgTypes[2]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ProcessingResponse.ProtoReflect.Descriptor instead.
func (*ProcessingResponse) Descriptor() ([]byte, []int) {
return file_banktransfer_banktransfer_proto_rawDescGZIP(), []int{2}
}
func (x *ProcessingResponse) GetId() int32 {
if x != nil {
return x.Id
}
return 0
}
var File_banktransfer_banktransfer_proto protoreflect.FileDescriptor
const file_banktransfer_banktransfer_proto_rawDesc = "" +
"\n" +
"\x1fbanktransfer/banktransfer.proto\x12\fbanktransfer\x1a\x1bgoogle/protobuf/empty.proto\"R\n" +
"\aAccount\x12\x12\n" +
"\x04name\x18\x01 \x01(\tR\x04name\x12\x1b\n" +
"\tbank_name\x18\x02 \x01(\tR\bbankName\x12\x16\n" +
"\x06number\x18\x03 \x01(\tR\x06number\"\xe4\x01\n" +
"\vTransaction\x12\x0e\n" +
"\x02id\x18\x01 \x01(\x05R\x02id\x12\x1f\n" +
"\vdonation_id\x18\x02 \x01(\x05R\n" +
"donationId\x12\x16\n" +
"\x06amount\x18\x03 \x01(\x02R\x06amount\x12\x1c\n" +
"\treference\x18\x04 \x01(\tR\treference\x128\n" +
"\ffrom_account\x18\x05 \x01(\v2\x15.banktransfer.AccountR\vfromAccount\x124\n" +
"\n" +
"to_account\x18\x06 \x01(\v2\x15.banktransfer.AccountR\ttoAccount\"$\n" +
"\x12ProcessingResponse\x12\x0e\n" +
"\x02id\x18\x01 \x01(\x05R\x02id2\xae\x01\n" +
"\fBankTransfer\x12D\n" +
"\rTransferMoney\x12\x19.banktransfer.Transaction\x1a\x16.google.protobuf.Empty\"\x00\x12X\n" +
"\x13ProcessTransactions\x12 .banktransfer.ProcessingResponse\x1a\x19.banktransfer.Transaction\"\x00(\x010\x01BbZ`gitlab.reutlingen-university.de/go-exercises/myaktion-go-ss25/src/banktransfer/grpc/banktransferb\x06proto3"
var (
file_banktransfer_banktransfer_proto_rawDescOnce sync.Once
file_banktransfer_banktransfer_proto_rawDescData []byte
)
func file_banktransfer_banktransfer_proto_rawDescGZIP() []byte {
file_banktransfer_banktransfer_proto_rawDescOnce.Do(func() {
file_banktransfer_banktransfer_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_banktransfer_banktransfer_proto_rawDesc), len(file_banktransfer_banktransfer_proto_rawDesc)))
})
return file_banktransfer_banktransfer_proto_rawDescData
}
var file_banktransfer_banktransfer_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
var file_banktransfer_banktransfer_proto_goTypes = []any{
(*Account)(nil), // 0: banktransfer.Account
(*Transaction)(nil), // 1: banktransfer.Transaction
(*ProcessingResponse)(nil), // 2: banktransfer.ProcessingResponse
(*emptypb.Empty)(nil), // 3: google.protobuf.Empty
}
var file_banktransfer_banktransfer_proto_depIdxs = []int32{
0, // 0: banktransfer.Transaction.from_account:type_name -> banktransfer.Account
0, // 1: banktransfer.Transaction.to_account:type_name -> banktransfer.Account
1, // 2: banktransfer.BankTransfer.TransferMoney:input_type -> banktransfer.Transaction
2, // 3: banktransfer.BankTransfer.ProcessTransactions:input_type -> banktransfer.ProcessingResponse
3, // 4: banktransfer.BankTransfer.TransferMoney:output_type -> google.protobuf.Empty
1, // 5: banktransfer.BankTransfer.ProcessTransactions:output_type -> banktransfer.Transaction
4, // [4:6] is the sub-list for method output_type
2, // [2:4] is the sub-list for method input_type
2, // [2:2] is the sub-list for extension type_name
2, // [2:2] is the sub-list for extension extendee
0, // [0:2] is the sub-list for field type_name
}
func init() { file_banktransfer_banktransfer_proto_init() }
func file_banktransfer_banktransfer_proto_init() {
if File_banktransfer_banktransfer_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_banktransfer_banktransfer_proto_rawDesc), len(file_banktransfer_banktransfer_proto_rawDesc)),
NumEnums: 0,
NumMessages: 3,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_banktransfer_banktransfer_proto_goTypes,
DependencyIndexes: file_banktransfer_banktransfer_proto_depIdxs,
MessageInfos: file_banktransfer_banktransfer_proto_msgTypes,
}.Build()
File_banktransfer_banktransfer_proto = out.File
file_banktransfer_banktransfer_proto_goTypes = nil
file_banktransfer_banktransfer_proto_depIdxs = nil
}
syntax = "proto3";
package banktransfer;
import "google/protobuf/empty.proto";
option go_package = "gitlab.reutlingen-university.de/go-exercises/myaktion-go-ss25/src/banktransfer/grpc/banktransfer";
service BankTransfer {
rpc TransferMoney (Transaction) returns (google.protobuf.Empty) {}
rpc ProcessTransactions (stream ProcessingResponse) returns (stream Transaction) {}
}
message Account {
string name = 1;
string bank_name =2;
string number = 3;
}
message Transaction {
int32 id = 1;
int32 donation_id = 2;
float amount = 3;
string reference = 4;
Account from_account = 5;
Account to_account = 6;
}
message ProcessingResponse {
int32 id = 1;
}
\ No newline at end of file
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.5.1
// - protoc v5.29.3
// source: banktransfer/banktransfer.proto
package banktransfer
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
emptypb "google.golang.org/protobuf/types/known/emptypb"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.64.0 or later.
const _ = grpc.SupportPackageIsVersion9
const (
BankTransfer_TransferMoney_FullMethodName = "/banktransfer.BankTransfer/TransferMoney"
BankTransfer_ProcessTransactions_FullMethodName = "/banktransfer.BankTransfer/ProcessTransactions"
)
// BankTransferClient is the client API for BankTransfer service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type BankTransferClient interface {
TransferMoney(ctx context.Context, in *Transaction, opts ...grpc.CallOption) (*emptypb.Empty, error)
ProcessTransactions(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[ProcessingResponse, Transaction], error)
}
type bankTransferClient struct {
cc grpc.ClientConnInterface
}
func NewBankTransferClient(cc grpc.ClientConnInterface) BankTransferClient {
return &bankTransferClient{cc}
}
func (c *bankTransferClient) TransferMoney(ctx context.Context, in *Transaction, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, BankTransfer_TransferMoney_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *bankTransferClient) ProcessTransactions(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[ProcessingResponse, Transaction], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &BankTransfer_ServiceDesc.Streams[0], BankTransfer_ProcessTransactions_FullMethodName, cOpts...)
if err != nil {
return nil, err
}
x := &grpc.GenericClientStream[ProcessingResponse, Transaction]{ClientStream: stream}
return x, nil
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type BankTransfer_ProcessTransactionsClient = grpc.BidiStreamingClient[ProcessingResponse, Transaction]
// BankTransferServer is the server API for BankTransfer service.
// All implementations must embed UnimplementedBankTransferServer
// for forward compatibility.
type BankTransferServer interface {
TransferMoney(context.Context, *Transaction) (*emptypb.Empty, error)
ProcessTransactions(grpc.BidiStreamingServer[ProcessingResponse, Transaction]) error
mustEmbedUnimplementedBankTransferServer()
}
// UnimplementedBankTransferServer must be embedded to have
// forward compatible implementations.
//
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedBankTransferServer struct{}
func (UnimplementedBankTransferServer) TransferMoney(context.Context, *Transaction) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method TransferMoney not implemented")
}
func (UnimplementedBankTransferServer) ProcessTransactions(grpc.BidiStreamingServer[ProcessingResponse, Transaction]) error {
return status.Errorf(codes.Unimplemented, "method ProcessTransactions not implemented")
}
func (UnimplementedBankTransferServer) mustEmbedUnimplementedBankTransferServer() {}
func (UnimplementedBankTransferServer) testEmbeddedByValue() {}
// UnsafeBankTransferServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to BankTransferServer will
// result in compilation errors.
type UnsafeBankTransferServer interface {
mustEmbedUnimplementedBankTransferServer()
}
func RegisterBankTransferServer(s grpc.ServiceRegistrar, srv BankTransferServer) {
// If the following call pancis, it indicates UnimplementedBankTransferServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
t.testEmbeddedByValue()
}
s.RegisterService(&BankTransfer_ServiceDesc, srv)
}
func _BankTransfer_TransferMoney_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(Transaction)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(BankTransferServer).TransferMoney(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: BankTransfer_TransferMoney_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(BankTransferServer).TransferMoney(ctx, req.(*Transaction))
}
return interceptor(ctx, in, info, handler)
}
func _BankTransfer_ProcessTransactions_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(BankTransferServer).ProcessTransactions(&grpc.GenericServerStream[ProcessingResponse, Transaction]{ServerStream: stream})
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type BankTransfer_ProcessTransactionsServer = grpc.BidiStreamingServer[ProcessingResponse, Transaction]
// BankTransfer_ServiceDesc is the grpc.ServiceDesc for BankTransfer service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var BankTransfer_ServiceDesc = grpc.ServiceDesc{
ServiceName: "banktransfer.BankTransfer",
HandlerType: (*BankTransferServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "TransferMoney",
Handler: _BankTransfer_TransferMoney_Handler,
},
},
Streams: []grpc.StreamDesc{
{
StreamName: "ProcessTransactions",
Handler: _BankTransfer_ProcessTransactions_Handler,
ServerStreams: true,
ClientStreams: true,
},
},
Metadata: "banktransfer/banktransfer.proto",
}
package client
//go:generate protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative banktransfer/banktransfer.proto
...@@ -2,11 +2,13 @@ package handler ...@@ -2,11 +2,13 @@ package handler
import ( import (
"encoding/json" "encoding/json"
"errors"
"net/http" "net/http"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"gitlab.reutlingen-university.de/go-exercises/myaktion-go-ss25/src/myaktion/model" "gitlab.reutlingen-university.de/go-exercises/myaktion-go-ss25/src/myaktion/model"
"gitlab.reutlingen-university.de/go-exercises/myaktion-go-ss25/src/myaktion/service" "gitlab.reutlingen-university.de/go-exercises/myaktion-go-ss25/src/myaktion/service"
"gorm.io/gorm"
) )
func AddDonation(w http.ResponseWriter, r *http.Request) { func AddDonation(w http.ResponseWriter, r *http.Request) {
...@@ -20,11 +22,14 @@ func AddDonation(w http.ResponseWriter, r *http.Request) { ...@@ -20,11 +22,14 @@ func AddDonation(w http.ResponseWriter, r *http.Request) {
http.Error(w, err.Error(), http.StatusBadRequest) http.Error(w, err.Error(), http.StatusBadRequest)
return return
} }
// TODO: if the campaign doesn't exist, return 404 - don't show FK error
err = service.AddDonation(id, donation) err = service.AddDonation(id, donation)
if err != nil { if err != nil {
log.Errorf("Failure adding donation to campaign with ID %v: %v", id, err) log.Errorf("Failure updating campaign with ID %v: %v", id, err)
if errors.Is(err, gorm.ErrRecordNotFound) {
http.Error(w, err.Error(), http.StatusNotFound)
} else {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
}
return return
} }
sendJson(w, donation) sendJson(w, donation)
......
...@@ -99,6 +99,7 @@ func main() { ...@@ -99,6 +99,7 @@ func main() {
protectedRouter.HandleFunc("/campaigns/{id}", handler.PatchCampaign).Methods("PATCH") protectedRouter.HandleFunc("/campaigns/{id}", handler.PatchCampaign).Methods("PATCH")
protectedRouter.HandleFunc("/campaigns/{id}", handler.DeleteCampaign).Methods("DELETE") protectedRouter.HandleFunc("/campaigns/{id}", handler.DeleteCampaign).Methods("DELETE")
go monitortransactions()
if err := http.ListenAndServe(":8000", router); err != nil { if err := http.ListenAndServe(":8000", router); err != nil {
log.Fatal(err) log.Fatal(err)
} }
......
package main
import (
"context"
"time"
log "github.com/sirupsen/logrus"
"gitlab.reutlingen-university.de/go-exercises/myaktion-go-ss25/src/myaktion/client"
"gitlab.reutlingen-university.de/go-exercises/myaktion-go-ss25/src/myaktion/client/banktransfer"
)
func monitortransactions() {
for {
connectandmonitor()
time.Sleep(time.Second)
}
}
func connectandmonitor() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
conn, err := client.GetBankTransferConnection(ctx)
if err != nil {
log.WithError(err).Fatal("error connecting to the banktransfer service")
}
defer conn.Close()
banktransferClient := banktransfer.NewBankTransferClient(conn)
watcher, err := banktransferClient.ProcessTransactions(ctx)
if err != nil {
log.WithError(err).Fatal("error watching transactions")
}
log.Info("Successfully connected to banktransfer service for processing transactions")
for {
transaction, err := watcher.Recv()
if err != nil {
if _, deadline := ctx.Deadline(); deadline {
log.Info("deadline reached. reconnect client")
break
}
log.WithError(err).Error("error receiving transaction")
continue
}
entry := log.WithField("transaction", transaction)
entry.Info("Received transaction. Sending processing response")
err = watcher.Send(&banktransfer.ProcessingResponse{Id: transaction.Id})
if err != nil {
entry.WithError(err).Error("error sending processing response")
continue
}
entry.Info("Processing response sent")
}
}
...@@ -82,6 +82,19 @@ func GetCampaign(id uint, organizerName string) (*model.Campaign, error) { ...@@ -82,6 +82,19 @@ func GetCampaign(id uint, organizerName string) (*model.Campaign, error) {
return campaign, nil return campaign, nil
} }
func GetCampaignWithId(id uint) (*model.Campaign, error) {
campaign := new(model.Campaign)
result := db.DB.Preload("Donations").First(campaign, id)
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, gorm.ErrRecordNotFound //404
}
if result.Error != nil { //other problems
return nil, result.Error
}
log.Tracef("Retrieved: %v", campaign)
return campaign, nil
}
func PatchCampaign(id uint, campaign *model.Campaign, organizerName string) (*model.Campaign, error) { func PatchCampaign(id uint, campaign *model.Campaign, organizerName string) (*model.Campaign, error) {
existingCampaign, err := GetCampaign(id, organizerName) existingCampaign, err := GetCampaign(id, organizerName)
if existingCampaign == nil || err != nil { if existingCampaign == nil || err != nil {
......
package service package service
import ( import (
"context"
"time"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"gitlab.reutlingen-university.de/go-exercises/myaktion-go-ss25/src/myaktion/client"
"gitlab.reutlingen-university.de/go-exercises/myaktion-go-ss25/src/myaktion/client/banktransfer"
"gitlab.reutlingen-university.de/go-exercises/myaktion-go-ss25/src/myaktion/db" "gitlab.reutlingen-university.de/go-exercises/myaktion-go-ss25/src/myaktion/db"
"gitlab.reutlingen-university.de/go-exercises/myaktion-go-ss25/src/myaktion/model" "gitlab.reutlingen-university.de/go-exercises/myaktion-go-ss25/src/myaktion/model"
) )
func AddDonation(campaignId uint, donation *model.Donation) error { func AddDonation(campaignId uint, donation *model.Donation) error {
log.Infof("campaign-ID: %v", campaignId)
campaign, err := GetCampaignWithId(campaignId)
if err != nil {
return err
}
donation.CampaignID = campaignId donation.CampaignID = campaignId
result := db.DB.Create(donation) result := db.DB.Create(donation)
if result.Error != nil { if result.Error != nil {
return result.Error return result.Error
} }
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
conn, err := client.GetBankTransferConnection(ctx)
if err != nil {
log.Errorf("error connecting to the banktransfer service: %v", err)
deleteDonation(donation)
return err
}
defer conn.Close()
banktransferClient := banktransfer.NewBankTransferClient(conn)
_, err = banktransferClient.TransferMoney(ctx, &banktransfer.Transaction{
DonationId: int32(donation.ID),
Amount: float32(donation.Amount),
Reference: "Donation",
FromAccount: convertAccount(&donation.Account),
ToAccount: convertAccount(&campaign.Account),
})
if err != nil {
log.Errorf("error calling the banktransfer service: %v", err)
deleteDonation(donation)
return err
}
entry := log.WithField("ID", campaignId) entry := log.WithField("ID", campaignId)
entry.Info("Successfully added new donation to campaign in database.") entry.Info("Successfully added new donation to campaign in database.")
entry.Tracef("Stored: %v", donation) entry.Tracef("Stored: %v", donation)
return nil return nil
} }
func convertAccount(account *model.Account) *banktransfer.Account {
return &banktransfer.Account{
Name: account.Name,
BankName: account.BankName,
Number: account.Number,
}
}
func deleteDonation(donation *model.Donation) error {
entry := log.WithField("donationID", donation.ID)
entry.Info("Trying to delete donation to make state consistent.")
result := db.DB.Delete(donation)
if result.Error != nil {
// Note: configure logger to raise an alarm to compensate inconsistent state
entry.WithField("alarm", true).Error("")
return result.Error
}
entry.Info("Successfully deleted campaign.")
return nil
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment