initial commit

This commit is contained in:
m.zare
2026-04-10 18:25:21 +03:30
commit 77ca6c34a3
263 changed files with 34470 additions and 0 deletions

View File

@@ -0,0 +1,60 @@
# Asset relations: Report and Comment
## Overview
Reports and comments are **child relations** of an asset. They are stored in separate tables (`asset_reports`, `asset_comments`) and are always loaded/saved **through the asset** (no standalone Report or Comment repository in this layer).
---
## Comment relation
### Storage
- **Table:** `asset_comments`
- **Columns:** `id`, `asset_id`, `content`, `writer_id`, `writer_type`, `parent_id`, `created_at`, `updated_at`, `deleted_at`
- **Parent link:** `asset_id``assets.id`
### What happens
| Operation | Behavior |
|-----------|----------|
| **Create asset** | If `asset.Comments` is non-empty, each comment is inserted with `asset_id = new_asset_id`. IDs/timestamps come from DB. |
| **FindByID / FindByProfileID** | All rows in `asset_comments` with `asset_id = asset.id` are loaded and mapped to `asset.Comments` (flat list). |
| **Update asset** | All existing comments for that asset are **deleted**, then `asset.Comments` is re-inserted (replace strategy). |
| **Delete asset** | All comments for that asset are deleted in the same transaction before the asset row is deleted. |
### Domain vs persistence
- **Domain:** `Comment` has `Replies []Comment` (nested).
- **Persistence:** Stored as rows in `asset_comments` with `parent_id` for replies.
- **When loading:** A flat list is read from DB, then `buildCommentTree()` turns it into a tree: top-level comments have `Replies` populated.
- **When saving:** `flattenComments()` turns the tree into a flat list (parent then its replies); all rows are persisted. Note: when **creating** a new asset with new nested comments, reply `ParentID` in the domain may be zero (parent not yet saved); the current single-batch insert does not resolve parent IDs, so nested replies on create may end up with `parent_id = NULL`. For **updates** after load, parent IDs exist and nested replies persist correctly.
---
## Report relation
### Storage
- **Table:** `asset_reports`
- **Columns:** `id`, `asset_id`, `reported_by` (JSONB), `reported_at`, `reason` (JSONB), `status`, `notes`, `attachments` (JSONB), `created_at`, `updated_at`, `deleted_at`
- **Parent link:** `asset_id``assets.id`
- **Nested data:** `ReportedBy`, `ReportReason`, and `Attachments` are stored as JSON in the same row.
### What happens
| Operation | Behavior |
|-----------|----------|
| **Create asset** | If `asset.Reports` is non-empty, each report is inserted: `ReportedBy`, `Reason`, and `Attachments` are JSON-encoded into the report row. |
| **FindByID / FindByProfileID** | All rows in `asset_reports` with `asset_id = asset.id` are loaded; JSONB columns are decoded into `Report.ReportedBy`, `Report.Reason`, `Report.Attachments`. |
| **Update asset** | All existing reports for that asset are **deleted**, then `asset.Reports` is re-inserted (replace strategy). |
| **Delete asset** | All reports for that asset are deleted in the same transaction before the asset row is deleted. |
### Domain vs persistence
- **ReportedBy**, **ReportReason**, **Attachments** are fully round-tripped via JSON; no separate tables.
- Report **ID** and **ReportedAt** are set when loading from DB; on create, IDs/timestamps come from DB.
---
## Summary
- **Comment:** Stored and loaded as a **flat** list per asset; `parent_id` is persisted but **Replies** are not built when loading and nested replies are not written when saving.
- **Report:** Stored and loaded as a list per asset; nested structures (ReportedBy, Reason, Attachments) are stored as JSONB and fully restored on load.
- Both relations use a **replace-on-update** strategy: updating an asset deletes all its comments and reports and re-inserts from `asset.Comments` and `asset.Reports`.

View File

