Episodes now have their own ID
This commit is contained in:
@ -144,7 +144,7 @@ func (amv *AMV) MainAnime() *Anime {
|
||||
// ExtraAnime returns main anime for the AMV, if available.
|
||||
func (amv *AMV) ExtraAnime() []*Anime {
|
||||
objects := DB.GetMany("Anime", amv.ExtraAnimeIDs)
|
||||
animes := []*Anime{}
|
||||
animes := make([]*Anime, 0, len(amv.ExtraAnimeIDs))
|
||||
|
||||
for _, obj := range objects {
|
||||
if obj == nil {
|
||||
|
56
arn/Anime.go
56
arn/Anime.go
@ -83,6 +83,7 @@ type Anime struct {
|
||||
Rating *AnimeRating `json:"rating"`
|
||||
Popularity *AnimePopularity `json:"popularity"`
|
||||
Trailers []*ExternalMedia `json:"trailers" editable:"true"`
|
||||
EpisodeIDs []string `json:"episodes" editable:"true"`
|
||||
|
||||
// Mixins
|
||||
hasMappings
|
||||
@ -356,15 +357,20 @@ func (anime *Anime) EndDateTime() time.Time {
|
||||
return t
|
||||
}
|
||||
|
||||
// Episodes returns the anime episodes wrapper.
|
||||
func (anime *Anime) Episodes() *AnimeEpisodes {
|
||||
record, err := DB.Get("AnimeEpisodes", anime.ID)
|
||||
// Episodes returns the anime episodes.
|
||||
func (anime *Anime) Episodes() EpisodeList {
|
||||
objects := DB.GetMany("Episode", anime.EpisodeIDs)
|
||||
episodes := make([]*Episode, 0, len(anime.EpisodeIDs))
|
||||
|
||||
if err != nil {
|
||||
return nil
|
||||
for _, obj := range objects {
|
||||
if obj == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
episodes = append(episodes, obj.(*Episode))
|
||||
}
|
||||
|
||||
return record.(*AnimeEpisodes)
|
||||
return episodes
|
||||
}
|
||||
|
||||
// UsersWatchingOrPlanned returns a list of users who are watching the anime right now.
|
||||
@ -387,13 +393,6 @@ func (anime *Anime) RefreshEpisodes() error {
|
||||
// Fetch episodes
|
||||
episodes := anime.Episodes()
|
||||
|
||||
if episodes == nil {
|
||||
episodes = &AnimeEpisodes{
|
||||
AnimeID: anime.ID,
|
||||
Items: []*AnimeEpisode{},
|
||||
}
|
||||
}
|
||||
|
||||
// Save number of available episodes for comparison later
|
||||
oldAvailableCount := episodes.AvailableCount()
|
||||
|
||||
@ -441,7 +440,7 @@ func (anime *Anime) RefreshEpisodes() error {
|
||||
// Number remaining episodes
|
||||
startNumber := 0
|
||||
|
||||
for _, episode := range episodes.Items {
|
||||
for _, episode := range episodes {
|
||||
if episode.Number != -1 {
|
||||
startNumber = episode.Number
|
||||
continue
|
||||
@ -456,7 +455,7 @@ func (anime *Anime) RefreshEpisodes() error {
|
||||
lastAiringDate := ""
|
||||
timeDifference := oneWeek
|
||||
|
||||
for _, episode := range episodes.Items {
|
||||
for _, episode := range episodes {
|
||||
if validate.DateTime(episode.AiringDate.Start) {
|
||||
if lastAiringDate != "" {
|
||||
a, _ := time.Parse(time.RFC3339, lastAiringDate)
|
||||
@ -485,12 +484,20 @@ func (anime *Anime) RefreshEpisodes() error {
|
||||
lastAiringDate = episode.AiringDate.Start
|
||||
}
|
||||
|
||||
episodes.Save()
|
||||
// Save new episode ID list
|
||||
episodeIDs := make([]string, len(episodes))
|
||||
|
||||
for index := range episodes {
|
||||
episodeIDs[index] = episodes[index].ID
|
||||
}
|
||||
|
||||
anime.EpisodeIDs = episodeIDs
|
||||
anime.Save()
|
||||
return nil
|
||||
}
|
||||
|
||||
// ShoboiEpisodes returns a slice of episode info from cal.syoboi.jp.
|
||||
func (anime *Anime) ShoboiEpisodes() ([]*AnimeEpisode, error) {
|
||||
func (anime *Anime) ShoboiEpisodes() (EpisodeList, error) {
|
||||
shoboiID := anime.GetMapping("shoboi/anime")
|
||||
|
||||
if shoboiID == "" {
|
||||
@ -503,7 +510,7 @@ func (anime *Anime) ShoboiEpisodes() ([]*AnimeEpisode, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
arnEpisodes := []*AnimeEpisode{}
|
||||
arnEpisodes := []*Episode{}
|
||||
shoboiEpisodes := shoboiAnime.Episodes()
|
||||
|
||||
for _, shoboiEpisode := range shoboiEpisodes {
|
||||
@ -529,7 +536,7 @@ func (anime *Anime) ShoboiEpisodes() ([]*AnimeEpisode, error) {
|
||||
}
|
||||
|
||||
// TwistEpisodes returns a slice of episode info from twist.moe.
|
||||
func (anime *Anime) TwistEpisodes() ([]*AnimeEpisode, error) {
|
||||
func (anime *Anime) TwistEpisodes() (EpisodeList, error) {
|
||||
idList, err := GetIDList("animetwist index")
|
||||
|
||||
if err != nil {
|
||||
@ -566,7 +573,7 @@ func (anime *Anime) TwistEpisodes() ([]*AnimeEpisode, error) {
|
||||
return episodes[a].Number < episodes[b].Number
|
||||
})
|
||||
|
||||
arnEpisodes := []*AnimeEpisode{}
|
||||
arnEpisodes := []*Episode{}
|
||||
|
||||
for _, episode := range episodes {
|
||||
arnEpisode := NewAnimeEpisode()
|
||||
@ -584,10 +591,9 @@ func (anime *Anime) TwistEpisodes() ([]*AnimeEpisode, error) {
|
||||
// UpcomingEpisodes ...
|
||||
func (anime *Anime) UpcomingEpisodes() []*UpcomingEpisode {
|
||||
var upcomingEpisodes []*UpcomingEpisode
|
||||
|
||||
now := time.Now().UTC().Format(time.RFC3339)
|
||||
|
||||
for _, episode := range anime.Episodes().Items {
|
||||
for _, episode := range anime.Episodes() {
|
||||
if episode.AiringDate.Start > now && validate.DateTime(episode.AiringDate.Start) {
|
||||
upcomingEpisodes = append(upcomingEpisodes, &UpcomingEpisode{
|
||||
Anime: anime,
|
||||
@ -603,7 +609,7 @@ func (anime *Anime) UpcomingEpisodes() []*UpcomingEpisode {
|
||||
func (anime *Anime) UpcomingEpisode() *UpcomingEpisode {
|
||||
now := time.Now().UTC().Format(time.RFC3339)
|
||||
|
||||
for _, episode := range anime.Episodes().Items {
|
||||
for _, episode := range anime.Episodes() {
|
||||
if episode.AiringDate.Start > now && validate.DateTime(episode.AiringDate.Start) {
|
||||
return &UpcomingEpisode{
|
||||
Anime: anime,
|
||||
@ -719,8 +725,8 @@ func (anime *Anime) CalculatedStatus() string {
|
||||
}
|
||||
|
||||
// EpisodeByNumber returns the episode with the given number.
|
||||
func (anime *Anime) EpisodeByNumber(number int) *AnimeEpisode {
|
||||
for _, episode := range anime.Episodes().Items {
|
||||
func (anime *Anime) EpisodeByNumber(number int) *Episode {
|
||||
for _, episode := range anime.Episodes() {
|
||||
if number == episode.Number {
|
||||
return episode
|
||||
}
|
||||
|
@ -1,78 +0,0 @@
|
||||
package arn
|
||||
|
||||
import "github.com/animenotifier/notify.moe/arn/validate"
|
||||
|
||||
// AnimeEpisode ...
|
||||
type AnimeEpisode struct {
|
||||
Number int `json:"number" editable:"true"`
|
||||
Title EpisodeTitle `json:"title" editable:"true"`
|
||||
AiringDate AiringDate `json:"airingDate" editable:"true"`
|
||||
Links map[string]string `json:"links"`
|
||||
}
|
||||
|
||||
// EpisodeTitle ...
|
||||
type EpisodeTitle struct {
|
||||
Romaji string `json:"romaji" editable:"true"`
|
||||
English string `json:"english" editable:"true"`
|
||||
Japanese string `json:"japanese" editable:"true"`
|
||||
}
|
||||
|
||||
// Available tells you whether the episode is available (triggered when it has a link).
|
||||
func (a *AnimeEpisode) Available() bool {
|
||||
return len(a.Links) > 0
|
||||
}
|
||||
|
||||
// AvailableOn tells you whether the episode is available on a given service.
|
||||
func (a *AnimeEpisode) AvailableOn(serviceName string) bool {
|
||||
return a.Links[serviceName] != ""
|
||||
}
|
||||
|
||||
// Merge combines the data of both episodes to one.
|
||||
func (a *AnimeEpisode) Merge(b *AnimeEpisode) {
|
||||
if b == nil {
|
||||
return
|
||||
}
|
||||
|
||||
a.Number = b.Number
|
||||
|
||||
// Titles
|
||||
if b.Title.Romaji != "" {
|
||||
a.Title.Romaji = b.Title.Romaji
|
||||
}
|
||||
|
||||
if b.Title.English != "" {
|
||||
a.Title.English = b.Title.English
|
||||
}
|
||||
|
||||
if b.Title.Japanese != "" {
|
||||
a.Title.Japanese = b.Title.Japanese
|
||||
}
|
||||
|
||||
// Airing date
|
||||
if validate.DateTime(b.AiringDate.Start) {
|
||||
a.AiringDate.Start = b.AiringDate.Start
|
||||
}
|
||||
|
||||
if validate.DateTime(b.AiringDate.End) {
|
||||
a.AiringDate.End = b.AiringDate.End
|
||||
}
|
||||
|
||||
// Links
|
||||
if a.Links == nil {
|
||||
a.Links = map[string]string{}
|
||||
}
|
||||
|
||||
for name, link := range b.Links {
|
||||
a.Links[name] = link
|
||||
}
|
||||
}
|
||||
|
||||
// NewAnimeEpisode creates an empty anime episode.
|
||||
func NewAnimeEpisode() *AnimeEpisode {
|
||||
return &AnimeEpisode{
|
||||
Number: -1,
|
||||
Title: EpisodeTitle{},
|
||||
AiringDate: AiringDate{},
|
||||
Links: map[string]string{},
|
||||
}
|
||||
}
|
@ -1,157 +0,0 @@
|
||||
package arn
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/aerogo/nano"
|
||||
)
|
||||
|
||||
// AnimeEpisodes is a list of episodes for an anime.
|
||||
type AnimeEpisodes struct {
|
||||
AnimeID AnimeID `json:"animeId" mainID:"true"`
|
||||
Items []*AnimeEpisode `json:"items" editable:"true"`
|
||||
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
// Link returns the link for that object.
|
||||
func (episodes *AnimeEpisodes) Link() string {
|
||||
return "/anime/" + episodes.AnimeID + "/episodes"
|
||||
}
|
||||
|
||||
// Sort sorts the episodes by episode number.
|
||||
func (episodes *AnimeEpisodes) Sort() {
|
||||
episodes.Lock()
|
||||
defer episodes.Unlock()
|
||||
|
||||
sort.Slice(episodes.Items, func(i, j int) bool {
|
||||
return episodes.Items[i].Number < episodes.Items[j].Number
|
||||
})
|
||||
}
|
||||
|
||||
// Find finds the given episode number.
|
||||
func (episodes *AnimeEpisodes) Find(episodeNumber int) (*AnimeEpisode, int) {
|
||||
episodes.Lock()
|
||||
defer episodes.Unlock()
|
||||
|
||||
for index, episode := range episodes.Items {
|
||||
if episode.Number == episodeNumber {
|
||||
return episode, index
|
||||
}
|
||||
}
|
||||
|
||||
return nil, -1
|
||||
}
|
||||
|
||||
// Merge combines the data of both episode slices to one.
|
||||
func (episodes *AnimeEpisodes) Merge(b []*AnimeEpisode) {
|
||||
if b == nil {
|
||||
return
|
||||
}
|
||||
|
||||
episodes.Lock()
|
||||
defer episodes.Unlock()
|
||||
|
||||
for index, episode := range b {
|
||||
if index >= len(episodes.Items) {
|
||||
episodes.Items = append(episodes.Items, episode)
|
||||
} else {
|
||||
episodes.Items[index].Merge(episode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Last returns the last n items.
|
||||
func (episodes *AnimeEpisodes) Last(count int) []*AnimeEpisode {
|
||||
return episodes.Items[len(episodes.Items)-count:]
|
||||
}
|
||||
|
||||
// AvailableCount counts the number of available episodes.
|
||||
func (episodes *AnimeEpisodes) AvailableCount() int {
|
||||
episodes.Lock()
|
||||
defer episodes.Unlock()
|
||||
|
||||
available := 0
|
||||
|
||||
for _, episode := range episodes.Items {
|
||||
if len(episode.Links) > 0 {
|
||||
available++
|
||||
}
|
||||
}
|
||||
|
||||
return available
|
||||
}
|
||||
|
||||
// Anime returns the anime the episodes refer to.
|
||||
func (episodes *AnimeEpisodes) Anime() *Anime {
|
||||
anime, _ := GetAnime(episodes.AnimeID)
|
||||
return anime
|
||||
}
|
||||
|
||||
// String implements the default string serialization.
|
||||
func (episodes *AnimeEpisodes) String() string {
|
||||
return episodes.Anime().String()
|
||||
}
|
||||
|
||||
// GetID returns the anime ID.
|
||||
func (episodes *AnimeEpisodes) GetID() string {
|
||||
return episodes.AnimeID
|
||||
}
|
||||
|
||||
// TypeName returns the type name.
|
||||
func (episodes *AnimeEpisodes) TypeName() string {
|
||||
return "AnimeEpisodes"
|
||||
}
|
||||
|
||||
// Self returns the object itself.
|
||||
func (episodes *AnimeEpisodes) Self() Loggable {
|
||||
return episodes
|
||||
}
|
||||
|
||||
// ListString returns a text representation of the anime episodes.
|
||||
func (episodes *AnimeEpisodes) ListString() string {
|
||||
episodes.Lock()
|
||||
defer episodes.Unlock()
|
||||
|
||||
b := strings.Builder{}
|
||||
|
||||
for _, episode := range episodes.Items {
|
||||
b.WriteString(strconv.Itoa(episode.Number))
|
||||
b.WriteString(" | ")
|
||||
b.WriteString(episode.Title.Japanese)
|
||||
b.WriteString(" | ")
|
||||
b.WriteString(episode.AiringDate.StartDateHuman())
|
||||
b.WriteByte('\n')
|
||||
}
|
||||
|
||||
return strings.TrimRight(b.String(), "\n")
|
||||
}
|
||||
|
||||
// StreamAnimeEpisodes returns a stream of all anime episodes.
|
||||
func StreamAnimeEpisodes() <-chan *AnimeEpisodes {
|
||||
channel := make(chan *AnimeEpisodes, nano.ChannelBufferSize)
|
||||
|
||||
go func() {
|
||||
for obj := range DB.All("AnimeEpisodes") {
|
||||
channel <- obj.(*AnimeEpisodes)
|
||||
}
|
||||
|
||||
close(channel)
|
||||
}()
|
||||
|
||||
return channel
|
||||
}
|
||||
|
||||
// GetAnimeEpisodes ...
|
||||
func GetAnimeEpisodes(id string) (*AnimeEpisodes, error) {
|
||||
obj, err := DB.Get("AnimeEpisodes", id)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return obj.(*AnimeEpisodes), nil
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
package arn
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/aerogo/aero"
|
||||
"github.com/aerogo/api"
|
||||
)
|
||||
|
||||
// Force interface implementations
|
||||
var (
|
||||
_ fmt.Stringer = (*AnimeEpisodes)(nil)
|
||||
_ api.Editable = (*AnimeEpisodes)(nil)
|
||||
_ api.ArrayEventListener = (*AnimeEpisodes)(nil)
|
||||
)
|
||||
|
||||
// Authorize returns an error if the given API POST request is not authorized.
|
||||
func (episodes *AnimeEpisodes) Authorize(ctx aero.Context, action string) error {
|
||||
user := GetUserFromContext(ctx)
|
||||
|
||||
if user == nil || (user.Role != "editor" && user.Role != "admin") {
|
||||
return errors.New("Not logged in or not authorized to edit")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Edit creates an edit log entry.
|
||||
func (episodes *AnimeEpisodes) Edit(ctx aero.Context, key string, value reflect.Value, newValue reflect.Value) (consumed bool, err error) {
|
||||
return edit(episodes, ctx, key, value, newValue)
|
||||
}
|
||||
|
||||
// OnAppend saves a log entry.
|
||||
func (episodes *AnimeEpisodes) OnAppend(ctx aero.Context, key string, index int, obj interface{}) {
|
||||
onAppend(episodes, ctx, key, index, obj)
|
||||
}
|
||||
|
||||
// OnRemove saves a log entry.
|
||||
func (episodes *AnimeEpisodes) OnRemove(ctx aero.Context, key string, index int, obj interface{}) {
|
||||
onRemove(episodes, ctx, key, index, obj)
|
||||
}
|
||||
|
||||
// Save saves the episodes in the database.
|
||||
func (episodes *AnimeEpisodes) Save() {
|
||||
DB.Set("AnimeEpisodes", episodes.AnimeID, episodes)
|
||||
}
|
||||
|
||||
// Delete deletes the episode list from the database.
|
||||
func (episodes *AnimeEpisodes) Delete() error {
|
||||
DB.Delete("AnimeEpisodes", episodes.AnimeID)
|
||||
return nil
|
||||
}
|
@ -20,7 +20,6 @@ var DB = Node.Namespace("arn").RegisterTypes(
|
||||
(*Analytics)(nil),
|
||||
(*Anime)(nil),
|
||||
(*AnimeCharacters)(nil),
|
||||
(*AnimeEpisodes)(nil),
|
||||
(*AnimeRelations)(nil),
|
||||
(*AnimeList)(nil),
|
||||
(*Character)(nil),
|
||||
@ -29,6 +28,7 @@ var DB = Node.Namespace("arn").RegisterTypes(
|
||||
(*DraftIndex)(nil),
|
||||
(*EditLogEntry)(nil),
|
||||
(*EmailToUser)(nil),
|
||||
(*Episode)(nil),
|
||||
(*FacebookToUser)(nil),
|
||||
(*GoogleToUser)(nil),
|
||||
(*Group)(nil),
|
||||
|
164
arn/Episode.go
Normal file
164
arn/Episode.go
Normal file
@ -0,0 +1,164 @@
|
||||
package arn
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/aerogo/nano"
|
||||
"github.com/animenotifier/notify.moe/arn/validate"
|
||||
)
|
||||
|
||||
// Episode represents a single episode for an anime.
|
||||
type Episode struct {
|
||||
ID string `json:"id"`
|
||||
AnimeID AnimeID `json:"animeId"`
|
||||
Number int `json:"number" editable:"true"`
|
||||
Title EpisodeTitle `json:"title" editable:"true"`
|
||||
AiringDate AiringDate `json:"airingDate" editable:"true"`
|
||||
Links map[string]string `json:"links"`
|
||||
}
|
||||
|
||||
// EpisodeTitle contains the title information for an anime episode.
|
||||
type EpisodeTitle struct {
|
||||
Romaji string `json:"romaji" editable:"true"`
|
||||
English string `json:"english" editable:"true"`
|
||||
Japanese string `json:"japanese" editable:"true"`
|
||||
}
|
||||
|
||||
// NewAnimeEpisode creates a new anime episode.
|
||||
func NewAnimeEpisode() *Episode {
|
||||
return &Episode{
|
||||
ID: GenerateID("Episode"),
|
||||
Number: -1,
|
||||
}
|
||||
}
|
||||
|
||||
// Anime returns the anime the episode refers to.
|
||||
func (episode *Episode) Anime() *Anime {
|
||||
anime, _ := GetAnime(episode.AnimeID)
|
||||
return anime
|
||||
}
|
||||
|
||||
// GetID returns the episode ID.
|
||||
func (episode *Episode) GetID() string {
|
||||
return episode.ID
|
||||
}
|
||||
|
||||
// TypeName returns the type name.
|
||||
func (episode *Episode) TypeName() string {
|
||||
return "Episode"
|
||||
}
|
||||
|
||||
// Self returns the object itself.
|
||||
func (episode *Episode) Self() Loggable {
|
||||
return episode
|
||||
}
|
||||
|
||||
// Link returns the permalink to the episode.
|
||||
func (episode *Episode) Link() string {
|
||||
return "/episode/" + episode.ID
|
||||
}
|
||||
|
||||
// Available tells you whether the episode is available (triggered when it has a link).
|
||||
func (episode *Episode) Available() bool {
|
||||
return len(episode.Links) > 0
|
||||
}
|
||||
|
||||
// AvailableOn tells you whether the episode is available on a given service.
|
||||
func (episode *Episode) AvailableOn(serviceName string) bool {
|
||||
return episode.Links[serviceName] != ""
|
||||
}
|
||||
|
||||
// Previous returns the previous episode, if available.
|
||||
func (episode *Episode) Previous() *Episode {
|
||||
episodes := episode.Anime().Episodes()
|
||||
_, index := episodes.Find(episode.Number)
|
||||
|
||||
if index > 0 {
|
||||
return episodes[index-1]
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Next returns the next episode, if available.
|
||||
func (episode *Episode) Next() *Episode {
|
||||
episodes := episode.Anime().Episodes()
|
||||
_, index := episodes.Find(episode.Number)
|
||||
|
||||
if index != -1 && index+1 < len(episodes) {
|
||||
return episodes[index+1]
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Merge combines the data of both episodes to one.
|
||||
func (episode *Episode) Merge(b *Episode) {
|
||||
if b == nil {
|
||||
return
|
||||
}
|
||||
|
||||
episode.Number = b.Number
|
||||
|
||||
// Titles
|
||||
if b.Title.Romaji != "" {
|
||||
episode.Title.Romaji = b.Title.Romaji
|
||||
}
|
||||
|
||||
if b.Title.English != "" {
|
||||
episode.Title.English = b.Title.English
|
||||
}
|
||||
|
||||
if b.Title.Japanese != "" {
|
||||
episode.Title.Japanese = b.Title.Japanese
|
||||
}
|
||||
|
||||
// Airing date
|
||||
if validate.DateTime(b.AiringDate.Start) {
|
||||
episode.AiringDate.Start = b.AiringDate.Start
|
||||
}
|
||||
|
||||
if validate.DateTime(b.AiringDate.End) {
|
||||
episode.AiringDate.End = b.AiringDate.End
|
||||
}
|
||||
|
||||
// Links
|
||||
if episode.Links == nil {
|
||||
episode.Links = map[string]string{}
|
||||
}
|
||||
|
||||
for name, link := range b.Links {
|
||||
episode.Links[name] = link
|
||||
}
|
||||
}
|
||||
|
||||
// String implements the default string serialization.
|
||||
func (episode *Episode) String() string {
|
||||
return fmt.Sprintf("%s ep. %d", episode.Anime().TitleByUser(nil), episode.Number)
|
||||
}
|
||||
|
||||
// StreamEpisodes returns a stream of all episodes.
|
||||
func StreamEpisodes() <-chan *Episode {
|
||||
channel := make(chan *Episode, nano.ChannelBufferSize)
|
||||
|
||||
go func() {
|
||||
for obj := range DB.All("Episode") {
|
||||
channel <- obj.(*Episode)
|
||||
}
|
||||
|
||||
close(channel)
|
||||
}()
|
||||
|
||||
return channel
|
||||
}
|
||||
|
||||
// GetEpisode returns the episode with the given ID.
|
||||
func GetEpisode(id string) (*Episode, error) {
|
||||
obj, err := DB.Get("Episode", id)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return obj.(*Episode), nil
|
||||
}
|
54
arn/EpisodeAPI.go
Normal file
54
arn/EpisodeAPI.go
Normal file
@ -0,0 +1,54 @@
|
||||
package arn
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/aerogo/aero"
|
||||
"github.com/aerogo/api"
|
||||
)
|
||||
|
||||
// Force interface implementations
|
||||
var (
|
||||
_ fmt.Stringer = (*Episode)(nil)
|
||||
_ api.Editable = (*Episode)(nil)
|
||||
_ api.ArrayEventListener = (*Episode)(nil)
|
||||
)
|
||||
|
||||
// Authorize returns an error if the given API POST request is not authorized.
|
||||
func (episode *Episode) Authorize(ctx aero.Context, action string) error {
|
||||
user := GetUserFromContext(ctx)
|
||||
|
||||
if user == nil || (user.Role != "editor" && user.Role != "admin") {
|
||||
return errors.New("Not logged in or not authorized to edit")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Edit creates an edit log entry.
|
||||
func (episode *Episode) Edit(ctx aero.Context, key string, value reflect.Value, newValue reflect.Value) (consumed bool, err error) {
|
||||
return edit(episode, ctx, key, value, newValue)
|
||||
}
|
||||
|
||||
// OnAppend saves a log entry.
|
||||
func (episode *Episode) OnAppend(ctx aero.Context, key string, index int, obj interface{}) {
|
||||
onAppend(episode, ctx, key, index, obj)
|
||||
}
|
||||
|
||||
// OnRemove saves a log entry.
|
||||
func (episode *Episode) OnRemove(ctx aero.Context, key string, index int, obj interface{}) {
|
||||
onRemove(episode, ctx, key, index, obj)
|
||||
}
|
||||
|
||||
// Save saves the episode in the database.
|
||||
func (episode *Episode) Save() {
|
||||
DB.Set("Episode", episode.ID, episode)
|
||||
}
|
||||
|
||||
// Delete deletes the episode list from the database.
|
||||
func (episode *Episode) Delete() error {
|
||||
DB.Delete("Episode", episode.AnimeID)
|
||||
return nil
|
||||
}
|
68
arn/EpisodeList.go
Normal file
68
arn/EpisodeList.go
Normal file
@ -0,0 +1,68 @@
|
||||
package arn
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// EpisodeList is a list of episodes.
|
||||
type EpisodeList []*Episode
|
||||
|
||||
// Sort sorts the episodes by episode number.
|
||||
func (episodes EpisodeList) Sort() {
|
||||
sort.Slice(episodes, func(i, j int) bool {
|
||||
return episodes[i].Number < episodes[j].Number
|
||||
})
|
||||
}
|
||||
|
||||
// Find finds the given episode number.
|
||||
func (episodes EpisodeList) Find(episodeNumber int) (*Episode, int) {
|
||||
for index, episode := range episodes {
|
||||
if episode.Number == episodeNumber {
|
||||
return episode, index
|
||||
}
|
||||
}
|
||||
|
||||
return nil, -1
|
||||
}
|
||||
|
||||
// Merge combines the data of both episode lists to one.
|
||||
func (episodes EpisodeList) Merge(b EpisodeList) {
|
||||
for index, episode := range b {
|
||||
if index >= len(episodes) {
|
||||
episodes = append(episodes, episode)
|
||||
} else {
|
||||
episodes[index].Merge(episode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// HumanReadable returns a text representation of the anime episodes.
|
||||
func (episodes EpisodeList) HumanReadable() string {
|
||||
b := strings.Builder{}
|
||||
|
||||
for _, episode := range episodes {
|
||||
b.WriteString(strconv.Itoa(episode.Number))
|
||||
b.WriteString(" | ")
|
||||
b.WriteString(episode.Title.Japanese)
|
||||
b.WriteString(" | ")
|
||||
b.WriteString(episode.AiringDate.StartDateHuman())
|
||||
b.WriteByte('\n')
|
||||
}
|
||||
|
||||
return strings.TrimRight(b.String(), "\n")
|
||||
}
|
||||
|
||||
// AvailableCount counts the number of available episodes.
|
||||
func (episodes EpisodeList) AvailableCount() int {
|
||||
available := 0
|
||||
|
||||
for _, episode := range episodes {
|
||||
if episode.Available() {
|
||||
available++
|
||||
}
|
||||
}
|
||||
|
||||
return available
|
||||
}
|
@ -11,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
// NewAnimeFromKitsuAnime ...
|
||||
func NewAnimeFromKitsuAnime(kitsuAnime *kitsu.Anime) (*Anime, *AnimeCharacters, *AnimeRelations, *AnimeEpisodes) {
|
||||
func NewAnimeFromKitsuAnime(kitsuAnime *kitsu.Anime) (*Anime, *AnimeCharacters, *AnimeRelations) {
|
||||
anime := NewAnime()
|
||||
attr := kitsuAnime.Attributes
|
||||
|
||||
@ -82,16 +82,6 @@ func NewAnimeFromKitsuAnime(kitsuAnime *kitsu.Anime) (*Anime, *AnimeCharacters,
|
||||
}
|
||||
}
|
||||
|
||||
// Episodes
|
||||
episodes, _ := GetAnimeEpisodes(anime.ID)
|
||||
|
||||
if episodes == nil {
|
||||
episodes = &AnimeEpisodes{
|
||||
AnimeID: anime.ID,
|
||||
Items: []*AnimeEpisode{},
|
||||
}
|
||||
}
|
||||
|
||||
// Relations
|
||||
relations, _ := GetAnimeRelations(anime.ID)
|
||||
|
||||
@ -102,7 +92,7 @@ func NewAnimeFromKitsuAnime(kitsuAnime *kitsu.Anime) (*Anime, *AnimeCharacters,
|
||||
}
|
||||
}
|
||||
|
||||
return anime, characters, relations, episodes
|
||||
return anime, characters, relations
|
||||
}
|
||||
|
||||
// StreamKitsuAnime returns a stream of all Kitsu anime.
|
||||
|
@ -3,5 +3,5 @@ package arn
|
||||
// UpcomingEpisode is used in the user schedule.
|
||||
type UpcomingEpisode struct {
|
||||
Anime *Anime
|
||||
Episode *AnimeEpisode
|
||||
Episode *Episode
|
||||
}
|
||||
|
Reference in New Issue
Block a user