add tv detail page

This commit is contained in:
Simon Ding
2024-07-06 16:43:40 +08:00
parent 1fef91fe66
commit 198e5fd43d
19 changed files with 469 additions and 165 deletions

View File

@@ -67,7 +67,7 @@ func (c *Client) GetLanguage() string {
func (c *Client) AddWatchlist(path string, detail *tmdb.TVDetails, episodes []int) (*ent.Series, error) { func (c *Client) AddWatchlist(path string, detail *tmdb.TVDetails, episodes []int) (*ent.Series, error) {
count := c.ent.Series.Query().Where(series.TmdbID(int(detail.ID))).CountX(context.Background()) count := c.ent.Series.Query().Where(series.TmdbID(int(detail.ID))).CountX(context.Background())
if (count > 0) { if count > 0 {
return nil, fmt.Errorf("tv series %s already in watchlist", detail.Name) return nil, fmt.Errorf("tv series %s already in watchlist", detail.Name)
} }
r, err := c.ent.Series.Create(). r, err := c.ent.Series.Create().
@@ -91,6 +91,20 @@ func (c *Client) GetWatchlist() []*ent.Series {
return list return list
} }
type SeriesDetails struct {
*ent.Series
Episodes []*ent.Episode `json:"episodes"`
}
func (c *Client) GetSeriesDetails(id int) *SeriesDetails {
se := c.ent.Series.Query().Where(series.ID(id)).FirstX(context.TODO())
ep := se.QueryEpisodes().AllX(context.Background())
return &SeriesDetails{
Series: se,
Episodes: ep,
}
}
func (c *Client) SaveEposideDetail(d *ent.Episode) (int, error) { func (c *Client) SaveEposideDetail(d *ent.Episode) (int, error) {
ep, err := c.ent.Episode.Create(). ep, err := c.ent.Episode.Create().
SetAirDate(d.AirDate). SetAirDate(d.AirDate).
@@ -99,7 +113,7 @@ func (c *Client) SaveEposideDetail(d *ent.Episode) (int, error) {
SetOverview(d.Overview). SetOverview(d.Overview).
SetTitle(d.Title).Save(context.TODO()) SetTitle(d.Title).Save(context.TODO())
return ep.ID,err return ep.ID, err
} }
type TorznabSetting struct { type TorznabSetting struct {

View File

@@ -17,8 +17,10 @@ type Episode struct {
config `json:"-"` config `json:"-"`
// ID of the ent. // ID of the ent.
ID int `json:"id,omitempty"` ID int `json:"id,omitempty"`
// SeriesID holds the value of the "series_id" field.
SeriesID int `json:"series_id,omitempty"`
// SeasonNumber holds the value of the "season_number" field. // SeasonNumber holds the value of the "season_number" field.
SeasonNumber int `json:"season_number,omitempty"` SeasonNumber int `json:"season_number"`
// EpisodeNumber holds the value of the "episode_number" field. // EpisodeNumber holds the value of the "episode_number" field.
EpisodeNumber int `json:"episode_number,omitempty"` EpisodeNumber int `json:"episode_number,omitempty"`
// Title holds the value of the "title" field. // Title holds the value of the "title" field.
@@ -29,9 +31,8 @@ type Episode struct {
AirDate string `json:"air_date,omitempty"` AirDate string `json:"air_date,omitempty"`
// Edges holds the relations/edges for other nodes in the graph. // Edges holds the relations/edges for other nodes in the graph.
// The values are being populated by the EpisodeQuery when eager-loading is set. // The values are being populated by the EpisodeQuery when eager-loading is set.
Edges EpisodeEdges `json:"edges"` Edges EpisodeEdges `json:"edges"`
series_episodes *int selectValues sql.SelectValues
selectValues sql.SelectValues
} }
// EpisodeEdges holds the relations/edges for other nodes in the graph. // EpisodeEdges holds the relations/edges for other nodes in the graph.
@@ -59,12 +60,10 @@ func (*Episode) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns)) values := make([]any, len(columns))
for i := range columns { for i := range columns {
switch columns[i] { switch columns[i] {
case episode.FieldID, episode.FieldSeasonNumber, episode.FieldEpisodeNumber: case episode.FieldID, episode.FieldSeriesID, episode.FieldSeasonNumber, episode.FieldEpisodeNumber:
values[i] = new(sql.NullInt64) values[i] = new(sql.NullInt64)
case episode.FieldTitle, episode.FieldOverview, episode.FieldAirDate: case episode.FieldTitle, episode.FieldOverview, episode.FieldAirDate:
values[i] = new(sql.NullString) values[i] = new(sql.NullString)
case episode.ForeignKeys[0]: // series_episodes
values[i] = new(sql.NullInt64)
default: default:
values[i] = new(sql.UnknownType) values[i] = new(sql.UnknownType)
} }
@@ -86,6 +85,12 @@ func (e *Episode) assignValues(columns []string, values []any) error {
return fmt.Errorf("unexpected type %T for field id", value) return fmt.Errorf("unexpected type %T for field id", value)
} }
e.ID = int(value.Int64) e.ID = int(value.Int64)
case episode.FieldSeriesID:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field series_id", values[i])
} else if value.Valid {
e.SeriesID = int(value.Int64)
}
case episode.FieldSeasonNumber: case episode.FieldSeasonNumber:
if value, ok := values[i].(*sql.NullInt64); !ok { if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field season_number", values[i]) return fmt.Errorf("unexpected type %T for field season_number", values[i])
@@ -116,13 +121,6 @@ func (e *Episode) assignValues(columns []string, values []any) error {
} else if value.Valid { } else if value.Valid {
e.AirDate = value.String e.AirDate = value.String
} }
case episode.ForeignKeys[0]:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for edge-field series_episodes", value)
} else if value.Valid {
e.series_episodes = new(int)
*e.series_episodes = int(value.Int64)
}
default: default:
e.selectValues.Set(columns[i], values[i]) e.selectValues.Set(columns[i], values[i])
} }
@@ -164,6 +162,9 @@ func (e *Episode) String() string {
var builder strings.Builder var builder strings.Builder
builder.WriteString("Episode(") builder.WriteString("Episode(")
builder.WriteString(fmt.Sprintf("id=%v, ", e.ID)) builder.WriteString(fmt.Sprintf("id=%v, ", e.ID))
builder.WriteString("series_id=")
builder.WriteString(fmt.Sprintf("%v", e.SeriesID))
builder.WriteString(", ")
builder.WriteString("season_number=") builder.WriteString("season_number=")
builder.WriteString(fmt.Sprintf("%v", e.SeasonNumber)) builder.WriteString(fmt.Sprintf("%v", e.SeasonNumber))
builder.WriteString(", ") builder.WriteString(", ")

View File

@@ -12,6 +12,8 @@ const (
Label = "episode" Label = "episode"
// FieldID holds the string denoting the id field in the database. // FieldID holds the string denoting the id field in the database.
FieldID = "id" FieldID = "id"
// FieldSeriesID holds the string denoting the series_id field in the database.
FieldSeriesID = "series_id"
// FieldSeasonNumber holds the string denoting the season_number field in the database. // FieldSeasonNumber holds the string denoting the season_number field in the database.
FieldSeasonNumber = "season_number" FieldSeasonNumber = "season_number"
// FieldEpisodeNumber holds the string denoting the episode_number field in the database. // FieldEpisodeNumber holds the string denoting the episode_number field in the database.
@@ -32,12 +34,13 @@ const (
// It exists in this package in order to avoid circular dependency with the "series" package. // It exists in this package in order to avoid circular dependency with the "series" package.
SeriesInverseTable = "series" SeriesInverseTable = "series"
// SeriesColumn is the table column denoting the series relation/edge. // SeriesColumn is the table column denoting the series relation/edge.
SeriesColumn = "series_episodes" SeriesColumn = "series_id"
) )
// Columns holds all SQL columns for episode fields. // Columns holds all SQL columns for episode fields.
var Columns = []string{ var Columns = []string{
FieldID, FieldID,
FieldSeriesID,
FieldSeasonNumber, FieldSeasonNumber,
FieldEpisodeNumber, FieldEpisodeNumber,
FieldTitle, FieldTitle,
@@ -45,12 +48,6 @@ var Columns = []string{
FieldAirDate, FieldAirDate,
} }
// ForeignKeys holds the SQL foreign-keys that are owned by the "episodes"
// table and are not defined as standalone fields in the schema.
var ForeignKeys = []string{
"series_episodes",
}
// ValidColumn reports if the column name is valid (part of the table columns). // ValidColumn reports if the column name is valid (part of the table columns).
func ValidColumn(column string) bool { func ValidColumn(column string) bool {
for i := range Columns { for i := range Columns {
@@ -58,11 +55,6 @@ func ValidColumn(column string) bool {
return true return true
} }
} }
for i := range ForeignKeys {
if column == ForeignKeys[i] {
return true
}
}
return false return false
} }
@@ -74,6 +66,11 @@ func ByID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldID, opts...).ToFunc() return sql.OrderByField(FieldID, opts...).ToFunc()
} }
// BySeriesID orders the results by the series_id field.
func BySeriesID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldSeriesID, opts...).ToFunc()
}
// BySeasonNumber orders the results by the season_number field. // BySeasonNumber orders the results by the season_number field.
func BySeasonNumber(opts ...sql.OrderTermOption) OrderOption { func BySeasonNumber(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldSeasonNumber, opts...).ToFunc() return sql.OrderByField(FieldSeasonNumber, opts...).ToFunc()

View File

@@ -54,6 +54,11 @@ func IDLTE(id int) predicate.Episode {
return predicate.Episode(sql.FieldLTE(FieldID, id)) return predicate.Episode(sql.FieldLTE(FieldID, id))
} }
// SeriesID applies equality check predicate on the "series_id" field. It's identical to SeriesIDEQ.
func SeriesID(v int) predicate.Episode {
return predicate.Episode(sql.FieldEQ(FieldSeriesID, v))
}
// SeasonNumber applies equality check predicate on the "season_number" field. It's identical to SeasonNumberEQ. // SeasonNumber applies equality check predicate on the "season_number" field. It's identical to SeasonNumberEQ.
func SeasonNumber(v int) predicate.Episode { func SeasonNumber(v int) predicate.Episode {
return predicate.Episode(sql.FieldEQ(FieldSeasonNumber, v)) return predicate.Episode(sql.FieldEQ(FieldSeasonNumber, v))
@@ -79,6 +84,36 @@ func AirDate(v string) predicate.Episode {
return predicate.Episode(sql.FieldEQ(FieldAirDate, v)) return predicate.Episode(sql.FieldEQ(FieldAirDate, v))
} }
// SeriesIDEQ applies the EQ predicate on the "series_id" field.
func SeriesIDEQ(v int) predicate.Episode {
return predicate.Episode(sql.FieldEQ(FieldSeriesID, v))
}
// SeriesIDNEQ applies the NEQ predicate on the "series_id" field.
func SeriesIDNEQ(v int) predicate.Episode {
return predicate.Episode(sql.FieldNEQ(FieldSeriesID, v))
}
// SeriesIDIn applies the In predicate on the "series_id" field.
func SeriesIDIn(vs ...int) predicate.Episode {
return predicate.Episode(sql.FieldIn(FieldSeriesID, vs...))
}
// SeriesIDNotIn applies the NotIn predicate on the "series_id" field.
func SeriesIDNotIn(vs ...int) predicate.Episode {
return predicate.Episode(sql.FieldNotIn(FieldSeriesID, vs...))
}
// SeriesIDIsNil applies the IsNil predicate on the "series_id" field.
func SeriesIDIsNil() predicate.Episode {
return predicate.Episode(sql.FieldIsNull(FieldSeriesID))
}
// SeriesIDNotNil applies the NotNil predicate on the "series_id" field.
func SeriesIDNotNil() predicate.Episode {
return predicate.Episode(sql.FieldNotNull(FieldSeriesID))
}
// SeasonNumberEQ applies the EQ predicate on the "season_number" field. // SeasonNumberEQ applies the EQ predicate on the "season_number" field.
func SeasonNumberEQ(v int) predicate.Episode { func SeasonNumberEQ(v int) predicate.Episode {
return predicate.Episode(sql.FieldEQ(FieldSeasonNumber, v)) return predicate.Episode(sql.FieldEQ(FieldSeasonNumber, v))

View File

@@ -20,6 +20,20 @@ type EpisodeCreate struct {
hooks []Hook hooks []Hook
} }
// SetSeriesID sets the "series_id" field.
func (ec *EpisodeCreate) SetSeriesID(i int) *EpisodeCreate {
ec.mutation.SetSeriesID(i)
return ec
}
// SetNillableSeriesID sets the "series_id" field if the given value is not nil.
func (ec *EpisodeCreate) SetNillableSeriesID(i *int) *EpisodeCreate {
if i != nil {
ec.SetSeriesID(*i)
}
return ec
}
// SetSeasonNumber sets the "season_number" field. // SetSeasonNumber sets the "season_number" field.
func (ec *EpisodeCreate) SetSeasonNumber(i int) *EpisodeCreate { func (ec *EpisodeCreate) SetSeasonNumber(i int) *EpisodeCreate {
ec.mutation.SetSeasonNumber(i) ec.mutation.SetSeasonNumber(i)
@@ -50,20 +64,6 @@ func (ec *EpisodeCreate) SetAirDate(s string) *EpisodeCreate {
return ec return ec
} }
// SetSeriesID sets the "series" edge to the Series entity by ID.
func (ec *EpisodeCreate) SetSeriesID(id int) *EpisodeCreate {
ec.mutation.SetSeriesID(id)
return ec
}
// SetNillableSeriesID sets the "series" edge to the Series entity by ID if the given value is not nil.
func (ec *EpisodeCreate) SetNillableSeriesID(id *int) *EpisodeCreate {
if id != nil {
ec = ec.SetSeriesID(*id)
}
return ec
}
// SetSeries sets the "series" edge to the Series entity. // SetSeries sets the "series" edge to the Series entity.
func (ec *EpisodeCreate) SetSeries(s *Series) *EpisodeCreate { func (ec *EpisodeCreate) SetSeries(s *Series) *EpisodeCreate {
return ec.SetSeriesID(s.ID) return ec.SetSeriesID(s.ID)
@@ -178,7 +178,7 @@ func (ec *EpisodeCreate) createSpec() (*Episode, *sqlgraph.CreateSpec) {
for _, k := range nodes { for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k) edge.Target.Nodes = append(edge.Target.Nodes, k)
} }
_node.series_episodes = &nodes[0] _node.SeriesID = nodes[0]
_spec.Edges = append(_spec.Edges, edge) _spec.Edges = append(_spec.Edges, edge)
} }
return _node, _spec return _node, _spec

View File

@@ -23,7 +23,6 @@ type EpisodeQuery struct {
inters []Interceptor inters []Interceptor
predicates []predicate.Episode predicates []predicate.Episode
withSeries *SeriesQuery withSeries *SeriesQuery
withFKs bool
// intermediate query (i.e. traversal path). // intermediate query (i.e. traversal path).
sql *sql.Selector sql *sql.Selector
path func(context.Context) (*sql.Selector, error) path func(context.Context) (*sql.Selector, error)
@@ -298,12 +297,12 @@ func (eq *EpisodeQuery) WithSeries(opts ...func(*SeriesQuery)) *EpisodeQuery {
// Example: // Example:
// //
// var v []struct { // var v []struct {
// SeasonNumber int `json:"season_number,omitempty"` // SeriesID int `json:"series_id,omitempty"`
// Count int `json:"count,omitempty"` // Count int `json:"count,omitempty"`
// } // }
// //
// client.Episode.Query(). // client.Episode.Query().
// GroupBy(episode.FieldSeasonNumber). // GroupBy(episode.FieldSeriesID).
// Aggregate(ent.Count()). // Aggregate(ent.Count()).
// Scan(ctx, &v) // Scan(ctx, &v)
func (eq *EpisodeQuery) GroupBy(field string, fields ...string) *EpisodeGroupBy { func (eq *EpisodeQuery) GroupBy(field string, fields ...string) *EpisodeGroupBy {
@@ -321,11 +320,11 @@ func (eq *EpisodeQuery) GroupBy(field string, fields ...string) *EpisodeGroupBy
// Example: // Example:
// //
// var v []struct { // var v []struct {
// SeasonNumber int `json:"season_number,omitempty"` // SeriesID int `json:"series_id,omitempty"`
// } // }
// //
// client.Episode.Query(). // client.Episode.Query().
// Select(episode.FieldSeasonNumber). // Select(episode.FieldSeriesID).
// Scan(ctx, &v) // Scan(ctx, &v)
func (eq *EpisodeQuery) Select(fields ...string) *EpisodeSelect { func (eq *EpisodeQuery) Select(fields ...string) *EpisodeSelect {
eq.ctx.Fields = append(eq.ctx.Fields, fields...) eq.ctx.Fields = append(eq.ctx.Fields, fields...)
@@ -369,18 +368,11 @@ func (eq *EpisodeQuery) prepareQuery(ctx context.Context) error {
func (eq *EpisodeQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Episode, error) { func (eq *EpisodeQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Episode, error) {
var ( var (
nodes = []*Episode{} nodes = []*Episode{}
withFKs = eq.withFKs
_spec = eq.querySpec() _spec = eq.querySpec()
loadedTypes = [1]bool{ loadedTypes = [1]bool{
eq.withSeries != nil, eq.withSeries != nil,
} }
) )
if eq.withSeries != nil {
withFKs = true
}
if withFKs {
_spec.Node.Columns = append(_spec.Node.Columns, episode.ForeignKeys...)
}
_spec.ScanValues = func(columns []string) ([]any, error) { _spec.ScanValues = func(columns []string) ([]any, error) {
return (*Episode).scanValues(nil, columns) return (*Episode).scanValues(nil, columns)
} }
@@ -412,10 +404,7 @@ func (eq *EpisodeQuery) loadSeries(ctx context.Context, query *SeriesQuery, node
ids := make([]int, 0, len(nodes)) ids := make([]int, 0, len(nodes))
nodeids := make(map[int][]*Episode) nodeids := make(map[int][]*Episode)
for i := range nodes { for i := range nodes {
if nodes[i].series_episodes == nil { fk := nodes[i].SeriesID
continue
}
fk := *nodes[i].series_episodes
if _, ok := nodeids[fk]; !ok { if _, ok := nodeids[fk]; !ok {
ids = append(ids, fk) ids = append(ids, fk)
} }
@@ -432,7 +421,7 @@ func (eq *EpisodeQuery) loadSeries(ctx context.Context, query *SeriesQuery, node
for _, n := range neighbors { for _, n := range neighbors {
nodes, ok := nodeids[n.ID] nodes, ok := nodeids[n.ID]
if !ok { if !ok {
return fmt.Errorf(`unexpected foreign-key "series_episodes" returned %v`, n.ID) return fmt.Errorf(`unexpected foreign-key "series_id" returned %v`, n.ID)
} }
for i := range nodes { for i := range nodes {
assign(nodes[i], n) assign(nodes[i], n)
@@ -466,6 +455,9 @@ func (eq *EpisodeQuery) querySpec() *sqlgraph.QuerySpec {
_spec.Node.Columns = append(_spec.Node.Columns, fields[i]) _spec.Node.Columns = append(_spec.Node.Columns, fields[i])
} }
} }
if eq.withSeries != nil {
_spec.Node.AddColumnOnce(episode.FieldSeriesID)
}
} }
if ps := eq.predicates; len(ps) > 0 { if ps := eq.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) { _spec.Predicate = func(selector *sql.Selector) {

View File

@@ -28,6 +28,26 @@ func (eu *EpisodeUpdate) Where(ps ...predicate.Episode) *EpisodeUpdate {
return eu return eu
} }
// SetSeriesID sets the "series_id" field.
func (eu *EpisodeUpdate) SetSeriesID(i int) *EpisodeUpdate {
eu.mutation.SetSeriesID(i)
return eu
}
// SetNillableSeriesID sets the "series_id" field if the given value is not nil.
func (eu *EpisodeUpdate) SetNillableSeriesID(i *int) *EpisodeUpdate {
if i != nil {
eu.SetSeriesID(*i)
}
return eu
}
// ClearSeriesID clears the value of the "series_id" field.
func (eu *EpisodeUpdate) ClearSeriesID() *EpisodeUpdate {
eu.mutation.ClearSeriesID()
return eu
}
// SetSeasonNumber sets the "season_number" field. // SetSeasonNumber sets the "season_number" field.
func (eu *EpisodeUpdate) SetSeasonNumber(i int) *EpisodeUpdate { func (eu *EpisodeUpdate) SetSeasonNumber(i int) *EpisodeUpdate {
eu.mutation.ResetSeasonNumber() eu.mutation.ResetSeasonNumber()
@@ -112,20 +132,6 @@ func (eu *EpisodeUpdate) SetNillableAirDate(s *string) *EpisodeUpdate {
return eu return eu
} }
// SetSeriesID sets the "series" edge to the Series entity by ID.
func (eu *EpisodeUpdate) SetSeriesID(id int) *EpisodeUpdate {
eu.mutation.SetSeriesID(id)
return eu
}
// SetNillableSeriesID sets the "series" edge to the Series entity by ID if the given value is not nil.
func (eu *EpisodeUpdate) SetNillableSeriesID(id *int) *EpisodeUpdate {
if id != nil {
eu = eu.SetSeriesID(*id)
}
return eu
}
// SetSeries sets the "series" edge to the Series entity. // SetSeries sets the "series" edge to the Series entity.
func (eu *EpisodeUpdate) SetSeries(s *Series) *EpisodeUpdate { func (eu *EpisodeUpdate) SetSeries(s *Series) *EpisodeUpdate {
return eu.SetSeriesID(s.ID) return eu.SetSeriesID(s.ID)
@@ -248,6 +254,26 @@ type EpisodeUpdateOne struct {
mutation *EpisodeMutation mutation *EpisodeMutation
} }
// SetSeriesID sets the "series_id" field.
func (euo *EpisodeUpdateOne) SetSeriesID(i int) *EpisodeUpdateOne {
euo.mutation.SetSeriesID(i)
return euo
}
// SetNillableSeriesID sets the "series_id" field if the given value is not nil.
func (euo *EpisodeUpdateOne) SetNillableSeriesID(i *int) *EpisodeUpdateOne {
if i != nil {
euo.SetSeriesID(*i)
}
return euo
}
// ClearSeriesID clears the value of the "series_id" field.
func (euo *EpisodeUpdateOne) ClearSeriesID() *EpisodeUpdateOne {
euo.mutation.ClearSeriesID()
return euo
}
// SetSeasonNumber sets the "season_number" field. // SetSeasonNumber sets the "season_number" field.
func (euo *EpisodeUpdateOne) SetSeasonNumber(i int) *EpisodeUpdateOne { func (euo *EpisodeUpdateOne) SetSeasonNumber(i int) *EpisodeUpdateOne {
euo.mutation.ResetSeasonNumber() euo.mutation.ResetSeasonNumber()
@@ -332,20 +358,6 @@ func (euo *EpisodeUpdateOne) SetNillableAirDate(s *string) *EpisodeUpdateOne {
return euo return euo
} }
// SetSeriesID sets the "series" edge to the Series entity by ID.
func (euo *EpisodeUpdateOne) SetSeriesID(id int) *EpisodeUpdateOne {
euo.mutation.SetSeriesID(id)
return euo
}
// SetNillableSeriesID sets the "series" edge to the Series entity by ID if the given value is not nil.
func (euo *EpisodeUpdateOne) SetNillableSeriesID(id *int) *EpisodeUpdateOne {
if id != nil {
euo = euo.SetSeriesID(*id)
}
return euo
}
// SetSeries sets the "series" edge to the Series entity. // SetSeries sets the "series" edge to the Series entity.
func (euo *EpisodeUpdateOne) SetSeries(s *Series) *EpisodeUpdateOne { func (euo *EpisodeUpdateOne) SetSeries(s *Series) *EpisodeUpdateOne {
return euo.SetSeriesID(s.ID) return euo.SetSeriesID(s.ID)

View File

@@ -37,7 +37,7 @@ var (
{Name: "title", Type: field.TypeString}, {Name: "title", Type: field.TypeString},
{Name: "overview", Type: field.TypeString}, {Name: "overview", Type: field.TypeString},
{Name: "air_date", Type: field.TypeString}, {Name: "air_date", Type: field.TypeString},
{Name: "series_episodes", Type: field.TypeInt, Nullable: true}, {Name: "series_id", Type: field.TypeInt, Nullable: true},
} }
// EpisodesTable holds the schema information for the "episodes" table. // EpisodesTable holds the schema information for the "episodes" table.
EpisodesTable = &schema.Table{ EpisodesTable = &schema.Table{

View File

@@ -1022,6 +1022,55 @@ func (m *EpisodeMutation) IDs(ctx context.Context) ([]int, error) {
} }
} }
// SetSeriesID sets the "series_id" field.
func (m *EpisodeMutation) SetSeriesID(i int) {
m.series = &i
}
// SeriesID returns the value of the "series_id" field in the mutation.
func (m *EpisodeMutation) SeriesID() (r int, exists bool) {
v := m.series
if v == nil {
return
}
return *v, true
}
// OldSeriesID returns the old "series_id" field's value of the Episode entity.
// If the Episode object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
func (m *EpisodeMutation) OldSeriesID(ctx context.Context) (v int, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldSeriesID is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldSeriesID requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldSeriesID: %w", err)
}
return oldValue.SeriesID, nil
}
// ClearSeriesID clears the value of the "series_id" field.
func (m *EpisodeMutation) ClearSeriesID() {
m.series = nil
m.clearedFields[episode.FieldSeriesID] = struct{}{}
}
// SeriesIDCleared returns if the "series_id" field was cleared in this mutation.
func (m *EpisodeMutation) SeriesIDCleared() bool {
_, ok := m.clearedFields[episode.FieldSeriesID]
return ok
}
// ResetSeriesID resets all changes to the "series_id" field.
func (m *EpisodeMutation) ResetSeriesID() {
m.series = nil
delete(m.clearedFields, episode.FieldSeriesID)
}
// SetSeasonNumber sets the "season_number" field. // SetSeasonNumber sets the "season_number" field.
func (m *EpisodeMutation) SetSeasonNumber(i int) { func (m *EpisodeMutation) SetSeasonNumber(i int) {
m.season_number = &i m.season_number = &i
@@ -1242,27 +1291,15 @@ func (m *EpisodeMutation) ResetAirDate() {
m.air_date = nil m.air_date = nil
} }
// SetSeriesID sets the "series" edge to the Series entity by id.
func (m *EpisodeMutation) SetSeriesID(id int) {
m.series = &id
}
// ClearSeries clears the "series" edge to the Series entity. // ClearSeries clears the "series" edge to the Series entity.
func (m *EpisodeMutation) ClearSeries() { func (m *EpisodeMutation) ClearSeries() {
m.clearedseries = true m.clearedseries = true
m.clearedFields[episode.FieldSeriesID] = struct{}{}
} }
// SeriesCleared reports if the "series" edge to the Series entity was cleared. // SeriesCleared reports if the "series" edge to the Series entity was cleared.
func (m *EpisodeMutation) SeriesCleared() bool { func (m *EpisodeMutation) SeriesCleared() bool {
return m.clearedseries return m.SeriesIDCleared() || m.clearedseries
}
// SeriesID returns the "series" edge ID in the mutation.
func (m *EpisodeMutation) SeriesID() (id int, exists bool) {
if m.series != nil {
return *m.series, true
}
return
} }
// SeriesIDs returns the "series" edge IDs in the mutation. // SeriesIDs returns the "series" edge IDs in the mutation.
@@ -1315,7 +1352,10 @@ func (m *EpisodeMutation) Type() string {
// order to get all numeric fields that were incremented/decremented, call // order to get all numeric fields that were incremented/decremented, call
// AddedFields(). // AddedFields().
func (m *EpisodeMutation) Fields() []string { func (m *EpisodeMutation) Fields() []string {
fields := make([]string, 0, 5) fields := make([]string, 0, 6)
if m.series != nil {
fields = append(fields, episode.FieldSeriesID)
}
if m.season_number != nil { if m.season_number != nil {
fields = append(fields, episode.FieldSeasonNumber) fields = append(fields, episode.FieldSeasonNumber)
} }
@@ -1339,6 +1379,8 @@ func (m *EpisodeMutation) Fields() []string {
// schema. // schema.
func (m *EpisodeMutation) Field(name string) (ent.Value, bool) { func (m *EpisodeMutation) Field(name string) (ent.Value, bool) {
switch name { switch name {
case episode.FieldSeriesID:
return m.SeriesID()
case episode.FieldSeasonNumber: case episode.FieldSeasonNumber:
return m.SeasonNumber() return m.SeasonNumber()
case episode.FieldEpisodeNumber: case episode.FieldEpisodeNumber:
@@ -1358,6 +1400,8 @@ func (m *EpisodeMutation) Field(name string) (ent.Value, bool) {
// database failed. // database failed.
func (m *EpisodeMutation) OldField(ctx context.Context, name string) (ent.Value, error) { func (m *EpisodeMutation) OldField(ctx context.Context, name string) (ent.Value, error) {
switch name { switch name {
case episode.FieldSeriesID:
return m.OldSeriesID(ctx)
case episode.FieldSeasonNumber: case episode.FieldSeasonNumber:
return m.OldSeasonNumber(ctx) return m.OldSeasonNumber(ctx)
case episode.FieldEpisodeNumber: case episode.FieldEpisodeNumber:
@@ -1377,6 +1421,13 @@ func (m *EpisodeMutation) OldField(ctx context.Context, name string) (ent.Value,
// type. // type.
func (m *EpisodeMutation) SetField(name string, value ent.Value) error { func (m *EpisodeMutation) SetField(name string, value ent.Value) error {
switch name { switch name {
case episode.FieldSeriesID:
v, ok := value.(int)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetSeriesID(v)
return nil
case episode.FieldSeasonNumber: case episode.FieldSeasonNumber:
v, ok := value.(int) v, ok := value.(int)
if !ok { if !ok {
@@ -1468,7 +1519,11 @@ func (m *EpisodeMutation) AddField(name string, value ent.Value) error {
// ClearedFields returns all nullable fields that were cleared during this // ClearedFields returns all nullable fields that were cleared during this
// mutation. // mutation.
func (m *EpisodeMutation) ClearedFields() []string { func (m *EpisodeMutation) ClearedFields() []string {
return nil var fields []string
if m.FieldCleared(episode.FieldSeriesID) {
fields = append(fields, episode.FieldSeriesID)
}
return fields
} }
// FieldCleared returns a boolean indicating if a field with the given name was // FieldCleared returns a boolean indicating if a field with the given name was
@@ -1481,6 +1536,11 @@ func (m *EpisodeMutation) FieldCleared(name string) bool {
// ClearField clears the value of the field with the given name. It returns an // ClearField clears the value of the field with the given name. It returns an
// error if the field is not defined in the schema. // error if the field is not defined in the schema.
func (m *EpisodeMutation) ClearField(name string) error { func (m *EpisodeMutation) ClearField(name string) error {
switch name {
case episode.FieldSeriesID:
m.ClearSeriesID()
return nil
}
return fmt.Errorf("unknown Episode nullable field %s", name) return fmt.Errorf("unknown Episode nullable field %s", name)
} }
@@ -1488,6 +1548,9 @@ func (m *EpisodeMutation) ClearField(name string) error {
// It returns an error if the field is not defined in the schema. // It returns an error if the field is not defined in the schema.
func (m *EpisodeMutation) ResetField(name string) error { func (m *EpisodeMutation) ResetField(name string) error {
switch name { switch name {
case episode.FieldSeriesID:
m.ResetSeriesID()
return nil
case episode.FieldSeasonNumber: case episode.FieldSeasonNumber:
m.ResetSeasonNumber() m.ResetSeasonNumber()
return nil return nil

View File

@@ -14,8 +14,8 @@ type Episode struct {
// Fields of the Episode. // Fields of the Episode.
func (Episode) Fields() []ent.Field { func (Episode) Fields() []ent.Field {
return []ent.Field{ return []ent.Field{
//field.Int("series_id"), field.Int("series_id").Optional(),
field.Int("season_number"), field.Int("season_number").StructTag("json:\"season_number\""),
field.Int("episode_number"), field.Int("episode_number"),
field.String("title"), field.String("title"),
field.String("overview"), field.String("overview"),
@@ -28,7 +28,8 @@ func (Episode) Edges() []ent.Edge {
return []ent.Edge{ return []ent.Edge{
edge.From("series", Series.Type). edge.From("series", Series.Type).
Ref("episodes"). Ref("episodes").
Unique(), Unique().
Field("series_id"),
} }
} }

View File

@@ -40,7 +40,7 @@ const (
// It exists in this package in order to avoid circular dependency with the "episode" package. // It exists in this package in order to avoid circular dependency with the "episode" package.
EpisodesInverseTable = "episodes" EpisodesInverseTable = "episodes"
// EpisodesColumn is the table column denoting the episodes relation/edge. // EpisodesColumn is the table column denoting the episodes relation/edge.
EpisodesColumn = "series_episodes" EpisodesColumn = "series_id"
) )
// Columns holds all SQL columns for series fields. // Columns holds all SQL columns for series fields.

View File

@@ -412,7 +412,9 @@ func (sq *SeriesQuery) loadEpisodes(ctx context.Context, query *EpisodeQuery, no
init(nodes[i]) init(nodes[i])
} }
} }
query.withFKs = true if len(query.ctx.Fields) > 0 {
query.ctx.AppendFieldOnce(episode.FieldSeriesID)
}
query.Where(predicate.Episode(func(s *sql.Selector) { query.Where(predicate.Episode(func(s *sql.Selector) {
s.Where(sql.InValues(s.C(series.EpisodesColumn), fks...)) s.Where(sql.InValues(s.C(series.EpisodesColumn), fks...))
})) }))
@@ -421,13 +423,10 @@ func (sq *SeriesQuery) loadEpisodes(ctx context.Context, query *EpisodeQuery, no
return err return err
} }
for _, n := range neighbors { for _, n := range neighbors {
fk := n.series_episodes fk := n.SeriesID
if fk == nil { node, ok := nodeids[fk]
return fmt.Errorf(`foreign-key "series_episodes" is nil for node %v`, n.ID)
}
node, ok := nodeids[*fk]
if !ok { if !ok {
return fmt.Errorf(`unexpected referenced foreign-key "series_episodes" returned %v for node %v`, *fk, n.ID) return fmt.Errorf(`unexpected referenced foreign-key "series_id" returned %v for node %v`, fk, n.ID)
} }
assign(node, n) assign(node, n)
} }

View File

@@ -56,6 +56,7 @@ func (s *Server) Serve() error {
tv.GET("/search", HttpHandler(s.SearchTvSeries)) tv.GET("/search", HttpHandler(s.SearchTvSeries))
tv.POST("/watchlist", HttpHandler(s.AddWatchlist)) tv.POST("/watchlist", HttpHandler(s.AddWatchlist))
tv.GET("/watchlist", HttpHandler(s.GetWatchlist)) tv.GET("/watchlist", HttpHandler(s.GetWatchlist))
tv.GET("/series/:id", HttpHandler(s.GetTvDetails))
} }
indexer := api.Group("/indexer") indexer := api.Group("/indexer")
{ {

View File

@@ -3,6 +3,7 @@ package server
import ( import (
"polaris/ent" "polaris/ent"
"polaris/log" "polaris/log"
"strconv"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/pkg/errors" "github.com/pkg/errors"
@@ -81,5 +82,11 @@ func (s *Server) GetWatchlist(c *gin.Context) (interface{}, error) {
func (s *Server) GetTvDetails(c *gin.Context) (interface{}, error) { func (s *Server) GetTvDetails(c *gin.Context) (interface{}, error) {
return nil, nil ids := c.Param("id")
id, err := strconv.Atoi(ids)
if err != nil {
return nil, errors.Wrap(err, "convert")
}
detail := s.db.GetSeriesDetails(id)
return detail, nil
} }

View File

@@ -3,6 +3,7 @@ class APIs {
static const searchUrl = "$_baseUrl/api/v1/tv/search"; static const searchUrl = "$_baseUrl/api/v1/tv/search";
static const settingsUrl = "$_baseUrl/api/v1/setting/do"; static const settingsUrl = "$_baseUrl/api/v1/setting/do";
static const watchlistUrl = "$_baseUrl/api/v1/tv/watchlist"; static const watchlistUrl = "$_baseUrl/api/v1/tv/watchlist";
static const seriesDetailUrl = "$_baseUrl/api/v1/tv/series/";
static const tmdbImgBaseUrl = "https://image.tmdb.org/t/p/w500/"; static const tmdbImgBaseUrl = "https://image.tmdb.org/t/p/w500/";

View File

@@ -3,6 +3,7 @@ import 'package:go_router/go_router.dart';
import 'package:ui/navdrawer.dart'; import 'package:ui/navdrawer.dart';
import 'package:ui/search.dart'; import 'package:ui/search.dart';
import 'package:ui/system_settings.dart'; import 'package:ui/system_settings.dart';
import 'package:ui/tv_details.dart';
import 'package:ui/weclome.dart'; import 'package:ui/weclome.dart';
void main() { void main() {
@@ -57,6 +58,10 @@ class MyApp extends StatelessWidget {
GoRoute( GoRoute(
path: SystemSettingsPage.route, path: SystemSettingsPage.route,
builder: (context, state) => SystemSettingsPage(), builder: (context, state) => SystemSettingsPage(),
),
GoRoute(
path: TvDetailsPage.route,
builder: (context, state) => TvDetailsPage(seriesId: state.pathParameters['id']!),
) )
], ],
); );

View File

@@ -42,7 +42,7 @@ class _NavDrawerState extends State<NavDrawer> {
destinations: const <NavigationRailDestination>[ destinations: const <NavigationRailDestination>[
NavigationRailDestination( NavigationRailDestination(
icon: Icon(Icons.live_tv), icon: Icon(Icons.live_tv),
label: Text(' 电视剧'), label: Text('电视剧'),
), ),
NavigationRailDestination( NavigationRailDestination(
icon: Icon(Icons.download), icon: Icon(Icons.download),

View File

@@ -1,24 +1,197 @@
import 'package:dio/dio.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:ui/APIs.dart';
import 'package:ui/server_response.dart';
import 'package:ui/utils.dart';
class TvDetailsPage extends StatefulWidget { class TvDetailsPage extends StatefulWidget {
@override static const route = "/series/:id";
State<StatefulWidget> createState() {
// TODO: implement createState static String toRoute(int id) {
throw UnimplementedError(); return "/series/$id";
} }
final String seriesId;
const TvDetailsPage({super.key, required this.seriesId});
@override
State<StatefulWidget> createState() {
return _TvDetailsPageState(seriesId: seriesId);
}
} }
class _TvDetailsPageState extends State<TvDetailsPage> { class _TvDetailsPageState extends State<TvDetailsPage> {
final int tvId = 1; final String seriesId;
_TvDetailsPageState({required this.seriesId});
SeriesDetails? details;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// TODO: implement build _querySeriesDetails(context);
throw Column(
children: [
if (details == null) {
return const Center(
child: Text("nothing here"),
);
}
Map<int, List<Widget>> m = Map();
for (final ep in details!.episodes!) {
var w = Container(
alignment: Alignment.topLeft,
child: Text(
"${ep.seasonNumber} 季,第 ${ep.episodeNumber} 集:${ep.title}",
textAlign: TextAlign.left,
),
);
if (m[ep.seasonNumber] == null) {
m[ep.seasonNumber!] = List.empty(growable: true);
}
m[ep.seasonNumber!]!.add(w);
}
List<ExpansionTile> list = List.empty(growable: true);
for (final k in m.keys) {
bool _customTileExpanded = false;
var seasonList = ExpansionTile(
initiallyExpanded: true,
title: Text("$k"),
trailing: Icon(
_customTileExpanded
? Icons.arrow_drop_down_circle
: Icons.arrow_drop_down,
),
children: m[k]!,
onExpansionChanged: (bool expanded) {
setState(() {
_customTileExpanded = expanded;
});
},
);
list.add(seasonList);
}
return Column(
children: [
Card(
margin: const EdgeInsets.all(4),
clipBehavior: Clip.hardEdge,
child: Row(
children: <Widget>[
Flexible(
child: SizedBox(
width: 150,
height: 200,
child: Image.network(
APIs.tmdbImgBaseUrl + details!.posterPath!,
fit: BoxFit.contain,
),
),
),
Flexible(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"${details!.name}",
style: const TextStyle(
fontSize: 14, fontWeight: FontWeight.bold),
),
const Text(""),
Text(details!.overview!)
],
),
),
],
),
),
Expanded(
child: ListView(
children: list,
),
),
], ],
) );
} }
} void _querySeriesDetails(BuildContext context) async {
var resp = await Dio().get("${APIs.seriesDetailUrl}$seriesId");
var rsp = ServerResponse.fromJson(resp.data);
if (rsp.code != 0 && context.mounted) {
Utils.showAlertDialog(context, rsp.message);
}
setState(() {
details = SeriesDetails.fromJson(rsp.data);
});
}
}
class SeriesDetails {
int? id;
int? tmdbId;
String? name;
String? originalName;
String? overview;
String? path;
String? posterPath;
String? createdAt;
List<Episodes>? episodes;
SeriesDetails(
{this.id,
this.tmdbId,
this.name,
this.originalName,
this.overview,
this.path,
this.posterPath,
this.createdAt,
this.episodes});
SeriesDetails.fromJson(Map<String, dynamic> json) {
id = json['id'];
tmdbId = json['tmdb_id'];
name = json['name'];
originalName = json['original_name'];
overview = json['overview'];
path = json['path'];
posterPath = json['poster_path'];
createdAt = json['created_at'];
if (json['episodes'] != null) {
episodes = <Episodes>[];
json['episodes'].forEach((v) {
episodes!.add(Episodes.fromJson(v));
});
}
}
}
class Episodes {
int? id;
int? seriesId;
int? episodeNumber;
String? title;
String? airDate;
int? seasonNumber;
String? overview;
Episodes(
{this.id,
this.seriesId,
this.episodeNumber,
this.title,
this.airDate,
this.seasonNumber,
this.overview});
Episodes.fromJson(Map<String, dynamic> json) {
id = json['id'];
seriesId = json['series_id'];
episodeNumber = json['episode_number'];
title = json['title'];
airDate = json['air_date'];
seasonNumber = json['season_number'];
overview = json['overview'];
}
}

View File

@@ -1,7 +1,9 @@
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:ui/APIs.dart'; import 'package:ui/APIs.dart';
import 'package:ui/server_response.dart'; import 'package:ui/server_response.dart';
import 'package:ui/tv_details.dart';
class WelcomePage extends StatefulWidget { class WelcomePage extends StatefulWidget {
const WelcomePage({super.key}); const WelcomePage({super.key});
@@ -20,44 +22,45 @@ class _WeclomePageState extends State<WelcomePage> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
_onRefresh(); _onRefresh();
return GridView.builder( return GridView.builder(
itemCount: favList.length, itemCount: favList.length,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( gridDelegate:
crossAxisCount: 4), const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 4),
itemBuilder: (context, i) { itemBuilder: (context, i) {
var item = TvSeries.fromJson(favList[i]); var item = TvSeries.fromJson(favList[i]);
return Container( return Container(
child: Card( child: Card(
margin: const EdgeInsets.all(4), margin: const EdgeInsets.all(4),
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
child: InkWell( child: InkWell(
//splashColor: Colors.blue.withAlpha(30), //splashColor: Colors.blue.withAlpha(30),
onTap: () { onTap: () {
//showDialog(context: context, builder: builder) context.go(TvDetailsPage.toRoute(item.id!));
}, //showDialog(context: context, builder: builder)
child: Column( },
children: <Widget>[ child: Column(
Flexible( children: <Widget>[
child: SizedBox( Flexible(
width: 300, child: SizedBox(
height: 600, width: 300,
child: Image.network( height: 600,
APIs.tmdbImgBaseUrl + item.posterPath!, child: Image.network(
fit: BoxFit.contain, APIs.tmdbImgBaseUrl + item.posterPath!,
), fit: BoxFit.contain,
),
), ),
Flexible( ),
child: Text(
item.name!,
style: const TextStyle(
fontSize: 14, fontWeight: FontWeight.bold),
),
)
],
), ),
)), Flexible(
); child: Text(
}); item.name!,
style: const TextStyle(
fontSize: 14, fontWeight: FontWeight.bold),
),
)
],
),
)),
);
});
} }
Future<void> _onRefresh() async { Future<void> _onRefresh() async {