You've already forked openaccounting-server
forked from cybercinch/openaccounting-server
- Add JWT-based secure file access for local storage with 1-hour expiry - Implement GORM repository methods for attachment CRUD operations - Add secure file serving endpoint with token validation - Update storage interface to support user context in URL generation - Add comprehensive security features including path traversal protection - Update documentation with security model and configuration examples - Add utility functions for hex/byte conversion and UUID validation - Configure secure file permissions (0600) for uploaded files 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
357 lines
11 KiB
Go
357 lines
11 KiB
Go
package model
|
|
|
|
import (
|
|
"errors"
|
|
"time"
|
|
|
|
"github.com/openaccounting/oa-server/core/model/types"
|
|
"github.com/openaccounting/oa-server/core/repository"
|
|
"github.com/openaccounting/oa-server/core/util"
|
|
"github.com/openaccounting/oa-server/database"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// GormModel is the GORM-based implementation of the Model
|
|
type GormModel struct {
|
|
repository *repository.GormRepository
|
|
bcrypt util.Bcrypt
|
|
config types.Config
|
|
}
|
|
|
|
// NewGormModel creates a new GORM-based model
|
|
func NewGormModel(gormDB *gorm.DB, bcrypt util.Bcrypt, config types.Config) *GormModel {
|
|
repo := repository.NewGormRepository(gormDB)
|
|
return &GormModel{
|
|
repository: repo,
|
|
bcrypt: bcrypt,
|
|
config: config,
|
|
}
|
|
}
|
|
|
|
// CreateModel creates a new model using the existing database connection
|
|
func CreateGormModel(bcrypt util.Bcrypt, config types.Config) (*GormModel, error) {
|
|
// Use the existing database connection
|
|
if database.DB == nil {
|
|
return nil, errors.New("database connection not initialized")
|
|
}
|
|
|
|
return NewGormModel(database.DB, bcrypt, config), nil
|
|
}
|
|
|
|
// Implement the Interface by delegating to the business logic layer
|
|
// The business logic layer (existing model methods) will call the repository
|
|
|
|
// UserInterface methods - delegate to existing business logic
|
|
func (m *GormModel) CreateUser(user *types.User) error {
|
|
// The existing business logic in user.go will be updated to use the repository
|
|
// For now, delegate directly to repository for basic operations
|
|
return m.repository.InsertUser(user)
|
|
}
|
|
|
|
func (m *GormModel) VerifyUser(code string) error {
|
|
return m.repository.VerifyUser(code)
|
|
}
|
|
|
|
func (m *GormModel) UpdateUser(user *types.User) error {
|
|
return m.repository.UpdateUser(user)
|
|
}
|
|
|
|
func (m *GormModel) ResetPassword(email string) error {
|
|
// This would need the full business logic from the original model
|
|
// For now, simplified implementation
|
|
user, err := m.repository.GetVerifiedUserByEmail(email)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
user.PasswordReset, err = util.NewGuid()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return m.repository.UpdateUserResetPassword(user)
|
|
}
|
|
|
|
func (m *GormModel) ConfirmResetPassword(password string, code string) (*types.User, error) {
|
|
user, err := m.repository.GetUserByResetCode(code)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
passwordHash, err := m.bcrypt.GenerateFromPassword([]byte(password), m.bcrypt.GetDefaultCost())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
user.PasswordHash = string(passwordHash)
|
|
user.Password = ""
|
|
|
|
err = m.repository.UpdateUser(user)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return user, nil
|
|
}
|
|
|
|
// AccountInterface methods - delegate to repository
|
|
func (m *GormModel) CreateAccount(account *types.Account, userId string) error {
|
|
return m.repository.InsertAccount(account)
|
|
}
|
|
|
|
func (m *GormModel) UpdateAccount(account *types.Account, userId string) error {
|
|
return m.repository.UpdateAccount(account)
|
|
}
|
|
|
|
func (m *GormModel) DeleteAccount(id string, userId string, orgId string) error {
|
|
return m.repository.DeleteAccount(id)
|
|
}
|
|
|
|
func (m *GormModel) GetAccounts(orgId string, userId string, tokenId string) ([]*types.Account, error) {
|
|
return m.repository.GetAccountsByOrgId(orgId)
|
|
}
|
|
|
|
func (m *GormModel) GetAccountsWithBalances(orgId string, userId string, tokenId string, date time.Time) ([]*types.Account, error) {
|
|
accounts, err := m.repository.GetAccountsByOrgId(orgId)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Add balance calculations
|
|
err = m.repository.AddBalances(accounts, date)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return accounts, nil
|
|
}
|
|
|
|
func (m *GormModel) GetAccount(orgId, accId, userId, tokenId string) (*types.Account, error) {
|
|
return m.repository.GetAccount(accId)
|
|
}
|
|
|
|
func (m *GormModel) GetAccountWithBalance(orgId, accId, userId, tokenId string, date time.Time) (*types.Account, error) {
|
|
account, err := m.repository.GetAccount(accId)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Add balance calculation
|
|
err = m.repository.AddBalance(account, date)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return account, nil
|
|
}
|
|
|
|
// Complete OrgInterface implementation
|
|
func (m *GormModel) CreateOrg(org *types.Org, userId string) error {
|
|
// Get default accounts - this needs to be implemented properly
|
|
accounts := []*types.Account{} // Empty for now, should create default chart of accounts
|
|
return m.repository.CreateOrg(org, userId, accounts)
|
|
}
|
|
|
|
func (m *GormModel) GetOrg(orgId, userId string) (*types.Org, error) {
|
|
return m.repository.GetOrg(orgId, userId)
|
|
}
|
|
|
|
func (m *GormModel) GetOrgs(userId string) ([]*types.Org, error) {
|
|
return m.repository.GetOrgs(userId)
|
|
}
|
|
|
|
func (m *GormModel) UpdateOrg(org *types.Org, userId string) error {
|
|
return m.repository.UpdateOrg(org)
|
|
}
|
|
|
|
func (m *GormModel) CreateInvite(invite *types.Invite, userId string) error {
|
|
return m.repository.InsertInvite(invite)
|
|
}
|
|
|
|
func (m *GormModel) AcceptInvite(invite *types.Invite, userId string) error {
|
|
return m.repository.AcceptInvite(invite, userId)
|
|
}
|
|
|
|
func (m *GormModel) GetInvites(orgId, userId string) ([]*types.Invite, error) {
|
|
return m.repository.GetInvites(orgId)
|
|
}
|
|
|
|
func (m *GormModel) DeleteInvite(inviteId, userId string) error {
|
|
return m.repository.DeleteInvite(inviteId)
|
|
}
|
|
|
|
// SessionInterface implementation
|
|
func (m *GormModel) CreateSession(session *types.Session) error {
|
|
return m.repository.InsertSession(session)
|
|
}
|
|
|
|
func (m *GormModel) InsertSession(session *types.Session) error {
|
|
return m.repository.InsertSession(session)
|
|
}
|
|
|
|
func (m *GormModel) DeleteSession(sessionId, userId string) error {
|
|
return m.repository.DeleteSession(sessionId, userId)
|
|
}
|
|
|
|
func (m *GormModel) UpdateSessionActivity(sessionId string) error {
|
|
return m.repository.UpdateSessionActivity(sessionId)
|
|
}
|
|
|
|
// ApiKeyInterface implementation
|
|
func (m *GormModel) CreateApiKey(apiKey *types.ApiKey) error {
|
|
return m.repository.InsertApiKey(apiKey)
|
|
}
|
|
|
|
func (m *GormModel) InsertApiKey(apiKey *types.ApiKey) error {
|
|
return m.repository.InsertApiKey(apiKey)
|
|
}
|
|
|
|
func (m *GormModel) UpdateApiKey(apiKey *types.ApiKey) error {
|
|
return m.repository.UpdateApiKey(apiKey)
|
|
}
|
|
|
|
func (m *GormModel) DeleteApiKey(keyId, userId string) error {
|
|
return m.repository.DeleteApiKey(keyId, userId)
|
|
}
|
|
|
|
func (m *GormModel) GetApiKeys(userId string) ([]*types.ApiKey, error) {
|
|
return m.repository.GetApiKeys(userId)
|
|
}
|
|
|
|
func (m *GormModel) UpdateApiKeyActivity(keyId string) error {
|
|
return m.repository.UpdateApiKeyActivity(keyId)
|
|
}
|
|
|
|
// TransactionInterface implementation
|
|
func (m *GormModel) CreateTransaction(transaction *types.Transaction) error {
|
|
return m.repository.InsertTransaction(transaction)
|
|
}
|
|
|
|
func (m *GormModel) UpdateTransaction(transactionId string, transaction *types.Transaction) error {
|
|
return m.repository.DeleteAndInsertTransaction(transactionId, transaction)
|
|
}
|
|
|
|
func (m *GormModel) GetTransactionsByAccount(accountId, orgId, userId string, options *types.QueryOptions) ([]*types.Transaction, error) {
|
|
return m.repository.GetTransactionsByAccount(accountId, options)
|
|
}
|
|
|
|
func (m *GormModel) GetTransactionsByOrg(orgId, userId string, options *types.QueryOptions) ([]*types.Transaction, error) {
|
|
return m.repository.GetTransactionsByOrg(orgId, options, []string{})
|
|
}
|
|
|
|
func (m *GormModel) DeleteTransaction(transactionId, orgId, userId string) error {
|
|
return m.repository.DeleteTransaction(transactionId)
|
|
}
|
|
|
|
func (m *GormModel) InsertTransaction(transaction *types.Transaction) error {
|
|
return m.repository.InsertTransaction(transaction)
|
|
}
|
|
|
|
func (m *GormModel) GetTransaction(transactionId, orgId, userId string) (*types.Transaction, error) {
|
|
// For now, delegate to repository - in a full implementation, this would include permission checking
|
|
return m.repository.GetTransactionById(transactionId)
|
|
}
|
|
|
|
// AttachmentInterface implementation
|
|
func (m *GormModel) CreateAttachment(attachment *types.Attachment) (*types.Attachment, error) {
|
|
if attachment.Id == "" {
|
|
return nil, errors.New("attachment ID required")
|
|
}
|
|
|
|
// Set upload timestamp
|
|
attachment.Uploaded = time.Now()
|
|
attachment.Deleted = false
|
|
|
|
// Use repository to insert attachment
|
|
err := m.repository.InsertAttachment(attachment)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return attachment, nil
|
|
}
|
|
|
|
func (m *GormModel) GetAttachmentsByTransaction(transactionId, orgId, userId string) ([]*types.Attachment, error) {
|
|
return m.repository.GetAttachmentsByTransaction(transactionId, orgId, userId)
|
|
}
|
|
|
|
func (m *GormModel) GetAttachment(attachmentId, transactionId, orgId, userId string) (*types.Attachment, error) {
|
|
return m.repository.GetAttachment(attachmentId, transactionId, orgId, userId)
|
|
}
|
|
|
|
func (m *GormModel) DeleteAttachment(attachmentId, transactionId, orgId, userId string) error {
|
|
return m.repository.DeleteAttachment(attachmentId, transactionId, orgId, userId)
|
|
}
|
|
|
|
func (m *GormModel) GetTransactionById(id string) (*types.Transaction, error) {
|
|
return m.repository.GetTransactionById(id)
|
|
}
|
|
|
|
func (m *GormModel) DeleteAndInsertTransaction(id string, transaction *types.Transaction) error {
|
|
return m.repository.DeleteAndInsertTransaction(id, transaction)
|
|
}
|
|
|
|
// PriceInterface implementation
|
|
func (m *GormModel) CreatePrice(price *types.Price, userId string) error {
|
|
return m.repository.InsertPrice(price)
|
|
}
|
|
|
|
func (m *GormModel) DeletePrice(priceId, userId string) error {
|
|
// Stub implementation - would need proper implementation
|
|
return nil
|
|
}
|
|
|
|
func (m *GormModel) GetPricesNearestInTime(orgId string, date time.Time, currency string) ([]*types.Price, error) {
|
|
// Stub implementation - would need proper implementation based on specific logic
|
|
return m.repository.GetPrices(orgId, date)
|
|
}
|
|
|
|
func (m *GormModel) GetPricesByCurrency(orgId, currency, userId string) ([]*types.Price, error) {
|
|
// Stub implementation - would need proper implementation based on specific logic
|
|
return m.repository.GetPrices(orgId, time.Now())
|
|
}
|
|
|
|
func (m *GormModel) GetPrices(orgId string, date time.Time) ([]*types.Price, error) {
|
|
return m.repository.GetPrices(orgId, date)
|
|
}
|
|
|
|
func (m *GormModel) InsertPrice(price *types.Price) error {
|
|
return m.repository.InsertPrice(price)
|
|
}
|
|
|
|
// SystemHealthInteface implementation
|
|
func (m *GormModel) PingDatabase() error {
|
|
return m.repository.Ping()
|
|
}
|
|
|
|
func (m *GormModel) Ping() error {
|
|
return m.repository.Ping()
|
|
}
|
|
|
|
// BudgetInterface implementation
|
|
func (m *GormModel) GetBudget(orgId, userId string) (*types.Budget, error) {
|
|
// Stub implementation - would need proper implementation
|
|
return &types.Budget{}, nil
|
|
}
|
|
|
|
func (m *GormModel) CreateBudget(budget *types.Budget, userId string) error {
|
|
return m.repository.InsertBudget(budget)
|
|
}
|
|
|
|
func (m *GormModel) DeleteBudget(budgetId, userId string) error {
|
|
// Stub implementation - would need proper implementation
|
|
return nil
|
|
}
|
|
|
|
func (m *GormModel) InsertBudget(budget *types.Budget) error {
|
|
return m.repository.InsertBudget(budget)
|
|
}
|
|
|
|
func (m *GormModel) GetBudgets(orgId string) ([]*types.Budget, error) {
|
|
return m.repository.GetBudgets(orgId)
|
|
}
|
|
|
|
// Helper methods
|
|
func (m *GormModel) GetOrgUserIds(orgId string) ([]string, error) {
|
|
return m.repository.GetOrgUserIds(orgId)
|
|
} |