diff --git a/core/model/account.go b/core/model/account.go index a2dd94d..d4ecec7 100644 --- a/core/model/account.go +++ b/core/model/account.go @@ -408,6 +408,15 @@ func (model *Model) accountsContainWriteAccess(accounts []*types.Account, accoun return false } +func (model *Model) accountsContainReadAccess(accounts []*types.Account, accountId string) bool { + for _, account := range accounts { + if account.Id == accountId { + return true + } + } + return false +} + func (model *Model) getAccountFromList(accounts []*types.Account, accountId string) *types.Account { for _, account := range accounts { if account.Id == accountId { diff --git a/core/model/attachment.go b/core/model/attachment.go new file mode 100644 index 0000000..01af3f1 --- /dev/null +++ b/core/model/attachment.go @@ -0,0 +1,163 @@ +package model + +import ( + "errors" + "time" + + "github.com/openaccounting/oa-server/core/model/types" +) + +type AttachmentInterface interface { + CreateAttachment(*types.Attachment) (*types.Attachment, error) + GetAttachmentsByTransaction(string, string, string) ([]*types.Attachment, error) + GetAttachment(string, string, string, string) (*types.Attachment, error) + DeleteAttachment(string, string, string, string) error +} + +func (model *Model) CreateAttachment(attachment *types.Attachment) (*types.Attachment, error) { + if attachment.Id == "" { + return nil, errors.New("attachment ID required") + } + + if attachment.TransactionId == "" { + return nil, errors.New("transaction ID required") + } + + if attachment.OrgId == "" { + return nil, errors.New("organization ID required") + } + + if attachment.UserId == "" { + return nil, errors.New("user ID required") + } + + if attachment.FileName == "" { + return nil, errors.New("file name required") + } + + if attachment.FilePath == "" { + return nil, errors.New("file path required") + } + + // Set upload timestamp + attachment.Uploaded = time.Now() + attachment.Deleted = false + + // Save to database + err := model.db.InsertAttachment(attachment) + if err != nil { + return nil, err + } + + return attachment, nil +} + +func (model *Model) GetAttachmentsByTransaction(transactionId, orgId, userId string) ([]*types.Attachment, error) { + if transactionId == "" { + return nil, errors.New("transaction ID required") + } + + if orgId == "" { + return nil, errors.New("organization ID required") + } + + if userId == "" { + return nil, errors.New("user ID required") + } + + // First verify the user has access to the transaction + tx, err := model.GetTransaction(transactionId, orgId, userId) + if err != nil { + return nil, err + } + if tx == nil { + return nil, errors.New("transaction not found or access denied") + } + + // Get attachments for the transaction + attachments, err := model.db.GetAttachmentsByTransaction(transactionId, orgId) + if err != nil { + return nil, err + } + + return attachments, nil +} + +func (model *Model) GetAttachment(attachmentId, transactionId, orgId, userId string) (*types.Attachment, error) { + if attachmentId == "" { + return nil, errors.New("attachment ID required") + } + + if transactionId == "" { + return nil, errors.New("transaction ID required") + } + + if orgId == "" { + return nil, errors.New("organization ID required") + } + + if userId == "" { + return nil, errors.New("user ID required") + } + + // First verify the user has access to the transaction + tx, err := model.GetTransaction(transactionId, orgId, userId) + if err != nil { + return nil, err + } + if tx == nil { + return nil, errors.New("transaction not found or access denied") + } + + // Get the attachment + attachment, err := model.db.GetAttachment(attachmentId, transactionId, orgId) + if err != nil { + return nil, err + } + + return attachment, nil +} + +func (model *Model) DeleteAttachment(attachmentId, transactionId, orgId, userId string) error { + if attachmentId == "" { + return errors.New("attachment ID required") + } + + if transactionId == "" { + return errors.New("transaction ID required") + } + + if orgId == "" { + return errors.New("organization ID required") + } + + if userId == "" { + return errors.New("user ID required") + } + + // First verify the user has access to the transaction + tx, err := model.GetTransaction(transactionId, orgId, userId) + if err != nil { + return err + } + if tx == nil { + return errors.New("transaction not found or access denied") + } + + // Verify the attachment exists and belongs to the transaction + attachment, err := model.db.GetAttachment(attachmentId, transactionId, orgId) + if err != nil { + return err + } + if attachment == nil { + return errors.New("attachment not found") + } + + // Soft delete the attachment + err = model.db.DeleteAttachment(attachmentId, transactionId, orgId) + if err != nil { + return err + } + + return nil +} \ No newline at end of file diff --git a/core/model/model.go b/core/model/model.go index 48e4e8a..10235de 100644 --- a/core/model/model.go +++ b/core/model/model.go @@ -20,11 +20,13 @@ type Interface interface { OrgInterface AccountInterface TransactionInterface + AttachmentInterface PriceInterface SessionInterface ApiKeyInterface SystemHealthInteface BudgetInterface + GetTransaction(string, string, string) (*types.Transaction, error) } func NewModel(db db.Datastore, bcrypt util.Bcrypt, config types.Config) *Model { diff --git a/core/model/transaction.go b/core/model/transaction.go index 87efb60..4d78bca 100644 --- a/core/model/transaction.go +++ b/core/model/transaction.go @@ -169,6 +169,31 @@ func (model *Model) getTransactionById(id string) (*types.Transaction, error) { return model.db.GetTransactionById(id) } +func (model *Model) GetTransaction(transactionId, orgId, userId string) (*types.Transaction, error) { + transaction, err := model.getTransactionById(transactionId) + if err != nil { + return nil, err + } + + if transaction == nil || transaction.OrgId != orgId { + return nil, nil + } + + // Check if user has access to all accounts in the transaction + userAccounts, err := model.GetAccounts(orgId, userId, "") + if err != nil { + return nil, err + } + + for _, split := range transaction.Splits { + if !model.accountsContainReadAccess(userAccounts, split.AccountId) { + return nil, fmt.Errorf("user does not have permission to access account %s", split.AccountId) + } + } + + return transaction, nil +} + func (model *Model) checkSplits(transaction *types.Transaction) (err error) { if len(transaction.Splits) < 2 { return errors.New("at least 2 splits are required")