Change Logs
* Add a ChangeLog table to collect historic updates to various database tables.
* Created, Updated (with field diffs) and Deleted actions are logged, as well
as certification photo approves/denies.
* Specific items added to the change log:
* When a user photo is marked Explicit by an admin
* When users block/unblock each other
* When photo comments are posted, edited, and deleted
* When forums are created, edited, and deleted
* When forum comments are created, edited and deleted
* When a new forum thread is created
* When a user uploads or removes their own certification photo
* When an admin approves or rejects a certification photo
* When a user uploads, modifies or deletes their gallery photos
* When a friend request is sent
* When a friend request is accepted, ignored, or rejected
* When a friendship is removed
2024-02-26 01:03:36 +00:00
|
|
|
package models
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"sort"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"code.nonshy.com/nonshy/website/pkg/log"
|
|
|
|
)
|
|
|
|
|
|
|
|
// ChangeLog table to track updates to the database.
|
|
|
|
type ChangeLog struct {
|
|
|
|
ID uint64 `gorm:"primaryKey"`
|
|
|
|
AboutUserID uint64 `gorm:"index"`
|
|
|
|
AdminUserID uint64 `gorm:"index"` // if an admin edits a user's item
|
|
|
|
TableName string `gorm:"index"`
|
|
|
|
TableID uint64 `gorm:"index"`
|
|
|
|
Event string `gorm:"index"`
|
|
|
|
Message string
|
|
|
|
CreatedAt time.Time
|
|
|
|
UpdatedAt time.Time
|
|
|
|
}
|
|
|
|
|
|
|
|
// Types of ChangeLog events.
|
|
|
|
const (
|
|
|
|
ChangeLogCreated = "created"
|
|
|
|
ChangeLogUpdated = "updated"
|
|
|
|
ChangeLogDeleted = "deleted"
|
|
|
|
|
|
|
|
// Certification photos.
|
|
|
|
ChangeLogApproved = "approved"
|
|
|
|
ChangeLogRejected = "rejected"
|
2024-02-26 01:36:01 +00:00
|
|
|
|
|
|
|
// Account status updates for easier filtering.
|
|
|
|
ChangeLogBanned = "banned"
|
|
|
|
ChangeLogAdmin = "admin" // admin status toggle
|
|
|
|
ChangeLogLifecycle = "lifecycle" // de/reactivated accounts
|
Change Logs
* Add a ChangeLog table to collect historic updates to various database tables.
* Created, Updated (with field diffs) and Deleted actions are logged, as well
as certification photo approves/denies.
* Specific items added to the change log:
* When a user photo is marked Explicit by an admin
* When users block/unblock each other
* When photo comments are posted, edited, and deleted
* When forums are created, edited, and deleted
* When forum comments are created, edited and deleted
* When a new forum thread is created
* When a user uploads or removes their own certification photo
* When an admin approves or rejects a certification photo
* When a user uploads, modifies or deletes their gallery photos
* When a friend request is sent
* When a friend request is accepted, ignored, or rejected
* When a friendship is removed
2024-02-26 01:03:36 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var ChangeLogEventTypes = []string{
|
|
|
|
ChangeLogCreated,
|
|
|
|
ChangeLogUpdated,
|
|
|
|
ChangeLogDeleted,
|
|
|
|
ChangeLogApproved,
|
|
|
|
ChangeLogRejected,
|
2024-02-26 01:36:01 +00:00
|
|
|
ChangeLogBanned,
|
|
|
|
ChangeLogAdmin,
|
|
|
|
ChangeLogLifecycle,
|
Change Logs
* Add a ChangeLog table to collect historic updates to various database tables.
* Created, Updated (with field diffs) and Deleted actions are logged, as well
as certification photo approves/denies.
* Specific items added to the change log:
* When a user photo is marked Explicit by an admin
* When users block/unblock each other
* When photo comments are posted, edited, and deleted
* When forums are created, edited, and deleted
* When forum comments are created, edited and deleted
* When a new forum thread is created
* When a user uploads or removes their own certification photo
* When an admin approves or rejects a certification photo
* When a user uploads, modifies or deletes their gallery photos
* When a friend request is sent
* When a friend request is accepted, ignored, or rejected
* When a friendship is removed
2024-02-26 01:03:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// PaginateChangeLog lists the change logs.
|
|
|
|
func PaginateChangeLog(tableName string, tableID, aboutUserID, adminUserID uint64, event string, pager *Pagination) ([]*ChangeLog, error) {
|
|
|
|
var (
|
|
|
|
cl = []*ChangeLog{}
|
|
|
|
where = []string{}
|
|
|
|
placeholders = []interface{}{}
|
|
|
|
)
|
|
|
|
|
|
|
|
if tableName != "" {
|
|
|
|
where = append(where, "table_name = ?")
|
|
|
|
placeholders = append(placeholders, tableName)
|
|
|
|
}
|
|
|
|
|
|
|
|
if tableID != 0 {
|
|
|
|
where = append(where, "table_id = ?")
|
|
|
|
placeholders = append(placeholders, tableID)
|
|
|
|
}
|
|
|
|
|
|
|
|
if aboutUserID != 0 {
|
|
|
|
where = append(where, "about_user_id = ?")
|
|
|
|
placeholders = append(placeholders, aboutUserID)
|
|
|
|
}
|
|
|
|
|
|
|
|
if adminUserID != 0 {
|
|
|
|
where = append(where, "admin_user_id = ?")
|
|
|
|
placeholders = append(placeholders, adminUserID)
|
|
|
|
}
|
|
|
|
|
|
|
|
if event != "" {
|
|
|
|
where = append(where, "event = ?")
|
|
|
|
placeholders = append(placeholders, event)
|
|
|
|
}
|
|
|
|
|
|
|
|
query := DB.Model(&ChangeLog{}).Where(
|
|
|
|
strings.Join(where, " AND "),
|
|
|
|
placeholders...,
|
|
|
|
).Order(
|
|
|
|
pager.Sort,
|
|
|
|
)
|
|
|
|
|
|
|
|
query.Count(&pager.Total)
|
|
|
|
result := query.Offset(pager.GetOffset()).Limit(pager.PerPage).Find(&cl)
|
|
|
|
return cl, result.Error
|
|
|
|
}
|
|
|
|
|
|
|
|
// ChangeLogTables returns all the distinct table_names appearing in the change log.
|
|
|
|
func ChangeLogTables() []string {
|
|
|
|
var result = []string{}
|
|
|
|
|
|
|
|
query := DB.Model(&ChangeLog{}).
|
|
|
|
Select("DISTINCT change_logs.table_name").
|
|
|
|
Group("change_logs.table_name").
|
|
|
|
Find(&result)
|
|
|
|
if query.Error != nil {
|
|
|
|
log.Error("ChangeLogTables: %s", query.Error)
|
|
|
|
}
|
|
|
|
|
|
|
|
sort.Strings(result)
|
|
|
|
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
// LogEvent puts in a generic/miscellaneous change log event (e.g. certification photo updates).
|
|
|
|
func LogEvent(aboutUser, adminUser *User, event, tableName string, tableID uint64, message string) (*ChangeLog, error) {
|
|
|
|
cl := &ChangeLog{
|
|
|
|
TableName: tableName,
|
|
|
|
TableID: tableID,
|
|
|
|
Event: event,
|
|
|
|
Message: message,
|
|
|
|
}
|
|
|
|
|
|
|
|
if aboutUser != nil {
|
|
|
|
cl.AboutUserID = aboutUser.ID
|
|
|
|
}
|
|
|
|
|
|
|
|
if adminUser != nil && adminUser != aboutUser {
|
|
|
|
cl.AdminUserID = adminUser.ID
|
|
|
|
}
|
|
|
|
|
|
|
|
result := DB.Create(cl)
|
|
|
|
return cl, result.Error
|
|
|
|
}
|
|
|
|
|
|
|
|
// LogCreated puts in a ChangeLog "created" event.
|
|
|
|
func LogCreated(aboutUser *User, tableName string, tableID uint64, message string) (*ChangeLog, error) {
|
|
|
|
cl := &ChangeLog{
|
|
|
|
TableName: tableName,
|
|
|
|
TableID: tableID,
|
|
|
|
Event: ChangeLogCreated,
|
|
|
|
Message: message,
|
|
|
|
}
|
|
|
|
|
|
|
|
if aboutUser != nil {
|
|
|
|
cl.AboutUserID = aboutUser.ID
|
|
|
|
}
|
|
|
|
|
|
|
|
result := DB.Create(cl)
|
|
|
|
return cl, result.Error
|
|
|
|
}
|
|
|
|
|
|
|
|
// LogDeleted puts in a ChangeLog "deleted" event.
|
|
|
|
func LogDeleted(aboutUser, adminUser *User, tableName string, tableID uint64, message string, original interface{}) (*ChangeLog, error) {
|
|
|
|
// If the original model is given, JSON serialize it nicely.
|
|
|
|
if original != nil {
|
|
|
|
w := bytes.NewBuffer([]byte{})
|
|
|
|
enc := json.NewEncoder(w)
|
|
|
|
enc.SetIndent("\n", "* ")
|
|
|
|
if err := enc.Encode(original); err != nil {
|
|
|
|
log.Error("LogDeleted(%s %d): couldn't encode original model to JSON: %s", tableName, tableID, err)
|
|
|
|
} else {
|
|
|
|
message += "\n\n" + w.String()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
cl := &ChangeLog{
|
|
|
|
TableName: tableName,
|
|
|
|
TableID: tableID,
|
|
|
|
Event: ChangeLogDeleted,
|
|
|
|
Message: message,
|
|
|
|
}
|
|
|
|
|
|
|
|
if aboutUser != nil {
|
|
|
|
cl.AboutUserID = aboutUser.ID
|
|
|
|
}
|
|
|
|
|
|
|
|
if adminUser != nil && adminUser != aboutUser {
|
|
|
|
cl.AdminUserID = adminUser.ID
|
|
|
|
}
|
|
|
|
|
|
|
|
result := DB.Create(cl)
|
|
|
|
return cl, result.Error
|
|
|
|
}
|
|
|
|
|
|
|
|
type FieldDiff struct {
|
|
|
|
Key string
|
|
|
|
Before interface{}
|
|
|
|
After interface{}
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewFieldDiff(key string, before, after interface{}) FieldDiff {
|
|
|
|
return FieldDiff{
|
|
|
|
Key: key,
|
|
|
|
Before: before,
|
|
|
|
After: after,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// LogUpdated puts in a ChangeLog "updated" event.
|
|
|
|
func LogUpdated(aboutUser, adminUser *User, tableName string, tableID uint64, message string, diffs []FieldDiff) (*ChangeLog, error) {
|
|
|
|
// Append field diffs to the message?
|
|
|
|
lines := []string{message}
|
|
|
|
if len(diffs) > 0 {
|
|
|
|
lines = append(lines, "")
|
|
|
|
for _, row := range diffs {
|
|
|
|
var (
|
|
|
|
before = fmt.Sprintf("%v", row.Before)
|
|
|
|
after = fmt.Sprintf("%v", row.After)
|
|
|
|
)
|
|
|
|
|
|
|
|
if before != after {
|
|
|
|
lines = append(lines,
|
|
|
|
fmt.Sprintf("* **%s** changed to <code>%s</code> from <code>%s</code>",
|
|
|
|
row.Key,
|
|
|
|
strings.ReplaceAll(after, "`", "'"),
|
|
|
|
strings.ReplaceAll(before, "`", "'"),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
cl := &ChangeLog{
|
|
|
|
TableName: tableName,
|
|
|
|
TableID: tableID,
|
|
|
|
Event: ChangeLogUpdated,
|
|
|
|
Message: strings.Join(lines, "\n"),
|
|
|
|
}
|
|
|
|
|
|
|
|
if aboutUser != nil {
|
|
|
|
cl.AboutUserID = aboutUser.ID
|
|
|
|
}
|
|
|
|
|
|
|
|
if adminUser != nil && adminUser != aboutUser {
|
|
|
|
cl.AdminUserID = adminUser.ID
|
|
|
|
}
|
|
|
|
|
|
|
|
result := DB.Create(cl)
|
|
|
|
return cl, result.Error
|
|
|
|
}
|