Implemented developer database downloads

This commit is contained in:
Eduard Urbach 2019-09-07 19:56:13 +09:00
parent 59b9be5992
commit 4e6aa5eef6
Signed by: akyoto
GPG Key ID: C874F672B1AF20C0
33 changed files with 155 additions and 46 deletions

View File

@ -4,7 +4,7 @@ import "github.com/aerogo/nano"
// Analytics stores user-related statistics. // Analytics stores user-related statistics.
type Analytics struct { type Analytics struct {
UserID string `json:"userId"` UserID string `json:"userId" primary:"true"`
General GeneralAnalytics `json:"general"` General GeneralAnalytics `json:"general"`
Screen ScreenAnalytics `json:"screen"` Screen ScreenAnalytics `json:"screen"`
System SystemAnalytics `json:"system"` System SystemAnalytics `json:"system"`

View File

@ -67,7 +67,7 @@ type AnimeID = string
// Anime represents an anime. // Anime represents an anime.
type Anime struct { type Anime struct {
ID AnimeID `json:"id"` ID AnimeID `json:"id" primary:"true"`
Type string `json:"type" editable:"true" datalist:"anime-types"` 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"` Summary string `json:"summary" editable:"true" type:"textarea"`

View File

@ -10,7 +10,7 @@ import (
// AnimeCharacters is a list of characters for an anime. // AnimeCharacters is a list of characters for an anime.
type AnimeCharacters struct { type AnimeCharacters struct {
AnimeID AnimeID `json:"animeId" mainID:"true"` AnimeID AnimeID `json:"animeId" primary:"true"`
Items []*AnimeCharacter `json:"items" editable:"true"` Items []*AnimeCharacter `json:"items" editable:"true"`
sync.Mutex sync.Mutex

View File

@ -11,7 +11,7 @@ import (
// AnimeList is a list of anime list items. // AnimeList is a list of anime list items.
type AnimeList struct { type AnimeList struct {
UserID UserID `json:"userId"` UserID UserID `json:"userId" primary:"true"`
Items []*AnimeListItem `json:"items"` Items []*AnimeListItem `json:"items"`
sync.Mutex sync.Mutex

View File

@ -9,7 +9,7 @@ import (
// AnimeRelations is a list of relations for an anime. // AnimeRelations is a list of relations for an anime.
type AnimeRelations struct { type AnimeRelations struct {
AnimeID AnimeID `json:"animeId" mainID:"true"` AnimeID AnimeID `json:"animeId" primary:"true"`
Items []*AnimeRelation `json:"items" editable:"true"` Items []*AnimeRelation `json:"items" editable:"true"`
sync.Mutex sync.Mutex

View File

@ -4,13 +4,13 @@ import "github.com/aerogo/nano"
// ClientErrorReport saves JavaScript errors that happen in web clients like browsers. // ClientErrorReport saves JavaScript errors that happen in web clients like browsers.
type ClientErrorReport struct { type ClientErrorReport struct {
ID string `json:"id"`
Message string `json:"message"` Message string `json:"message"`
Stack string `json:"stack"` Stack string `json:"stack"`
FileName string `json:"fileName"` FileName string `json:"fileName"`
LineNumber int `json:"lineNumber"` LineNumber int `json:"lineNumber"`
ColumnNumber int `json:"columnNumber"` ColumnNumber int `json:"columnNumber"`
hasID
hasCreator hasCreator
} }

View File

@ -7,7 +7,7 @@ import (
// DraftIndex has references to unpublished drafts a user created. // DraftIndex has references to unpublished drafts a user created.
type DraftIndex struct { type DraftIndex struct {
UserID string `json:"userId"` UserID string `json:"userId" primary:"true"`
GroupID string `json:"groupId"` GroupID string `json:"groupId"`
SoundTrackID string `json:"soundTrackId"` SoundTrackID string `json:"soundTrackId"`
CompanyID string `json:"companyId"` CompanyID string `json:"companyId"`

View File

@ -9,7 +9,7 @@ import (
// EditLogEntry is an entry in the editor log. // EditLogEntry is an entry in the editor log.
type EditLogEntry struct { type EditLogEntry struct {
ID string `json:"id"` ID string `json:"id" primary:"true"`
UserID string `json:"userId"` UserID string `json:"userId"`
Action string `json:"action"` Action string `json:"action"`
ObjectType string `json:"objectType"` // The typename of what was edited ObjectType string `json:"objectType"` // The typename of what was edited

View File

@ -2,6 +2,6 @@ package arn
// EmailToUser stores the user ID for an email address. // EmailToUser stores the user ID for an email address.
type EmailToUser struct { type EmailToUser struct {
Email string `json:"email"` Email string `json:"email" primary:"true"`
UserID UserID `json:"userId"` UserID UserID `json:"userId"`
} }

View File

@ -9,7 +9,7 @@ import (
// Episode represents a single episode for an anime. // Episode represents a single episode for an anime.
type Episode struct { type Episode struct {
ID string `json:"id"` ID string `json:"id" primary:"true"`
AnimeID AnimeID `json:"animeId"` AnimeID AnimeID `json:"animeId"`
Number int `json:"number" editable:"true"` Number int `json:"number" editable:"true"`
Title EpisodeTitle `json:"title" editable:"true"` Title EpisodeTitle `json:"title" editable:"true"`

View File

@ -2,6 +2,6 @@ package arn
// GoogleToUser stores the user ID by Google user ID. // GoogleToUser stores the user ID by Google user ID.
type GoogleToUser struct { type GoogleToUser struct {
ID string `json:"id"` ID string `json:"id" primary:"true"`
UserID UserID `json:"userId"` UserID UserID `json:"userId"`
} }

View File

@ -2,7 +2,7 @@ package arn
// hasID includes an object ID. // hasID includes an object ID.
type hasID struct { type hasID struct {
ID string `json:"id"` ID string `json:"id" primary:"true"`
} }
// GetID returns the ID. // GetID returns the ID.

6
arn/Identifiable.go Normal file
View File

@ -0,0 +1,6 @@
package arn
// Identifiable applies to any type that has an ID and exposes it via GetID.
type Identifiable interface {
GetID() string
}

View File

@ -11,10 +11,10 @@ const IgnoreAnimeDifferenceEditorScore = 2
// IgnoreAnimeDifference saves which differences between anime databases can be ignored. // IgnoreAnimeDifference saves which differences between anime databases can be ignored.
type IgnoreAnimeDifference struct { type IgnoreAnimeDifference struct {
// The ID is built like this: arn:323|mal:356|JapaneseTitle
ID string `json:"id"`
ValueHash uint64 `json:"valueHash"` ValueHash uint64 `json:"valueHash"`
// The ID is built like this: arn:323|mal:356|JapaneseTitle
hasID
hasCreator hasCreator
} }

View File

@ -9,7 +9,7 @@ const DefaultInventorySlotCount = 24
// Inventory has inventory slots that store shop item IDs and their quantity. // Inventory has inventory slots that store shop item IDs and their quantity.
type Inventory struct { type Inventory struct {
UserID UserID `json:"userId"` UserID UserID `json:"userId" primary:"true"`
Slots []*InventorySlot `json:"slots"` Slots []*InventorySlot `json:"slots"`
} }

View File

@ -9,7 +9,7 @@ import (
// Loggable applies to any type that has a TypeName function. // Loggable applies to any type that has a TypeName function.
type Loggable interface { type Loggable interface {
GetID() string Identifiable
TypeName() string TypeName() string
Self() Loggable Self() Loggable
} }

View File

@ -2,6 +2,6 @@ package arn
// NickToUser stores the user ID by nickname. // NickToUser stores the user ID by nickname.
type NickToUser struct { type NickToUser struct {
Nick string `json:"nick"` Nick string `json:"nick" primary:"true"`
UserID UserID `json:"userId"` UserID UserID `json:"userId"`
} }

View File

@ -9,10 +9,11 @@ import (
// Notification represents a user-associated notification. // Notification represents a user-associated notification.
type Notification struct { type Notification struct {
ID string `json:"id"`
UserID string `json:"userId"` UserID string `json:"userId"`
Created string `json:"created"` Created string `json:"created"`
Seen string `json:"seen"` Seen string `json:"seen"`
hasID
PushNotification PushNotification
} }
@ -36,7 +37,9 @@ func (notification *Notification) String() string {
// NewNotification creates a new notification. // NewNotification creates a new notification.
func NewNotification(userID UserID, pushNotification *PushNotification) *Notification { func NewNotification(userID UserID, pushNotification *PushNotification) *Notification {
return &Notification{ return &Notification{
hasID: hasID{
ID: GenerateID("Notification"), ID: GenerateID("Notification"),
},
UserID: userID, UserID: userID,
Created: DateTimeUTC(), Created: DateTimeUTC(),
Seen: "", Seen: "",

View File

@ -8,13 +8,29 @@ import (
// PayPalPayment is an approved and exeucted PayPal payment. // PayPalPayment is an approved and exeucted PayPal payment.
type PayPalPayment struct { type PayPalPayment struct {
ID string `json:"id"`
UserID string `json:"userId"` UserID string `json:"userId"`
PayerID string `json:"payerId"` PayerID string `json:"payerId"`
Amount string `json:"amount"` Amount string `json:"amount"`
Currency string `json:"currency"` Currency string `json:"currency"`
Method string `json:"method"` Method string `json:"method"`
Created string `json:"created"` Created string `json:"created"`
hasID
}
// NewPayPalPayment creates a new PayPalPayment object with the paypal provided ID.
func NewPayPalPayment(paymentID, payerID, userID, method, amount, currency string) *PayPalPayment {
return &PayPalPayment{
hasID: hasID{
ID: paymentID,
},
PayerID: payerID,
UserID: userID,
Method: method,
Amount: amount,
Currency: currency,
Created: DateTimeUTC(),
}
} }
// Gems returns the total amount of gems. // Gems returns the total amount of gems.

View File

@ -4,13 +4,14 @@ import "github.com/aerogo/nano"
// Purchase represents an item purchase by a user. // Purchase represents an item purchase by a user.
type Purchase struct { type Purchase struct {
ID string `json:"id"`
UserID string `json:"userId"` UserID string `json:"userId"`
ItemID string `json:"itemId"` ItemID string `json:"itemId"`
Quantity int `json:"quantity"` Quantity int `json:"quantity"`
Price int `json:"price"` Price int `json:"price"`
Currency string `json:"currency"` Currency string `json:"currency"`
Date string `json:"date"` Date string `json:"date"`
hasID
} }
// Item returns the item the user bought. // Item returns the item the user bought.
@ -28,7 +29,9 @@ func (purchase *Purchase) User() *User {
// NewPurchase creates a new Purchase object with a generated ID. // NewPurchase creates a new Purchase object with a generated ID.
func NewPurchase(userID UserID, itemID string, quantity int, price int, currency string) *Purchase { func NewPurchase(userID UserID, itemID string, quantity int, price int, currency string) *Purchase {
return &Purchase{ return &Purchase{
hasID: hasID{
ID: GenerateID("Purchase"), ID: GenerateID("Purchase"),
},
UserID: userID, UserID: userID,
ItemID: itemID, ItemID: itemID,
Quantity: quantity, Quantity: quantity,

View File

@ -4,7 +4,7 @@ import "errors"
// PushSubscriptions is a list of push subscriptions made by a user. // PushSubscriptions is a list of push subscriptions made by a user.
type PushSubscriptions struct { type PushSubscriptions struct {
UserID UserID `json:"userId"` UserID UserID `json:"userId" primary:"true"`
Items []*PushSubscription `json:"items"` Items []*PushSubscription `json:"items"`
} }

View File

@ -29,7 +29,7 @@ const (
// Settings represents user settings. // Settings represents user settings.
type Settings struct { type Settings struct {
UserID string `json:"userId"` UserID string `json:"userId" primary:"true"`
SortBy string `json:"sortBy" editable:"true"` SortBy string `json:"sortBy" editable:"true"`
TitleLanguage string `json:"titleLanguage" editable:"true"` TitleLanguage string `json:"titleLanguage" editable:"true"`
Providers ServiceProviders `json:"providers"` Providers ServiceProviders `json:"providers"`
@ -162,6 +162,11 @@ func GetSettings(userID UserID) (*Settings, error) {
return obj.(*Settings), nil return obj.(*Settings), nil
} }
// GetID returns the ID.
func (settings *Settings) GetID() string {
return settings.UserID
}
// User returns the user object for the settings. // User returns the user object for the settings.
func (settings *Settings) User() *User { func (settings *Settings) User() *User {
user, _ := GetUser(settings.UserID) user, _ := GetUser(settings.UserID)

View File

@ -10,6 +10,7 @@ import (
// Force interface implementations // Force interface implementations
var ( var (
_ Identifiable = (*Settings)(nil)
_ api.Editable = (*Settings)(nil) _ api.Editable = (*Settings)(nil)
_ api.Filter = (*Settings)(nil) _ api.Filter = (*Settings)(nil)
) )

View File

@ -21,7 +21,6 @@ const (
// ShopItem is a purchasable item in the shop. // ShopItem is a purchasable item in the shop.
type ShopItem struct { type ShopItem struct {
ID string `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Description string `json:"description"` Description string `json:"description"`
Price uint `json:"price"` Price uint `json:"price"`
@ -29,6 +28,8 @@ type ShopItem struct {
Rarity string `json:"rarity"` Rarity string `json:"rarity"`
Order int `json:"order"` Order int `json:"order"`
Consumable bool `json:"consumable"` Consumable bool `json:"consumable"`
hasID
} }
// GetShopItem ... // GetShopItem ...

View File

@ -37,7 +37,7 @@ type UserID = string
// User is a registered person. // User is a registered person.
type User struct { type User struct {
ID UserID `json:"id"` ID UserID `json:"id" primary:"true"`
Nick string `json:"nick" editable:"true"` Nick string `json:"nick" editable:"true"`
FirstName string `json:"firstName" private:"true"` FirstName string `json:"firstName" private:"true"`
LastName string `json:"lastName" private:"true"` LastName string `json:"lastName" private:"true"`

View File

@ -8,7 +8,7 @@ import (
// UserFollows is a list including IDs to users you follow. // UserFollows is a list including IDs to users you follow.
type UserFollows struct { type UserFollows struct {
UserID UserID `json:"userId"` UserID UserID `json:"userId" primary:"true"`
Items []string `json:"items"` Items []string `json:"items"`
} }

View File

@ -8,7 +8,7 @@ import (
// UserNotifications is a list including IDs to your notifications. // UserNotifications is a list including IDs to your notifications.
type UserNotifications struct { type UserNotifications struct {
UserID UserID `json:"userId"` UserID UserID `json:"userId" primary:"true"`
Items []string `json:"items"` Items []string `json:"items"`
} }

1
go.mod
View File

@ -49,6 +49,7 @@ require (
github.com/mailgun/mailgun-go/v3 v3.6.0 github.com/mailgun/mailgun-go/v3 v3.6.0
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e // indirect github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e // indirect
github.com/minio/minio-go/v6 v6.0.34 github.com/minio/minio-go/v6 v6.0.34
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
github.com/mssola/user_agent v0.5.0 github.com/mssola/user_agent v0.5.0
github.com/pariz/gountries v0.0.0-20171019111738-adb00f6513a3 github.com/pariz/gountries v0.0.0-20171019111738-adb00f6513a3
github.com/shirou/gopsutil v2.18.12+incompatible github.com/shirou/gopsutil v2.18.12+incompatible

View File

@ -1,10 +1,90 @@
package database package database
import ( import (
"encoding/json"
"fmt"
"io"
"net/http"
"github.com/aerogo/aero" "github.com/aerogo/aero"
"github.com/aerogo/api"
"github.com/akyoto/stringutils/unsafe"
"github.com/animenotifier/notify.moe/arn"
"github.com/mohae/deepcopy"
) )
// privateTypes are types that are not available for download.
var privateTypes = []string{
"EditLogEntry",
"EmailToUser",
"PayPalPayment",
"Purchase",
"Session",
}
// Download downloads a snapshot of a database collection. // Download downloads a snapshot of a database collection.
func Download(ctx aero.Context) error { func Download(ctx aero.Context) error {
return nil typ := ctx.Get("type")
if !arn.DB.HasType(typ) {
return ctx.Error(http.StatusNotFound, "Type doesn't exist")
}
if arn.Contains(privateTypes, typ) {
return ctx.Error(http.StatusUnauthorized, "Type is private and can not be downloaded")
}
// Send headers necessary for file downloads
ctx.Response().SetHeader("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.dat"`, typ))
// Stream data
reader, writer := io.Pipe()
encoder := json.NewEncoder(writer)
go func() {
for object := range arn.DB.All(typ) {
idObject, hasID := object.(arn.Identifiable)
if !hasID {
continue
}
// Filter out private data
filter, isFilter := object.(api.Filter)
if isFilter && filter.ShouldFilter(ctx) {
object = deepcopy.Copy(object)
filter = object.(api.Filter)
filter.Filter()
}
// Write ID
_, err := writer.Write(unsafe.StringToBytes(idObject.GetID()))
if err != nil {
_ = writer.CloseWithError(err)
return
}
// Write newline
_, err = writer.Write([]byte("\n"))
if err != nil {
_ = writer.CloseWithError(err)
return
}
// Write JSON (newline included)
err = encoder.Encode(object)
if err != nil {
_ = writer.CloseWithError(err)
return
}
}
writer.Close()
}()
return ctx.Reader(reader)
} }

View File

@ -13,6 +13,10 @@ func Types(ctx aero.Context) error {
types := make([]string, 0, len(typeMap)) types := make([]string, 0, len(typeMap))
for typeName := range typeMap { for typeName := range typeMap {
if arn.Contains(privateTypes, typeName) {
continue
}
types = append(types, typeName) types = append(types, typeName)
} }

View File

@ -54,7 +54,7 @@ func Register(app *aero.Application) {
// Types // Types
app.Get("/api/types", database.Types) app.Get("/api/types", database.Types)
app.Get("/api/types/:type/all", database.Download) app.Get("/api/types/:type/download", database.Download)
// SoundTrack // SoundTrack
app.Post("/api/soundtrack/:id/download", soundtrack.Download) app.Post("/api/soundtrack/:id/download", soundtrack.Download)

View File

@ -63,19 +63,8 @@ func Success(ctx aero.Context) error {
} }
stringutils.PrettyPrint(sdkPayment) stringutils.PrettyPrint(sdkPayment)
transaction := sdkPayment.Transactions[0] transaction := sdkPayment.Transactions[0]
payment := arn.NewPayPalPayment(paymentID, payerID, user.ID, sdkPayment.Payer.PaymentMethod, transaction.Amount.Total, transaction.Amount.Currency)
payment := &arn.PayPalPayment{
ID: paymentID,
PayerID: payerID,
UserID: user.ID,
Method: sdkPayment.Payer.PaymentMethod,
Amount: transaction.Amount.Total,
Currency: transaction.Amount.Currency,
Created: arn.DateTimeUTC(),
}
payment.Save() payment.Save()
// Increase user's balance // Increase user's balance

View File

@ -18,7 +18,7 @@ import (
func Render(obj interface{}, title string, user *arn.User) string { func Render(obj interface{}, title string, user *arn.User) string {
t := reflect.TypeOf(obj).Elem() t := reflect.TypeOf(obj).Elem()
v := reflect.ValueOf(obj).Elem() v := reflect.ValueOf(obj).Elem()
id := findMainID(t, v) id := findPrimaryID(t, v)
lowerCaseTypeName := strings.ToLower(t.Name()) lowerCaseTypeName := strings.ToLower(t.Name())
endpoint := `/api/` + lowerCaseTypeName + `/` + id.String() endpoint := `/api/` + lowerCaseTypeName + `/` + id.String()
@ -319,8 +319,8 @@ func renderSliceField(b *strings.Builder, field reflect.StructField, idPrefix st
b.WriteString(`</div>`) b.WriteString(`</div>`)
} }
// findMainID finds the main ID of the object. // findPrimaryID finds the primary ID of the object.
func findMainID(t reflect.Type, v reflect.Value) reflect.Value { func findPrimaryID(t reflect.Type, v reflect.Value) reflect.Value {
idField := v.FieldByName("ID") idField := v.FieldByName("ID")
if idField.IsValid() { if idField.IsValid() {
@ -330,7 +330,7 @@ func findMainID(t reflect.Type, v reflect.Value) reflect.Value {
for i := 0; i < t.NumField(); i++ { for i := 0; i < t.NumField(); i++ {
field := t.Field(i) field := t.Field(i)
if field.Tag.Get("mainID") == "true" { if field.Tag.Get("primary") == "true" {
return reflect.Indirect(v.Field(i)) return reflect.Indirect(v.Field(i))
} }
} }