@@ -0,0 +1,297 @@
package asset
import (
"context"
"errors"
"github.com/google/uuid"
"go.uber.org/fx"
"gorm.io/gorm"
domainAsset "base/internal/domain/asset"
)
type assetRepository struct {
db *gorm.DB
}
func NewAssetRepository(lc fx.Lifecycle, db *gorm.DB) domainAsset.AssetRepository {
lc.Append(
fx.Hook{
OnStart: func(ctx context.Context) error {
return nil
},
OnStop: func(ctx context.Context) error {
return nil
},
})
return &assetRepository{db: db}
}
func (r *assetRepository) loadRelatedData(ctx context.Context, assetID, categoryID uuid.UUID) (*domainAsset.Category, []domainAsset.Artifact, []domainAsset.Comment, []domainAsset.Report, error) {
var category *domainAsset.Category
if categoryID != uuid.Nil {
var catModel CategoryModel
if err := r.db.WithContext(ctx).Where("id = ?", categoryID).First(&catModel).Error; err == nil {
category = toCategoryDomain(&catModel)
}
}
var artifactModels []ArtifactModel
if err := r.db.WithContext(ctx).Where("asset_id = ?", assetID).Find(&artifactModels).Error; err != nil {
return nil, nil, nil, nil, err
}
artifacts := toArtifactDomains(artifactModels)
var commentModels []CommentModel
if err := r.db.WithContext(ctx).Where("asset_id = ?", assetID).Find(&commentModels).Error; err != nil {
return nil, nil, nil, nil, err
}
comments := toCommentDomains(commentModels)
var reportModels []ReportModel
if err := r.db.WithContext(ctx).Where("asset_id = ?", assetID).Find(&reportModels).Error; err != nil {
return nil, nil, nil, nil, err
}
reports, err := toReportDomains(reportModels)
if err != nil {
return nil, nil, nil, nil, err
}
return category, artifacts, comments, reports, nil
}
func (r *assetRepository) Create(ctx context.Context, asset *domainAsset.Asset) error {
model := toAssetModel(asset)
tx := r.db.WithContext(ctx).Begin()
if tx.Error != nil {
return tx.Error
}
defer tx.Rollback()
if err := tx.Create(model).Error; err != nil {
return err
}
if len(asset.AssetArtifacts) > 0 {
artifactModels := toArtifactModels(model.ID, asset.AssetArtifacts)
if err := tx.Create(&artifactModels).Error; err != nil {
return err
}
}
if len(asset.Comments) > 0 {
commentModels := toCommentModels(model.ID, asset.Comments)
if err := tx.Create(&commentModels).Error; err != nil {
return err
}
}
if len(asset.Reports) > 0 {
reportModels, err := toReportModels(model.ID, asset.Reports)
if err != nil {
return err
}
if err := tx.Create(&reportModels).Error; err != nil {
return err
}
}
if err := tx.Commit().Error; err != nil {
return err
}
copyAssetFromModel(asset, model)
return nil
}
func (r *assetRepository) FindByID(ctx context.Context, id uuid.UUID) (*domainAsset.Asset, error) {
var model Model
if err := r.db.WithContext(ctx).Where("id = ?", id).First(&model).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errors.New("asset not found")
}
return nil, err
}
category, artifacts, comments, reports, err := r.loadRelatedData(ctx, model.ID, model.AssetCategoryID)
if err != nil {
return nil, err
}
return toAssetDomain(&model, category, artifacts, comments, reports), nil
}
func (r *assetRepository) Update(ctx context.Context, asset *domainAsset.Asset) error {
model := toAssetModel(asset)
tx := r.db.WithContext(ctx).Begin()
if tx.Error != nil {
return tx.Error
}
defer tx.Rollback()
if err := tx.Model(&Model{}).Where("id = ?", asset.ID).Updates(model).Error; err != nil {
return err
}
if err := tx.Where("asset_id = ?", asset.ID).Delete(&ArtifactModel{}).Error; err != nil {
return err
}
if err := tx.Where("asset_id = ?", asset.ID).Delete(&CommentModel{}).Error; err != nil {
return err
}
if err := tx.Where("asset_id = ?", asset.ID).Delete(&ReportModel{}).Error; err != nil {
return err
}
if len(asset.AssetArtifacts) > 0 {
artifactModels := toArtifactModels(asset.ID, asset.AssetArtifacts)
if err := tx.Create(&artifactModels).Error; err != nil {
return err
}
}
if len(asset.Comments) > 0 {
commentModels := toCommentModels(asset.ID, asset.Comments)
if err := tx.Create(&commentModels).Error; err != nil {
return err
}
}
if len(asset.Reports) > 0 {
reportModels, err := toReportModels(asset.ID, asset.Reports)
if err != nil {
return err
}
if err := tx.Create(&reportModels).Error; err != nil {
return err
}
}
return tx.Commit().Error
}
func (r *assetRepository) Delete(ctx context.Context, asset *domainAsset.Asset) error {
tx := r.db.WithContext(ctx).Begin()
if tx.Error != nil {
return tx.Error
}
defer tx.Rollback()
if err := tx.Where("asset_id = ?", asset.ID).Delete(&ArtifactModel{}).Error; err != nil {
return err
}
if err := tx.Where("asset_id = ?", asset.ID).Delete(&CommentModel{}).Error; err != nil {
return err
}
if err := tx.Where("asset_id = ?", asset.ID).Delete(&ReportModel{}).Error; err != nil {
return err
}
if err := tx.Delete(&Model{}, "id = ?", asset.ID).Error; err != nil {
return err
}
return tx.Commit().Error
}
func (r *assetRepository) FindLatestByCategory(ctx context.Context, categoryID uuid.UUID, limit int) ([]*domainAsset.Asset, error) {
return r.FindLatestByCategoryPaginated(ctx, categoryID, limit, 0)
}
func (r *assetRepository) FindLatestByCategoryPaginated(ctx context.Context, categoryID uuid.UUID, limit, offset int) ([]*domainAsset.Asset, error) {
var models []Model
q := r.db.WithContext(ctx).Where("asset_category_id = ?", categoryID).Order("created_at DESC")
if limit > 0 {
q = q.Limit(limit)
}
if offset > 0 {
q = q.Offset(offset)
}
if err := q.Find(&models).Error; err != nil {
return nil, err
}
if len(models) == 0 {
return nil, nil
}
out := make([]*domainAsset.Asset, len(models))
for i, model := range models {
category, artifacts, comments, reports, err := r.loadRelatedData(ctx, model.ID, model.AssetCategoryID)
if err != nil {
return nil, err
}
out[i] = toAssetDomain(&model, category, artifacts, comments, reports)
}
return out, nil
}
func (r *assetRepository) CountByCategory(ctx context.Context, categoryID uuid.UUID) (int, error) {
var count int64
if err := r.db.WithContext(ctx).Model(&Model{}).Where("asset_category_id = ?", categoryID).Count(&count).Error; err != nil {
return 0, err
}
return int(count), nil
}
func (r *assetRepository) FindLatest(ctx context.Context, limit, offset int) ([]*domainAsset.Asset, error) {
var models []Model
q := r.db.WithContext(ctx).Order("created_at DESC")
if limit > 0 {
q = q.Limit(limit)
}
if offset > 0 {
q = q.Offset(offset)
}
if err := q.Find(&models).Error; err != nil {
return nil, err
}
if len(models) == 0 {
return nil, nil
}
out := make([]*domainAsset.Asset, len(models))
for i, model := range models {
category, artifacts, comments, reports, err := r.loadRelatedData(ctx, model.ID, model.AssetCategoryID)
if err != nil {
return nil, err
}
out[i] = toAssetDomain(&model, category, artifacts, comments, reports)
}
return out, nil
}
func (r *assetRepository) FindByProfileID(ctx context.Context, profileID uuid.UUID) ([]*domainAsset.Asset, error) {
var models []Model
if err := r.db.WithContext(ctx).Where("profile_id = ?", profileID).Order("created_at DESC").Find(&models).Error; err != nil {
return nil, err
}
if len(models) == 0 {
return nil, nil
}
out := make([]*domainAsset.Asset, len(models))
for i, model := range models {
category, artifacts, comments, reports, err := r.loadRelatedData(ctx, model.ID, model.AssetCategoryID)
if err != nil {
return nil, err
}
out[i] = toAssetDomain(&model, category, artifacts, comments, reports)
}
return out, nil
}
func (r *assetRepository) Count(ctx context.Context) (int, error) {
var count int64
if err := r.db.WithContext(ctx).Model(&Model{}).Count(&count).Error; err != nil {
return 0, err
}
return int(count), nil
}

