notify.moe/arn/ThreadAPI.go

217 lines
5.1 KiB
Go

package arn
import (
"errors"
"fmt"
"reflect"
"github.com/aerogo/aero"
"github.com/aerogo/aero/event"
"github.com/aerogo/api"
"github.com/aerogo/markdown"
"github.com/animenotifier/notify.moe/arn/autocorrect"
)
// Force interface implementations
var (
_ Postable = (*Thread)(nil)
_ Likeable = (*Thread)(nil)
_ LikeEventReceiver = (*Thread)(nil)
_ Lockable = (*Thread)(nil)
_ LockEventReceiver = (*Thread)(nil)
_ PostParent = (*Thread)(nil)
_ fmt.Stringer = (*Thread)(nil)
_ api.Newable = (*Thread)(nil)
_ api.Editable = (*Thread)(nil)
_ api.Actionable = (*Thread)(nil)
_ api.Deletable = (*Thread)(nil)
)
// Actions
func init() {
API.RegisterActions("Thread", []*api.Action{
// Like thread
LikeAction(),
// Unlike thread
UnlikeAction(),
// Lock thread
LockAction(),
// Unlock thread
UnlockAction(),
})
}
// Authorize returns an error if the given API POST request is not authorized.
func (thread *Thread) Authorize(ctx aero.Context, action string) error {
if !ctx.HasSession() {
return errors.New("Neither logged in nor in session")
}
if action == "edit" {
user := GetUserFromContext(ctx)
if thread.CreatedBy != user.ID && user.Role != "admin" {
return errors.New("Can't edit the threads of other users")
}
}
return nil
}
// Create sets the data for a new thread with data we received from the API request.
func (thread *Thread) Create(ctx aero.Context) error {
data, err := ctx.Request().Body().JSONObject()
if err != nil {
return err
}
userID, ok := ctx.Session().Get("userId").(string)
if !ok || userID == "" {
return errors.New("Not logged in")
}
user, err := GetUser(userID)
if err != nil {
return err
}
thread.ID = GenerateID("Thread")
thread.Title, _ = data["title"].(string)
thread.Text, _ = data["text"].(string)
thread.CreatedBy = user.ID
thread.Sticky, _ = data["sticky"].(int)
thread.Created = DateTimeUTC()
thread.Edited = ""
// Post-process text
thread.Title = autocorrect.ThreadTitle(thread.Title)
thread.Text = autocorrect.PostText(thread.Text)
// Tags
tags, _ := data["tags"].([]interface{})
thread.Tags = make([]string, len(tags))
for i := range thread.Tags {
thread.Tags[i] = tags[i].(string)
}
if len(tags) < 1 {
return errors.New("Need to specify at least one tag")
}
if len(thread.Title) < 10 {
return errors.New("Title too short: Should be at least 10 characters")
}
if len(thread.Text) < 10 {
return errors.New("Text too short: Should be at least 10 characters")
}
// Write log entry
logEntry := NewEditLogEntry(user.ID, "create", "Thread", thread.ID, "", "", "")
logEntry.Save()
// Create activity
activity := NewActivityCreate("Thread", thread.ID, user.ID)
activity.Save()
// Broadcast event to all users so they can reload the activity page if needed
for receiver := range StreamUsers() {
activityEvent := event.New("post activity", receiver.IsFollowing(user.ID))
receiver.BroadcastEvent(activityEvent)
}
return nil
}
// Edit creates an edit log entry.
func (thread *Thread) Edit(ctx aero.Context, key string, value reflect.Value, newValue reflect.Value) (consumed bool, err error) {
switch key {
case "Tags":
newTags := newValue.Interface().([]string)
if len(newTags) < 1 {
return true, errors.New("Need to specify at least one tag")
}
case "Title":
newTitle := newValue.String()
if len(newTitle) < 10 {
return true, errors.New("Title too short: Should be at least 10 characters")
}
case "Text":
newText := newValue.String()
if len(newText) < 10 {
return true, errors.New("Text too short: Should be at least 10 characters")
}
}
return edit(thread, ctx, key, value, newValue)
}
// OnAppend saves a log entry.
func (thread *Thread) OnAppend(ctx aero.Context, key string, index int, obj interface{}) {
onAppend(thread, ctx, key, index, obj)
}
// OnRemove saves a log entry.
func (thread *Thread) OnRemove(ctx aero.Context, key string, index int, obj interface{}) {
onRemove(thread, ctx, key, index, obj)
}
// AfterEdit sets the edited date on the thread object.
func (thread *Thread) AfterEdit(ctx aero.Context) error {
thread.Edited = DateTimeUTC()
thread.html = markdown.Render(thread.Text)
return nil
}
// Save saves the thread object in the database.
func (thread *Thread) Save() {
DB.Set("Thread", thread.ID, thread)
}
// DeleteInContext deletes the thread in the given context.
func (thread *Thread) DeleteInContext(ctx aero.Context) error {
user := GetUserFromContext(ctx)
// Write log entry
logEntry := NewEditLogEntry(user.ID, "delete", "Thread", thread.ID, "", fmt.Sprint(thread), "")
logEntry.Save()
return thread.Delete()
}
// Delete deletes the thread and its posts from the database.
func (thread *Thread) Delete() error {
for _, post := range thread.Posts() {
err := post.Delete()
if err != nil {
return err
}
}
// Remove activities
for activity := range StreamActivityCreates() {
if activity.ObjectID == thread.ID && activity.ObjectType == "Thread" {
err := activity.Delete()
if err != nil {
return err
}
}
}
DB.Delete("Thread", thread.ID)
return nil
}