feat: add core data model
This commit is contained in:
10
backend/internal/db/db.go
Normal file
10
backend/internal/db/db.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func Open(databaseURL string) (*gorm.DB, error) {
|
||||
return gorm.Open(postgres.Open(databaseURL), &gorm.Config{})
|
||||
}
|
||||
138
backend/internal/domain/models.go
Normal file
138
backend/internal/domain/models.go
Normal file
@@ -0,0 +1,138 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
Email string `gorm:"uniqueIndex;not null"`
|
||||
DisplayName string `gorm:"not null"`
|
||||
PasswordHash string `gorm:"not null"`
|
||||
Role string `gorm:"not null;default:user"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
type Project struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
OwnerID uint `gorm:"index;not null"`
|
||||
Name string `gorm:"not null"`
|
||||
Description string
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
type InboxItem struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
ProjectID uint `gorm:"index;not null"`
|
||||
CreatedBy uint `gorm:"index;not null"`
|
||||
SourceType string `gorm:"not null"`
|
||||
Title string
|
||||
Body string
|
||||
Status string `gorm:"not null;default:open"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
type Task struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
ProjectID uint `gorm:"index;not null"`
|
||||
CreatedBy uint `gorm:"index;not null"`
|
||||
AssigneeID *uint `gorm:"index"`
|
||||
Title string `gorm:"not null"`
|
||||
Description string
|
||||
Status string `gorm:"not null;default:open"`
|
||||
SortOrder int `gorm:"not null;default:0"`
|
||||
DueAt *time.Time
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
type Note struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
ProjectID uint `gorm:"index;not null"`
|
||||
CreatedBy uint `gorm:"index;not null"`
|
||||
Title string `gorm:"not null"`
|
||||
Markdown string `gorm:"type:text"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
type Source struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
ProjectID uint `gorm:"index;not null"`
|
||||
CreatedBy uint `gorm:"index;not null"`
|
||||
Kind string `gorm:"not null"`
|
||||
Title string `gorm:"not null"`
|
||||
URL string
|
||||
FilePath string
|
||||
ContentText string `gorm:"type:text"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
type AISession struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
ProjectID uint `gorm:"index;not null"`
|
||||
CreatedBy uint `gorm:"index;not null"`
|
||||
Title string `gorm:"not null"`
|
||||
Context string `gorm:"type:text"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
type Tag struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
ProjectID uint `gorm:"index;not null"`
|
||||
Name string `gorm:"not null"`
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
type ProjectEvent struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
ProjectID uint `gorm:"index;not null"`
|
||||
ActorID uint `gorm:"index;not null"`
|
||||
EventType string `gorm:"not null"`
|
||||
EntityType string `gorm:"not null"`
|
||||
EntityID uint `gorm:"not null"`
|
||||
Summary string `gorm:"not null"`
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
type AIKey struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
UserID uint `gorm:"uniqueIndex;not null"`
|
||||
Provider string `gorm:"not null"`
|
||||
EncryptedAPIKey string `gorm:"not null"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
type AICallLog struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
UserID uint `gorm:"index;not null"`
|
||||
Provider string `gorm:"not null"`
|
||||
UsedKeyType string `gorm:"not null"`
|
||||
Action string `gorm:"not null"`
|
||||
Status string `gorm:"not null"`
|
||||
Error string
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
type TaskShare struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
TaskID uint `gorm:"index;not null"`
|
||||
ObjectType string `gorm:"not null"`
|
||||
ObjectID uint `gorm:"not null"`
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
func AutoMigrate(database *gorm.DB) error {
|
||||
return database.AutoMigrate(
|
||||
&User{}, &Project{}, &InboxItem{}, &Task{}, &Note{}, &Source{},
|
||||
&AISession{}, &Tag{}, &ProjectEvent{}, &AIKey{}, &AICallLog{}, &TaskShare{},
|
||||
)
|
||||
}
|
||||
23
backend/internal/domain/models_test.go
Normal file
23
backend/internal/domain/models_test.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/glebarez/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func TestAutoMigrateCreatesCoreTables(t *testing.T) {
|
||||
database, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, AutoMigrate(database))
|
||||
|
||||
for _, table := range []string{
|
||||
"users", "projects", "inbox_items", "tasks", "notes", "sources",
|
||||
"ai_sessions", "tags", "project_events", "ai_keys", "ai_call_logs", "task_shares",
|
||||
} {
|
||||
require.True(t, database.Migrator().HasTable(table), "missing table %s", table)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user