View File

@@ -0,0 +1,90 @@
package asset
import (
"context"
"errors"
"time"
"github.com/google/uuid"
"go.uber.org/fx"
"gorm.io/gorm"
domainAsset "base/internal/domain/asset"
)
type categoryRepository struct {
db *gorm.DB
}
func NewCategoryRepository(lc fx.Lifecycle, db *gorm.DB) domainAsset.CategoryRepository {
lc.Append(
fx.Hook{
OnStart: func(ctx context.Context) error {
return nil
},
OnStop: func(ctx context.Context) error {
return nil
},
})
return &categoryRepository{db: db}
}
func (r *categoryRepository) Create(ctx context.Context, category *domainAsset.Category) error {
model := toCategoryModel(category)
now := time.Now()
model.CreatedAt = now
model.UpdatedAt = now
if err := r.db.WithContext(ctx).Create(model).Error; err != nil {
return err
}
category.ID = model.ID
return nil
}
func (r *categoryRepository) FindByID(ctx context.Context, id uuid.UUID) (*domainAsset.Category, error) {
var model CategoryModel
if err := r.db.WithContext(ctx).Where("id = ?", id).First(&model).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errors.New("category not found")
}
return nil, err
}
return toCategoryDomain(&model), nil
}
func (r *categoryRepository) Update(ctx context.Context, category *domainAsset.Category) error {
model := toCategoryModel(category)
model.UpdatedAt = time.Now()
return r.db.WithContext(ctx).Model(&CategoryModel{}).Where("id = ?", category.ID).Updates(model).Error
}
func (r *categoryRepository) Delete(ctx context.Context, id uuid.UUID) error {
return r.db.WithContext(ctx).Delete(&CategoryModel{}, "id = ?", id).Error
}
func (r *categoryRepository) FindAll(ctx context.Context) ([]*domainAsset.Category, error) {
var models []CategoryModel
if err := r.db.WithContext(ctx).Order("name ASC").Find(&models).Error; err != nil {
return nil, err
}
out := make([]*domainAsset.Category, len(models))
for i := range models {
out[i] = toCategoryDomain(&models[i])
}
return out, nil
}
func (r *categoryRepository) FindByIDs(ctx context.Context, ids []uuid.UUID) ([]*domainAsset.Category, error) {
if len(ids) == 0 {
return nil, nil
}
var models []CategoryModel
if err := r.db.WithContext(ctx).Where("id IN ?", ids).Order("name ASC").Find(&models).Error; err != nil {
return nil, err
}
out := make([]*domainAsset.Category, len(models))
for i := range models {
out[i] = toCategoryDomain(&models[i])
}
return out, nil
}

