Editors can now add anime from scratch

This commit is contained in:
2019-10-16 14:44:53 +09:00
parent 5ec435cf99
commit 75c03c7bdc
9 changed files with 180 additions and 62 deletions

View File

@ -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
}

View File

@ -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)

View File

@ -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()

View File

@ -93,7 +93,6 @@ func publish(draft Publishable) error {
}
draftIndex.Save()
return nil
}

View File

@ -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}
}