Editors can now add anime from scratch
This commit is contained in:
88
arn/Anime.go
88
arn/Anime.go
@ -8,6 +8,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aerogo/api"
|
||||
"github.com/aerogo/nano"
|
||||
"github.com/animenotifier/notify.moe/arn/validate"
|
||||
"github.com/animenotifier/twist"
|
||||
@ -60,6 +61,14 @@ func init() {
|
||||
for _, option := range DataLists["anime-sources"] {
|
||||
AnimeSourceHumanReadable[option.Value] = option.Label
|
||||
}
|
||||
|
||||
API.RegisterActions("Anime", []*api.Action{
|
||||
// Publish
|
||||
PublishAction(),
|
||||
|
||||
// Unpublish
|
||||
UnpublishAction(),
|
||||
})
|
||||
}
|
||||
|
||||
// AnimeID represents an anime ID.
|
||||
@ -69,7 +78,7 @@ type AnimeID = string
|
||||
type Anime struct {
|
||||
ID AnimeID `json:"id" primary:"true"`
|
||||
Type string `json:"type" editable:"true" datalist:"anime-types"`
|
||||
Title *MediaTitle `json:"title" editable:"true"`
|
||||
Title MediaTitle `json:"title" editable:"true"`
|
||||
Summary string `json:"summary" editable:"true" type:"textarea"`
|
||||
Status string `json:"status" editable:"true" datalist:"anime-status"`
|
||||
Genres []string `json:"genres" editable:"true"`
|
||||
@ -80,10 +89,10 @@ type Anime struct {
|
||||
Source string `json:"source" editable:"true" datalist:"anime-sources"`
|
||||
Image Image `json:"image"`
|
||||
FirstChannel string `json:"firstChannel"`
|
||||
Rating *AnimeRating `json:"rating"`
|
||||
Popularity *AnimePopularity `json:"popularity"`
|
||||
Rating AnimeRating `json:"rating"`
|
||||
Popularity AnimePopularity `json:"popularity"`
|
||||
Trailers []*ExternalMedia `json:"trailers" editable:"true"`
|
||||
EpisodeIDs []string `json:"episodes" editable:"true"`
|
||||
EpisodeIDs []string `json:"episodes"`
|
||||
|
||||
// Mixins
|
||||
hasMappings
|
||||
@ -107,19 +116,19 @@ type Anime struct {
|
||||
|
||||
// NewAnime creates a new anime.
|
||||
func NewAnime() *Anime {
|
||||
return &Anime{
|
||||
ID: GenerateID("Anime"),
|
||||
Title: &MediaTitle{},
|
||||
Rating: &AnimeRating{},
|
||||
Popularity: &AnimePopularity{},
|
||||
Trailers: []*ExternalMedia{},
|
||||
hasCreator: hasCreator{
|
||||
Created: DateTimeUTC(),
|
||||
},
|
||||
hasMappings: hasMappings{
|
||||
Mappings: []*Mapping{},
|
||||
},
|
||||
}
|
||||
anime := Anime{}
|
||||
return anime.init()
|
||||
}
|
||||
|
||||
// init is the constructor for Anime.
|
||||
func (anime *Anime) init() *Anime {
|
||||
anime.ID = GenerateID("Anime")
|
||||
anime.Type = "tv"
|
||||
anime.Status = "upcoming"
|
||||
anime.Trailers = []*ExternalMedia{}
|
||||
anime.Mappings = []*Mapping{}
|
||||
anime.Created = DateTimeUTC()
|
||||
return anime
|
||||
}
|
||||
|
||||
// GetAnime gets the anime with the given ID.
|
||||
@ -138,6 +147,41 @@ func (anime *Anime) TitleByUser(user *User) string {
|
||||
return anime.Title.ByUser(user)
|
||||
}
|
||||
|
||||
// Publish publishes the anime draft.
|
||||
func (anime *Anime) Publish() error {
|
||||
// No type
|
||||
if anime.Type == "" {
|
||||
return errors.New("No type")
|
||||
}
|
||||
|
||||
// No name
|
||||
if anime.Title.Canonical == "" {
|
||||
return errors.New("No canonical anime name")
|
||||
}
|
||||
|
||||
// No status
|
||||
if anime.Status == "" {
|
||||
return errors.New("No status")
|
||||
}
|
||||
|
||||
// No genres
|
||||
if len(anime.Genres) == 0 {
|
||||
return errors.New("No genres")
|
||||
}
|
||||
|
||||
// No image
|
||||
if !anime.HasImage() {
|
||||
return errors.New("No anime image")
|
||||
}
|
||||
|
||||
return publish(anime)
|
||||
}
|
||||
|
||||
// Unpublish turns the anime into a draft.
|
||||
func (anime *Anime) Unpublish() error {
|
||||
return unpublish(anime)
|
||||
}
|
||||
|
||||
// AddStudio adds the company ID to the studio ID list if it doesn't exist already.
|
||||
func (anime *Anime) AddStudio(companyID string) {
|
||||
// Is the ID valid?
|
||||
@ -299,16 +343,6 @@ func (anime *Anime) Season() string {
|
||||
// Characters returns the anime characters for this anime.
|
||||
func (anime *Anime) Characters() *AnimeCharacters {
|
||||
characters, _ := GetAnimeCharacters(anime.ID)
|
||||
|
||||
if characters != nil {
|
||||
// TODO: Sort by role in sync-characters job
|
||||
// Sort by role
|
||||
sort.Slice(characters.Items, func(i, j int) bool {
|
||||
// A little trick because "main" < "supporting"
|
||||
return characters.Items[i].Role < characters.Items[j].Role
|
||||
})
|
||||
}
|
||||
|
||||
return characters
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,8 @@ var (
|
||||
_ fmt.Stringer = (*Anime)(nil)
|
||||
_ Likeable = (*Anime)(nil)
|
||||
_ PostParent = (*Anime)(nil)
|
||||
_ Publishable = (*Anime)(nil)
|
||||
_ api.Creatable = (*Anime)(nil)
|
||||
_ api.Deletable = (*Anime)(nil)
|
||||
_ api.Editable = (*Anime)(nil)
|
||||
_ api.CustomEditable = (*Anime)(nil)
|
||||
@ -35,6 +37,48 @@ func init() {
|
||||
})
|
||||
}
|
||||
|
||||
// Create sets the data for a new anime.
|
||||
func (anime *Anime) Create(ctx aero.Context) error {
|
||||
user := GetUserFromContext(ctx)
|
||||
|
||||
if user == nil {
|
||||
return errors.New("Not logged in")
|
||||
}
|
||||
|
||||
anime.init()
|
||||
anime.CreatedBy = user.ID
|
||||
|
||||
// Characters
|
||||
characters := anime.Characters()
|
||||
|
||||
if characters == nil {
|
||||
characters = &AnimeCharacters{
|
||||
AnimeID: anime.ID,
|
||||
Items: []*AnimeCharacter{},
|
||||
}
|
||||
|
||||
characters.Save()
|
||||
}
|
||||
|
||||
// Relations
|
||||
relations := anime.Relations()
|
||||
|
||||
if relations == nil {
|
||||
relations = &AnimeRelations{
|
||||
AnimeID: anime.ID,
|
||||
Items: []*AnimeRelation{},
|
||||
}
|
||||
|
||||
relations.Save()
|
||||
}
|
||||
|
||||
// Write log entry
|
||||
logEntry := NewEditLogEntry(user.ID, "create", "Anime", anime.ID, "", "", "")
|
||||
logEntry.Save()
|
||||
|
||||
return anime.Unpublish()
|
||||
}
|
||||
|
||||
// Edit creates an edit log entry.
|
||||
func (anime *Anime) Edit(ctx aero.Context, key string, value reflect.Value, newValue reflect.Value) (consumed bool, err error) {
|
||||
user := GetUserFromContext(ctx)
|
||||
@ -104,6 +148,12 @@ func (anime *Anime) DeleteInContext(ctx aero.Context) error {
|
||||
|
||||
// Delete deletes the anime from the database.
|
||||
func (anime *Anime) Delete() error {
|
||||
if anime.IsDraft {
|
||||
draftIndex := anime.Creator().DraftIndex()
|
||||
draftIndex.AnimeID = ""
|
||||
draftIndex.Save()
|
||||
}
|
||||
|
||||
// Delete anime characters
|
||||
DB.Delete("AnimeCharacters", anime.ID)
|
||||
|
||||
|
@ -2,6 +2,7 @@ package arn
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"github.com/aerogo/nano"
|
||||
@ -81,6 +82,17 @@ func (characters *AnimeCharacters) Self() Loggable {
|
||||
return characters
|
||||
}
|
||||
|
||||
// Sort sorts the characters by role.
|
||||
func (characters *AnimeCharacters) Sort() {
|
||||
characters.Lock()
|
||||
defer characters.Unlock()
|
||||
|
||||
sort.Slice(characters.Items, func(i, j int) bool {
|
||||
// A little trick because "main" < "supporting"
|
||||
return characters.Items[i].Role < characters.Items[j].Role
|
||||
})
|
||||
}
|
||||
|
||||
// Contains tells you whether the given character ID exists.
|
||||
func (characters *AnimeCharacters) Contains(characterID string) bool {
|
||||
characters.Lock()
|
||||
|
@ -93,7 +93,6 @@ func publish(draft Publishable) error {
|
||||
}
|
||||
|
||||
draftIndex.Save()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -35,6 +35,10 @@ func Anime(originalTerm string, maxLength int) []*arn.Anime {
|
||||
}
|
||||
|
||||
for anime := range arn.StreamAnime() {
|
||||
if anime.IsDraft {
|
||||
continue
|
||||
}
|
||||
|
||||
if anime.ID == originalTerm {
|
||||
return []*arn.Anime{anime}
|
||||
}
|
||||
|
Reference in New Issue
Block a user