View File

@@ -0,0 +1,249 @@
package asset
import (
"encoding/json"
"github.com/google/uuid"
domainAsset "base/internal/domain/asset"
)
func toCategoryModel(category *domainAsset.Category) *CategoryModel {
return &CategoryModel{
ID: category.ID,
Name: category.Name,
Icon: category.Icon,
Color: category.Color,
CardType: category.CardType,
Featured: category.Featured,
Description: category.Description,
}
}
func toCategoryDomain(model *CategoryModel) *domainAsset.Category {
return &domainAsset.Category{
ID: model.ID,
Name: model.Name,
Icon: model.Icon,
Color: model.Color,
CardType: model.CardType,
Featured: model.Featured,
Description: model.Description,
}
}
func toAssetModel(asset *domainAsset.Asset) *Model {
return &Model{
ID: asset.ID,
ProfileID: asset.ProfileID,
Status: int(asset.Status),
AssetCategoryID: asset.AssetCategoryID,
Title: asset.Title,
Description: asset.Description,
Link: asset.Link,
Analytics: asset.Analytics,
CreatedAt: asset.CreatedAt,
UpdatedAt: asset.UpdatedAt,
}
}
func toAssetDomain(model *Model, category *domainAsset.Category, artifacts []domainAsset.Artifact, comments []domainAsset.Comment, reports []domainAsset.Report) *domainAsset.Asset {
cat := domainAsset.Category{}
if category != nil {
cat = *category
}
return &domainAsset.Asset{
ID: model.ID,
ProfileID: model.ProfileID,
Status: domainAsset.Status(model.Status),
AssetCategoryID: model.AssetCategoryID,
AssetCategory: cat,
Title: model.Title,
Description: model.Description,
Link: model.Link,
Analytics: model.Analytics,
Reports: reports,
AssetArtifacts: artifacts,
Comments: comments,
CreatedAt: model.CreatedAt,
UpdatedAt: model.UpdatedAt,
}
}
func copyAssetFromModel(asset *domainAsset.Asset, model *Model) {
asset.ID = model.ID
asset.CreatedAt = model.CreatedAt
asset.UpdatedAt = model.UpdatedAt
}
func toArtifactModels(assetID uuid.UUID, artifacts []domainAsset.Artifact) []ArtifactModel {
models := make([]ArtifactModel, len(artifacts))
for i, a := range artifacts {
models[i] = ArtifactModel{
AssetID: assetID,
Type: a.Type,
DownloadURL: a.DownloadURL,
Price: a.Price,
Title: a.Title,
Description: a.Description,
}
}
return models
}
func toArtifactDomains(models []ArtifactModel) []domainAsset.Artifact {
out := make([]domainAsset.Artifact, len(models))
for i, m := range models {
out[i] = domainAsset.Artifact{
ID: m.ID,
AssetID: m.AssetID,
Type: m.Type,
DownloadURL: m.DownloadURL,
Price: m.Price,
Title: m.Title,
Description: m.Description,
}
}
return out
}
// flattenComments turns a tree of comments (with Replies) into a single slice:
// top-level first, then each comment's replies recursively. Used when saving.
func flattenComments(comments []domainAsset.Comment) []domainAsset.Comment {
var out []domainAsset.Comment
for _, c := range comments {
out = append(out, c)
out = append(out, flattenComments(c.Replies)...)
}
return out
}
func toCommentModels(assetID uuid.UUID, comments []domainAsset.Comment) []CommentModel {
flat := flattenComments(comments)
models := make([]CommentModel, 0, len(flat))
for _, c := range flat {
models = append(models, CommentModel{
AssetID: assetID,
Content: c.Content,
WriterID: c.WriterID,
WriterType: c.WriterType,
ParentID: c.ParentID,
CreatedAt: c.CreatedAt,
UpdatedAt: c.UpdatedAt,
})
}
return models
}
func toCommentDomains(models []CommentModel) []domainAsset.Comment {
out := make([]domainAsset.Comment, len(models))
for i, m := range models {
out[i] = domainAsset.Comment{
ID: m.ID,
AssetID: m.AssetID,
Content: m.Content,
WriterID: m.WriterID,
WriterType: m.WriterType,
ParentID: m.ParentID,
CreatedAt: m.CreatedAt,
UpdatedAt: m.UpdatedAt,
}
}
return buildCommentTree(out)
}
// buildCommentTree turns a flat list of comments (with ParentID set) into a tree:
// top-level comments have Replies populated; nested Replies are not further nested in this type.
func buildCommentTree(flat []domainAsset.Comment) []domainAsset.Comment {
if len(flat) == 0 {
return nil
}
byID := make(map[uuid.UUID]*domainAsset.Comment)
for i := range flat {
flat[i].Replies = nil
byID[flat[i].ID] = &flat[i]
}
// First pass: attach replies to parents
for i := range flat {
c := &flat[i]
if c.ParentID == nil {
continue
}
if parent, ok := byID[*c.ParentID]; ok {
parent.Replies = append(parent.Replies, *c)
}
}
// Second pass: collect top-level comments (with Replies already populated)
var roots []domainAsset.Comment
for i := range flat {
c := &flat[i]
if c.ParentID == nil {
roots = append(roots, *c)
}
}
return roots
}
func toReportModels(assetID uuid.UUID, reports []domainAsset.Report) ([]ReportModel, error) {
models := make([]ReportModel, len(reports))
for i, r := range reports {
reportedBy, err := json.Marshal(r.ReportedBy)
if err != nil {
return nil, err
}
reason, err := json.Marshal(r.Reason)
if err != nil {
return nil, err
}
var attachments json.RawMessage
if len(r.Attachments) > 0 {
attachments, err = json.Marshal(r.Attachments)
if err != nil {
return nil, err
}
}
models[i] = ReportModel{
AssetID: assetID,
ReportedBy: reportedBy,
ReportedAt: r.ReportedAt,
Reason: reason,
Status: int(r.Status),
Notes: r.Notes,
Attachments: attachments,
CreatedAt: r.ReportedAt,
UpdatedAt: r.ReportedAt,
}
}
return models, nil
}
func toReportDomains(models []ReportModel) ([]domainAsset.Report, error) {
out := make([]domainAsset.Report, len(models))
for i, m := range models {
var reportedBy domainAsset.ReportedBy
if err := json.Unmarshal(m.ReportedBy, &reportedBy); err != nil {
return nil, err
}
var reason domainAsset.ReportReason
if err := json.Unmarshal(m.Reason, &reason); err != nil {
return nil, err
}
var attachments []domainAsset.Attachment
if len(m.Attachments) > 0 {
if err := json.Unmarshal(m.Attachments, &attachments); err != nil {
return nil, err
}
}
out[i] = domainAsset.Report{
ID: m.ID,
AssetID: m.AssetID,
ReportedBy: reportedBy,
ReportedAt: m.ReportedAt,
Reason: reason,
Status: domainAsset.ReportStatus(m.Status),
Notes: m.Notes,
Attachments: attachments,
}
}
return out, nil
}

