user_subscription.go 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. package schema
  2. import (
  3. "time"
  4. "github.com/Wei-Shaw/sub2api/ent/schema/mixins"
  5. "github.com/Wei-Shaw/sub2api/internal/domain"
  6. "entgo.io/ent"
  7. "entgo.io/ent/dialect"
  8. "entgo.io/ent/dialect/entsql"
  9. "entgo.io/ent/schema"
  10. "entgo.io/ent/schema/edge"
  11. "entgo.io/ent/schema/field"
  12. "entgo.io/ent/schema/index"
  13. )
  14. // UserSubscription holds the schema definition for the UserSubscription entity.
  15. type UserSubscription struct {
  16. ent.Schema
  17. }
  18. func (UserSubscription) Annotations() []schema.Annotation {
  19. return []schema.Annotation{
  20. entsql.Annotation{Table: "user_subscriptions"},
  21. }
  22. }
  23. func (UserSubscription) Mixin() []ent.Mixin {
  24. return []ent.Mixin{
  25. mixins.TimeMixin{},
  26. mixins.SoftDeleteMixin{},
  27. }
  28. }
  29. func (UserSubscription) Fields() []ent.Field {
  30. return []ent.Field{
  31. field.Int64("user_id"),
  32. field.Int64("group_id"),
  33. field.Time("starts_at").
  34. SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
  35. field.Time("expires_at").
  36. SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
  37. field.String("status").
  38. MaxLen(20).
  39. Default(domain.SubscriptionStatusActive),
  40. field.Time("daily_window_start").
  41. Optional().
  42. Nillable().
  43. SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
  44. field.Time("weekly_window_start").
  45. Optional().
  46. Nillable().
  47. SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
  48. field.Time("monthly_window_start").
  49. Optional().
  50. Nillable().
  51. SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
  52. field.Float("daily_usage_usd").
  53. SchemaType(map[string]string{dialect.Postgres: "decimal(20,10)"}).
  54. Default(0),
  55. field.Float("weekly_usage_usd").
  56. SchemaType(map[string]string{dialect.Postgres: "decimal(20,10)"}).
  57. Default(0),
  58. field.Float("monthly_usage_usd").
  59. SchemaType(map[string]string{dialect.Postgres: "decimal(20,10)"}).
  60. Default(0),
  61. field.Int64("assigned_by").
  62. Optional().
  63. Nillable(),
  64. field.Time("assigned_at").
  65. Default(time.Now).
  66. SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
  67. field.String("notes").
  68. Optional().
  69. Nillable().
  70. SchemaType(map[string]string{dialect.Postgres: "text"}),
  71. }
  72. }
  73. func (UserSubscription) Edges() []ent.Edge {
  74. return []ent.Edge{
  75. edge.From("user", User.Type).
  76. Ref("subscriptions").
  77. Field("user_id").
  78. Unique().
  79. Required(),
  80. edge.From("group", Group.Type).
  81. Ref("subscriptions").
  82. Field("group_id").
  83. Unique().
  84. Required(),
  85. edge.From("assigned_by_user", User.Type).
  86. Ref("assigned_subscriptions").
  87. Field("assigned_by").
  88. Unique(),
  89. edge.To("usage_logs", UsageLog.Type),
  90. }
  91. }
  92. func (UserSubscription) Indexes() []ent.Index {
  93. return []ent.Index{
  94. index.Fields("user_id"),
  95. index.Fields("group_id"),
  96. index.Fields("status"),
  97. index.Fields("expires_at"),
  98. // 活跃订阅查询复合索引(线上由 SQL 迁移创建部分索引,schema 仅用于模型可读性对齐)
  99. index.Fields("user_id", "status", "expires_at"),
  100. index.Fields("assigned_by"),
  101. // 唯一约束通过部分索引实现(WHERE deleted_at IS NULL),支持软删除后重新订阅
  102. // 见迁移文件 016_soft_delete_partial_unique_indexes.sql
  103. index.Fields("user_id", "group_id"),
  104. index.Fields("deleted_at"),
  105. }
  106. }