feat: implement attachment database layer

- 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 <noreply@anthropic.com>
This commit is contained in:
2025-07-01 11:03:44 +12:00
parent 3b89d8137e
commit 04653f2f02
2 changed files with 127 additions and 0 deletions

126
core/model/db/attachment.go Normal file
View File

@@ -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
}

View File

@@ -15,6 +15,7 @@ type Datastore interface {
OrgInterface
AccountInterface
TransactionInterface
AttachmentInterface
PriceInterface
SessionInterface
ApiKeyInterface