View File

@@ -0,0 +1,95 @@
package asset
import (
"encoding/json"
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
type CategoryModel struct {
ID uuid.UUID `gorm:"type:uuid;primaryKey;default:gen_random_uuid()"`
Name string `gorm:"column:name;type:text;not null"`
Icon string `gorm:"column:icon;type:text"`
Color string `gorm:"column:color;type:text"`
CardType string `gorm:"column:card_type;type:text"`
Featured bool `gorm:"column:featured;type:boolean;default:false"`
Description string `gorm:"column:description;type:text"`
CreatedAt time.Time `gorm:"column:created_at;type:timestamptz;not null"`
UpdatedAt time.Time `gorm:"column:updated_at;type:timestamptz;not null"`
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:timestamptz;index"`
}
func (CategoryModel) TableName() string {
return "asset_categories"
}
type Model struct {
ID uuid.UUID `gorm:"type:uuid;primaryKey;default:gen_random_uuid()"`
ProfileID uuid.UUID `gorm:"column:profile_id;type:uuid;not null;index:assets_profile_id_idx"`
Status int `gorm:"column:status;type:integer;not null;default:0"`
AssetCategoryID uuid.UUID `gorm:"column:asset_category_id;type:uuid;not null;index:assets_category_id_idx"`
Title string `gorm:"column:title;type:text;not null"`
Description string `gorm:"column:description;type:text"`
Link string `gorm:"column:link;type:text"`
Analytics json.RawMessage `gorm:"column:analytics;type:jsonb"`
CreatedAt time.Time `gorm:"column:created_at;type:timestamptz;not null"`
UpdatedAt time.Time `gorm:"column:updated_at;type:timestamptz;not null"`
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:timestamptz;index"`
}
func (Model) TableName() string {
return "assets"
}
type ArtifactModel struct {
ID uuid.UUID `gorm:"type:uuid;primaryKey;default:gen_random_uuid()"`
AssetID uuid.UUID `gorm:"column:asset_id;type:uuid;not null;index:asset_artifacts_asset_id_idx"`
Type string `gorm:"column:type;type:text;not null"`
DownloadURL string `gorm:"column:download_url;type:text"`
Price int `gorm:"column:price;type:integer;default:0"`
Title string `gorm:"column:title;type:text"`
Description string `gorm:"column:description;type:text"`
CreatedAt time.Time `gorm:"column:created_at;type:timestamptz;not null"`
UpdatedAt time.Time `gorm:"column:updated_at;type:timestamptz;not null"`
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:timestamptz;index"`
}
func (ArtifactModel) TableName() string {
return "asset_artifacts"
}
type CommentModel struct {
ID uuid.UUID `gorm:"type:uuid;primaryKey;default:gen_random_uuid()"`
AssetID uuid.UUID `gorm:"column:asset_id;type:uuid;not null;index:asset_comments_asset_id_idx"`
Content string `gorm:"column:content;type:text;not null"`
WriterID uuid.UUID `gorm:"column:writer_id;type:uuid;not null"`
WriterType string `gorm:"column:writer_type;type:text;not null"`
ParentID *uuid.UUID `gorm:"column:parent_id;type:uuid;index:asset_comments_parent_id_idx"`
CreatedAt time.Time `gorm:"column:created_at;type:timestamptz;not null"`
UpdatedAt time.Time `gorm:"column:updated_at;type:timestamptz;not null"`
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:timestamptz;index"`
}
func (CommentModel) TableName() string {
return "asset_comments"
}
type ReportModel struct {
ID uuid.UUID `gorm:"type:uuid;primaryKey;default:gen_random_uuid()"`
AssetID uuid.UUID `gorm:"column:asset_id;type:uuid;not null;index:asset_reports_asset_id_idx"`
ReportedBy json.RawMessage `gorm:"column:reported_by;type:jsonb;not null"`
ReportedAt time.Time `gorm:"column:reported_at;type:timestamptz;not null"`
Reason json.RawMessage `gorm:"column:reason;type:jsonb;not null"`
Status int `gorm:"column:status;type:integer;not null;default:0"`
Notes string `gorm:"column:notes;type:text"`
Attachments json.RawMessage `gorm:"column:attachments;type:jsonb"`
CreatedAt time.Time `gorm:"column:created_at;type:timestamptz;not null"`
UpdatedAt time.Time `gorm:"column:updated_at;type:timestamptz;not null"`
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:timestamptz;index"`
}
func (ReportModel) TableName() string {
return "asset_reports"
}