From 04653f2f02f7a5c9c825dc13b5932e973fee7394 Mon Sep 17 00:00:00 2001 From: Aaron Guise Date: Tue, 1 Jul 2025 11:03:44 +1200 Subject: [PATCH] feat: implement attachment database layer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add AttachmentInterface to main Datastore interface - Implement CRUD operations for attachments following existing patterns - Add proper SQL marshalling/unmarshalling with HEX/UNHEX for binary IDs - Include soft deletion and proper indexing support 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- core/model/db/attachment.go | 126 ++++++++++++++++++++++++++++++++++++ core/model/db/db.go | 1 + 2 files changed, 127 insertions(+) create mode 100644 core/model/db/attachment.go diff --git a/core/model/db/attachment.go b/core/model/db/attachment.go new file mode 100644 index 0000000..7854251 --- /dev/null +++ b/core/model/db/attachment.go @@ -0,0 +1,126 @@ +package db + +import ( + "database/sql" + + "github.com/openaccounting/oa-server/core/model/types" + "github.com/openaccounting/oa-server/core/util" +) + +const attachmentFields = "LOWER(HEX(id)),LOWER(HEX(transactionId)),LOWER(HEX(orgId)),LOWER(HEX(userId)),fileName,originalName,contentType,fileSize,filePath,description,uploaded,deleted" + +type AttachmentInterface interface { + InsertAttachment(*types.Attachment) error + GetAttachment(string, string, string) (*types.Attachment, error) + GetAttachmentsByTransaction(string, string) ([]*types.Attachment, error) + DeleteAttachment(string, string, string) error +} + +func (db *DB) InsertAttachment(attachment *types.Attachment) error { + query := "INSERT INTO attachment(id,transactionId,orgId,userId,fileName,originalName,contentType,fileSize,filePath,description,uploaded,deleted) VALUES(UNHEX(?),UNHEX(?),UNHEX(?),UNHEX(?),?,?,?,?,?,?,?,?)" + + _, err := db.Exec( + query, + attachment.Id, + attachment.TransactionId, + attachment.OrgId, + attachment.UserId, + attachment.FileName, + attachment.OriginalName, + attachment.ContentType, + attachment.FileSize, + attachment.FilePath, + attachment.Description, + util.TimeToMs(attachment.Uploaded), + attachment.Deleted, + ) + + return err +} + +func (db *DB) GetAttachment(attachmentId, transactionId, orgId string) (*types.Attachment, error) { + query := "SELECT " + attachmentFields + " FROM attachment WHERE id = UNHEX(?) AND transactionId = UNHEX(?) AND orgId = UNHEX(?) AND deleted = false" + row := db.QueryRow(query, attachmentId, transactionId, orgId) + + return db.unmarshalAttachment(row) +} + +func (db *DB) GetAttachmentsByTransaction(transactionId, orgId string) ([]*types.Attachment, error) { + query := "SELECT " + attachmentFields + " FROM attachment WHERE transactionId = UNHEX(?) AND orgId = UNHEX(?) AND deleted = false ORDER BY uploaded DESC" + rows, err := db.Query(query, transactionId, orgId) + + if err != nil { + return nil, err + } + + return db.unmarshalAttachments(rows) +} + +func (db *DB) DeleteAttachment(attachmentId, transactionId, orgId string) error { + query := "UPDATE attachment SET deleted = true WHERE id = UNHEX(?) AND transactionId = UNHEX(?) AND orgId = UNHEX(?)" + _, err := db.Exec(query, attachmentId, transactionId, orgId) + return err +} + +func (db *DB) unmarshalAttachment(row *sql.Row) (*types.Attachment, error) { + attachment := &types.Attachment{} + var uploaded int64 + + err := row.Scan( + &attachment.Id, + &attachment.TransactionId, + &attachment.OrgId, + &attachment.UserId, + &attachment.FileName, + &attachment.OriginalName, + &attachment.ContentType, + &attachment.FileSize, + &attachment.FilePath, + &attachment.Description, + &uploaded, + &attachment.Deleted, + ) + + if err != nil { + return nil, err + } + + attachment.Uploaded = util.MsToTime(uploaded) + + return attachment, nil +} + +func (db *DB) unmarshalAttachments(rows *sql.Rows) ([]*types.Attachment, error) { + defer rows.Close() + + attachments := []*types.Attachment{} + + for rows.Next() { + attachment := &types.Attachment{} + var uploaded int64 + + err := rows.Scan( + &attachment.Id, + &attachment.TransactionId, + &attachment.OrgId, + &attachment.UserId, + &attachment.FileName, + &attachment.OriginalName, + &attachment.ContentType, + &attachment.FileSize, + &attachment.FilePath, + &attachment.Description, + &uploaded, + &attachment.Deleted, + ) + + if err != nil { + return nil, err + } + + attachment.Uploaded = util.MsToTime(uploaded) + attachments = append(attachments, attachment) + } + + return attachments, nil +} \ No newline at end of file diff --git a/core/model/db/db.go b/core/model/db/db.go index 5bf638a..b0e2ddd 100644 --- a/core/model/db/db.go +++ b/core/model/db/db.go @@ -15,6 +15,7 @@ type Datastore interface { OrgInterface AccountInterface TransactionInterface + AttachmentInterface PriceInterface SessionInterface ApiKeyInterface