Albirew/nyaa-pantsu
Albirew
/
nyaa-pantsu
Archivé
1
0
Bifurcation 0

Start refactor

Cette révision appartient à :
PantsuDev 2017-06-25 23:26:46 +10:00
Parent 006dc1c034
révision 496e1d4ba4
75 fichiers modifiés avec 9 ajouts et 4318 suppressions

33
cache/cache.go externe
Voir le fichier

@ -1,33 +0,0 @@
package cache
import (
"github.com/NyaaPantsu/nyaa/cache/memcache"
"github.com/NyaaPantsu/nyaa/cache/nop"
"github.com/NyaaPantsu/nyaa/common"
"github.com/NyaaPantsu/nyaa/config"
"github.com/NyaaPantsu/nyaa/model"
)
// Cache defines interface for caching search results
type Cache interface {
Get(key common.SearchParam, r func() ([]model.Torrent, int, error)) ([]model.Torrent, int, error)
ClearAll()
}
// Impl cache implementation instance
var Impl Cache
func Configure(conf *config.CacheConfig) (err error) {
switch conf.Dialect {
/*case "native":
Impl = native.New(conf.Size)
return
*/
case "memcache":
Impl = memcache.New()
return
default:
Impl = nop.New()
}
return
}

Voir le fichier

@ -1,21 +0,0 @@
package memcache
import (
"github.com/NyaaPantsu/nyaa/common"
"github.com/NyaaPantsu/nyaa/model"
)
type Memcache struct {
}
func (c *Memcache) Get(key common.SearchParam, r func() ([]model.Torrent, int, error)) (torrents []model.Torrent, num int, err error) {
return
}
func (c *Memcache) ClearAll() {
}
func New() *Memcache {
return &Memcache{}
}

Voir le fichier

@ -1,145 +0,0 @@
package native
/*
import (
"container/list"
"sync"
"time"
"github.com/NyaaPantsu/nyaa/common"
"github.com/NyaaPantsu/nyaa/model"
)
const expiryTime = time.Minute
// NativeCache implements cache.Cache
type NativeCache struct {
cache map[common.SearchParam]*list.Element
ll *list.List
totalUsed int
mu sync.Mutex
// Size sets the maximum size of the cache before evicting unread data in MB
Size float64
}
// New Creates New Native Cache instance
func New(sz float64) *NativeCache {
return &NativeCache{
cache: make(map[common.SearchParam]*list.Element, 10),
Size: sz,
ll: list.New(),
}
}
// Key stores the ID of either a thread or board page
type Key struct {
LastN uint8
Board string
ID uint64
}
// Single cache entry
type store struct {
sync.Mutex // Controls general access to the contents of the struct
lastFetched time.Time
key common.SearchParam
data []model.Torrent
count, size int
n *NativeCache
}
// Check the cache for and existing record. If miss, run fn to retrieve fresh
// values.
func (n *NativeCache) Get(key common.SearchParam, fn func() ([]model.Torrent, int, error)) (
data []model.Torrent, count int, err error,
) {
s := n.getStore(key)
// Also keeps multiple requesters from simultaneously requesting the same
// data
s.Lock()
defer s.Unlock()
if s.isFresh() {
return s.data, s.count, nil
}
data, count, err = fn()
if err != nil {
return
}
s.update(data, count)
return
}
// Retrieve a store from the cache or create a new one
func (n *NativeCache) getStore(k common.SearchParam) (s *store) {
n.mu.Lock()
defer n.mu.Unlock()
el := n.cache[k]
if el == nil {
s = &store{key: k, n: n}
n.cache[k] = n.ll.PushFront(s)
} else {
n.ll.MoveToFront(el)
s = el.Value.(*store)
}
return s
}
// Clear the cache. Only used for testing.
func (n *NativeCache) ClearAll() {
n.mu.Lock()
defer n.mu.Unlock()
n.ll = list.New()
n.cache = make(map[common.SearchParam]*list.Element, 10)
}
// Update the total used memory counter and evict, if over limit
func (n *NativeCache) updateUsedSize(delta int) {
n.mu.Lock()
defer n.mu.Unlock()
n.totalUsed += delta
for n.totalUsed > int(n.Size)<<20 {
e := n.ll.Back()
if e == nil {
break
}
s := n.ll.Remove(e).(*store)
delete(n.cache, s.key)
s.Lock()
n.totalUsed -= s.size
s.Unlock()
}
}
// Return, if the data can still be considered fresh, without querying the DB
func (s *store) isFresh() bool {
if s.lastFetched.IsZero() { // New store
return false
}
return s.lastFetched.Add(expiryTime).After(time.Now())
}
// Stores the new values of s. Calculates and stores the new size. Passes the
// delta to the central cache to fire eviction checks.
func (s *store) update(data []model.Torrent, count int) {
newSize := 0
for _, d := range data {
newSize += d.Size()
}
s.data = data
s.count = count
delta := newSize - s.size
s.size = newSize
s.lastFetched = time.Now()
// In a separate goroutine, to ensure there is never any lock intersection
go s.n.updateUsedSize(delta)
}
*/

Voir le fichier

@ -1,49 +0,0 @@
package native
/*
import (
"path"
"sync"
"testing"
"github.com/NyaaPantsu/nyaa/common"
"github.com/NyaaPantsu/nyaa/config"
"github.com/NyaaPantsu/nyaa/model"
)
// run before config/parse.go:init()
var _ = func() (_ struct{}) {
config.ConfigPath = path.Join("..", "..", config.ConfigPath)
config.DefaultConfigPath = path.Join("..", "..", config.DefaultConfigPath)
config.Parse()
return
}()
// Basic test for deadlocks and race conditions
func TestConcurrency(t *testing.T) {
c := New(0.000001)
fn := func() ([]model.Torrent, int, error) {
return []model.Torrent{{}, {}, {}}, 10, nil
}
var wg sync.WaitGroup
wg.Add(300)
for i := 0; i < 3; i++ {
go func() {
for j := 0; j < 100; j++ {
go func(j int) {
defer wg.Done()
k := common.SearchParam{
Page: j,
}
if _, _, err := c.Get(k, fn); err != nil {
t.Fatal(err)
}
}(j)
}
}()
}
wg.Wait()
}
*/

22
cache/nop/nop.go externe
Voir le fichier

@ -1,22 +0,0 @@
package nop
import (
"github.com/NyaaPantsu/nyaa/common"
"github.com/NyaaPantsu/nyaa/model"
)
type NopCache struct {
}
func (c *NopCache) Get(key common.SearchParam, fn func() ([]model.Torrent, int, error)) ([]model.Torrent, int, error) {
return fn()
}
func (c *NopCache) ClearAll() {
}
// New creates a new Cache that does NOTHING :D
func New() *NopCache {
return &NopCache{}
}

Voir le fichier

@ -1,110 +0,0 @@
package database
import (
"github.com/NyaaPantsu/nyaa/common"
"github.com/NyaaPantsu/nyaa/config"
"github.com/NyaaPantsu/nyaa/database/postgres"
//"github.com/NyaaPantsu/nyaa/db/sqlite"
"github.com/NyaaPantsu/nyaa/model"
"github.com/NyaaPantsu/nyaa/util/log"
"errors"
)
// Database obstraction layer
type Database interface {
// Initialize internal state
Init() error
// return true if we need to call MigrateNext again
NeedsMigrate() (bool, error)
// migrate to next database revision
MigrateNext() error
// get torrents given parameters
GetTorrentsWhere(param *common.TorrentParam) ([]model.Torrent, error)
// insert new comment
InsertComment(comment *model.Comment) error
// new torrent report
InsertTorrentReport(report *model.TorrentReport) error
// check if user A follows B (by id)
UserFollows(a, b uint32) (bool, error)
// delete reports given params
DeleteTorrentReportsWhere(param *common.ReportParam) (uint32, error)
// get reports given params
GetTorrentReportsWhere(param *common.ReportParam) ([]model.TorrentReport, error)
// bulk record scrape events in 1 transaction
RecordScrapes(scrapes []common.ScrapeResult) error
// insert new user
InsertUser(u *model.User) error
// update existing user info
UpdateUser(u *model.User) error
// get users given paramteters
GetUsersWhere(param *common.UserParam) ([]model.User, error)
// delete many users given parameters
DeleteUsersWhere(param *common.UserParam) (uint32, error)
// get comments by given parameters
GetCommentsWhere(param *common.CommentParam) ([]model.Comment, error)
// delete comment by given parameters
DeleteCommentsWhere(param *common.CommentParam) (uint32, error)
// add user A following B
AddUserFollowing(a, b uint32) error
// delete user A following B
DeleteUserFollowing(a, b uint32) (bool, error)
// insert/update torrent
UpsertTorrent(t *model.Torrent) error
// XXX: add more as needed
}
var errInvalidDatabaseDialect = errors.New("invalid database dialect")
var errSqliteSucksAss = errors.New("sqlite3 sucks ass so it's not supported yet")
// Impl : Database variable
var Impl Database
// Configure : Configure Database
func Configure(conf *config.Config) (err error) {
switch conf.DBType {
case "postgres":
Impl, err = postgres.New(conf.DBParams)
case "sqlite3":
err = errSqliteSucksAss
// Impl, err = sqlite.New(conf.DBParams)
default:
err = errInvalidDatabaseDialect
}
if err == nil {
log.Infof("Init %s database", conf.DBType)
err = Impl.Init()
}
return
}
// Migrate migrates the database to latest revision, call after Configure
func Migrate() (err error) {
next := true
for err == nil && next {
next, err = Impl.NeedsMigrate()
if next {
err = Impl.MigrateNext()
}
}
return
}

Voir le fichier

@ -1,24 +0,0 @@
package postgres
import (
"github.com/NyaaPantsu/nyaa/common"
"github.com/NyaaPantsu/nyaa/model"
)
// InsertComment : Insert a comment
func (db *Database) InsertComment(comment *model.Comment) (err error) {
_, err = db.getPrepared(queryInsertComment).Exec(comment.ID, comment.TorrentID, comment.Content, comment.CreatedAt)
return
}
// GetCommentsWhere : Get comments on condition
func (db *Database) GetCommentsWhere(param *common.CommentParam) (comments []model.Comment, err error) {
return
}
// DeleteCommentsWhere : Delete comments on condition
func (db *Database) DeleteCommentsWhere(param *common.CommentParam) (deleted uint32, err error) {
return
}

Voir le fichier

@ -1,91 +0,0 @@
package postgres
import (
"database/sql"
"fmt"
"strings"
)
// sql query
type sqlQuery struct {
query string
params []interface{}
}
func (q *sqlQuery) Exec(conn *sql.DB) (err error) {
_, err = conn.Exec(q.query, q.params...)
return
}
func (q *sqlQuery) QueryRow(conn *sql.DB, visitor func(*sql.Row) error) (err error) {
err = visitor(conn.QueryRow(q.query, q.params))
return
}
func (q *sqlQuery) Query(conn *sql.DB, visitor func(*sql.Rows) error) (err error) {
var rows *sql.Rows
rows, err = conn.Query(q.query, q.params...)
if err == sql.ErrNoRows {
err = nil
} else if err == nil {
err = visitor(rows)
rows.Close()
}
return
}
// make a createQuery that creates an index for column on table
func createIndex(table, column string) sqlQuery {
return sqlQuery{
query: fmt.Sprintf("CREATE INDEX IF NOT EXISTS ON %s %s ", table, column),
}
}
// make a createQuery that creates a trigraph index on a table for multiple columns
func createTrigraph(table string, columns ...string) sqlQuery {
return sqlQuery{
query: fmt.Sprintf("CREATE INDEX IF NOT EXISTS ON %s USING gin(%s gin_trgm_ops)", table, strings.Join(columns, ", ")),
}
}
// defines table creation info
type createTable struct {
name string // Table's name
columns tableColumns // Table's columns
preCreate []sqlQuery // Queries to run before table is ensrued
postCreate []sqlQuery // Queries to run after table is ensured
}
func (t createTable) String() string {
return fmt.Sprintf("CREATE TABLE %s IF NOT EXISTS ( %s )", t.name, t.columns)
}
func (t createTable) Exec(conn *sql.DB) (err error) {
// pre queries
for idx := range t.preCreate {
err = t.preCreate[idx].Exec(conn)
if err != nil {
return
}
}
// table definition
_, err = conn.Exec(t.String())
if err != nil {
return
}
// post queries
for idx := range t.postCreate {
err = t.postCreate[idx].Exec(conn)
if err != nil {
return
}
}
return
}
// tableColumns is a list of columns for a table to be created
type tableColumns []string
func (def tableColumns) String() string {
return strings.Join(def, ", ")
}

Voir le fichier

@ -1,9 +0,0 @@
package postgres
func (db *Database) MigrateNext() (err error) {
return
}
func (db *Database) NeedsMigrate() (needs bool, err error) {
return
}

Voir le fichier

@ -1 +0,0 @@
package postgres

Voir le fichier

@ -1,88 +0,0 @@
package postgres
import (
"database/sql"
"github.com/NyaaPantsu/nyaa/util/log"
_ "github.com/lib/pq"
)
func New(param string) (db *Database, err error) {
db = new(Database)
db.conn, err = sql.Open("postgres", param)
if err != nil {
db = nil
}
return
}
type Database struct {
conn *sql.DB
prepared map[string]*sql.Stmt
}
func (db *Database) getPrepared(name string) *sql.Stmt {
return db.prepared[name]
}
func (db *Database) Query(q string, param ...interface{}) (*sql.Rows, error) {
return db.conn.Query(q, param...)
}
func (db *Database) Init() (err error) {
// ensure tables
for idx := range tables {
log.Debugf("ensure table %s", tables[idx].name)
err = tables[idx].Exec(db.conn)
if err != nil {
log.Errorf("Failed to ensure table %s: %s", tables[idx].name, err.Error())
return
}
}
// generate prepared statements
for k := range statements {
var stmt *sql.Stmt
stmt, err = db.conn.Prepare(statements[k])
if err != nil {
log.Errorf("failed to build prepared statement %s: %s", k, err.Error())
return
}
db.prepared[k] = stmt
}
return
}
// execute prepared statement with arguments, visit result and autoclose rows after done visiting
func (db *Database) queryWithPrepared(name string, visit func(*sql.Rows) error, params ...interface{}) (err error) {
var rows *sql.Rows
rows, err = db.getPrepared(name).Query(params...)
if err == sql.ErrNoRows {
err = nil
} else if err == nil {
err = visit(rows)
rows.Close()
}
return
}
// execute prepared statement with arguments, visit single row
func (db *Database) queryRowWithPrepared(name string, visit func(*sql.Row) error, params ...interface{}) (err error) {
err = visit(db.getPrepared(name).QueryRow(params...))
return
}
// execute a query by name and return how many rows were affected
func (db *Database) execQuery(name string, p ...interface{}) (affected uint32, err error) {
var result sql.Result
result, err = db.getPrepared(name).Exec(p...)
if err == nil {
var d int64
d, err = result.RowsAffected()
if err == nil {
affected = uint32(d)
}
}
return
}

Voir le fichier

@ -1,64 +0,0 @@
package postgres
import (
"database/sql"
"fmt"
"github.com/NyaaPantsu/nyaa/common"
"github.com/NyaaPantsu/nyaa/model"
)
func (db *Database) InsertTorrentReport(report *model.TorrentReport) (err error) {
_, err = db.getPrepared(queryInsertTorrentReport).Exec(report.Description, report.TorrentID, report.UserID, report.CreatedAt)
return
}
func reportParamToQuery(param *common.ReportParam) (q sqlQuery) {
q.query += fmt.Sprintf("SELECT %s FROM %s WHERE created_at IS NOT NULL ", torrentReportSelectColumnsFull, tableTorrentReports)
counter := 1
if !param.AllTime {
q.query += fmt.Sprintf("AND created_at < $%d AND created_at > $%d", counter, counter+1)
q.params = append(q.params, param.Before, param.After)
counter += 2
}
if param.Limit > 0 {
q.query += fmt.Sprintf("LIMIT $%d ", counter)
q.params = append(q.params, param.Limit)
counter++
}
if param.Offset > 0 {
q.query += fmt.Sprintf("OFFSET $%d ", counter)
q.params = append(q.params, param.Offset)
counter++
}
return
}
func (db *Database) GetTorrentReportsWhere(param *common.ReportParam) (reports []model.TorrentReport, err error) {
q := reportParamToQuery(param)
err = q.Query(db.conn, func(rows *sql.Rows) error {
for rows.Next() {
var r model.TorrentReport
scanTorrentReportColumnsFull(rows, &r)
reports = append(reports, r)
}
return nil
})
return
}
func (db *Database) DeleteTorrentReportByID(id uint32) (err error) {
_, err = db.getPrepared(queryDeleteTorrentReportByID).Exec(id)
if err == sql.ErrNoRows {
err = nil
}
return
}
func (db *Database) DeleteTorrentReportsWhere(param *common.ReportParam) (deleted uint32, err error) {
return
}

Voir le fichier

@ -1,28 +0,0 @@
package postgres
import (
"database/sql"
"github.com/NyaaPantsu/nyaa/common"
)
func (db *Database) RecordScrapes(scrape []common.ScrapeResult) (err error) {
if len(scrape) > 0 {
var tx *sql.Tx
tx, err = db.conn.Begin()
if err == nil {
st := tx.Stmt(db.getPrepared(queryInsertScrape))
for idx := range scrape {
_, err = st.Exec(scrape[idx].Seeders, scrape[idx].Leechers, scrape[idx].Completed, scrape[idx].Date, scrape[idx].TorrentID)
if err != nil {
break
}
}
}
if err == nil {
err = tx.Commit()
} else {
tx.Rollback()
}
}
return
}

Voir le fichier

@ -1,73 +0,0 @@
package postgres
import (
"database/sql"
"fmt"
"github.com/NyaaPantsu/nyaa/model"
)
const queryGetAllTorrents = "GetAllTorrents"
const queryGetTorrentByID = "GetTorrentByID"
const queryInsertComment = "InsertComment"
const queryInsertUser = "InsertUser"
const queryInsertTorrentReport = "InsertTorrentReport"
const queryUserFollows = "UserFollows"
const queryDeleteTorrentReportByID = "DeleteTorrentReportByID"
const queryInsertScrape = "InsertScrape"
const queryGetUserByApiToken = "GetUserByApiToken"
const queryGetUserByEmail = "GetUserByEmail"
const queryGetUserByName = "GetUserByName"
const queryGetUserByID = "GetUserByID"
const queryUpdateUser = "UpdateUser"
const queryDeleteUserByID = "DeleteUserByID"
const queryDeleteUserByEmail = "DeleteUserByEmail"
const queryDeleteUserByToken = "DeleteUserByToken"
const queryUserFollowsUpsert = "UserFollowsUpsert"
const queryDeleteUserFollowing = "DeleteUserFollowing"
const torrentSelectColumnsFull = `torrent_id, torrent_name, torrent_hash, category, sub_category, status, date, uploader, downloads, stardom, description, website_link, deleted_at, seeders, leechers, completed, last_scrape`
func scanTorrentColumnsFull(rows *sql.Rows, t *model.Torrent) {
rows.Scan(&t.ID, &t.Name, &t.Hash, &t.Category, &t.SubCategory, &t.Status, &t.Date, &t.UploaderID, &t.Downloads, &t.Stardom, &t.Description, &t.WebsiteLink, &t.DeletedAt, &t.Scrape.Seeders, &t.Scrape.Leechers, &t.Scrape.Completed, &t.Scrape.LastScrape)
}
const commentSelectColumnsFull = `comment_id, torrent_id, user_id, content, created_at, updated_at, deleted_at`
func scanCommentColumnsFull(rows *sql.Rows, c *model.Comment) {
}
const torrentReportSelectColumnsFull = `torrent_report_id, type, torrent_id, user_id, created_at`
func scanTorrentReportColumnsFull(rows *sql.Rows, r *model.TorrentReport) {
rows.Scan(&r.ID, &r.Description, &r.TorrentID, &r.UserID, &r.CreatedAt)
}
const userSelectColumnsFull = `user_id, username, password, email, status, created_at, updated_at, api_token, api_token_expiry, language, md5`
func scanUserColumnsFull(rows *sql.Rows, u *model.User) {
rows.Scan(&u.ID, &u.Username, &u.Password, &u.Email, &u.Status, &u.CreatedAt, &u.UpdatedAt, &u.APIToken, &u.APITokenExpiry, &u.Language, &u.MD5)
}
var statements = map[string]string{
queryGetTorrentByID: fmt.Sprintf("SELECT %s FROM %s WHERE torrent_id = $1 LIMIT 1", torrentSelectColumnsFull, tableTorrents),
queryGetAllTorrents: fmt.Sprintf("SELECT %s FROM %s LIMIT $2 OFFSET $1", torrentSelectColumnsFull, tableTorrents),
queryInsertComment: fmt.Sprintf("INSERT INTO %s (comment_id, torrent_id, content, created_at) VALUES ($1, $2, $3, $4)", tableComments),
queryInsertTorrentReport: fmt.Sprintf("INSERT INTO %s (type, torrent_id, user_id, created_at) VALUES ($1, $2, $3, $4)", tableTorrentReports),
queryUserFollows: fmt.Sprintf("SELECT user_id, following FROM %s WHERE user_id = $1 AND following = $1 LIMIT 1", tableUserFollows),
queryDeleteTorrentReportByID: fmt.Sprintf("DELETE FROM %s WHERE torrent_report_id = $1", tableTorrentReports),
queryInsertScrape: fmt.Sprintf("UPDATE %s SET (seeders = $1, leechers = $2, completed = $3, last_scrape = $4 ) WHERE torrent_id = $5", tableTorrents),
queryGetUserByApiToken: fmt.Sprintf("SELECT %s FROM %s WHERE api_token = $1", userSelectColumnsFull, tableUsers),
queryGetUserByEmail: fmt.Sprintf("SELECT %s FROM %s WHERE email = $1", userSelectColumnsFull, tableUsers),
queryGetUserByName: fmt.Sprintf("SELECT %s FROM %s WHERE username = $1", userSelectColumnsFull, tableUsers),
queryGetUserByID: fmt.Sprintf("SELECT %s FROM %s WHERE user_id = $1", userSelectColumnsFull, tableUsers),
queryInsertUser: fmt.Sprintf("INSERT INTO %s (username, password, email, status, created_at, updated_at, last_login_at, api_token, api_token_expires, language, md5 ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", tableUsers),
queryUpdateUser: fmt.Sprintf("UPDATE %s SET (username = $2, password = $3 , email = $4, status = $5, updated_at = $6, last_login_at = $7 , api_token = $8 , api_token_expiry = $9 , language = $10 , md5 = $11 ) WHERE user_id = $1", tableUsers),
queryDeleteUserByID: fmt.Sprintf("DELETE FROM %s WHERE user_id = $1", tableUsers),
queryDeleteUserByEmail: fmt.Sprintf("DELETE FROM %s WHERE email = $1", tableUsers),
queryDeleteUserByToken: fmt.Sprintf("DELETE FROM %s WHERE api_token = $1", tableUsers),
queryUserFollowsUpsert: fmt.Sprintf("INSERT INTO %s VALUES(user_id, following) ($1, $2) ON CONFLICT DO UPDATE", tableUserFollows),
queryDeleteUserFollowing: fmt.Sprintf("DELETE FROM %s WHERE user_id = $1 AND following = $2", tableUserFollows),
}

Voir le fichier

@ -1,140 +0,0 @@
package postgres
import "fmt"
// table name for torrents
const tableTorrents = "torrents"
// table name for users
const tableUsers = "users"
// table for new comments
const tableComments = "comments"
// table for old comments
const tableOldComments = "comments_old"
// table for torrent reports
const tableTorrentReports = "torrent_reports"
// table for user follows
const tableUserFollows = "user_follows"
// table for old user uploads
const tableOldUserUploads = "user_uploads_old"
// all tables that we have in current database schema in the order they are created
var tables = []createTable{
// users table
{
name: tableUsers,
columns: tableColumns{
"user_id SERIAL PRIMARY KEY",
"username TEXT NOT NULL",
"password TEXT NOT NULL",
"email TEXT",
"status INTEGER NOT NULL",
"created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL",
"updated_at TIMESTAMP WITHOUT TIME ZONE",
"last_login_at TIMESTAMP WITHOUT TIME ZONE",
"api_token TEXT",
"api_token_expiry TIMESTAMP WITHOUT TIME ZONE NOT NULL",
"language TEXT",
"MD5 TEXT",
},
postCreate: []sqlQuery{
createIndex(tableUsers, "username"),
},
},
// torrents table
{
name: tableTorrents,
columns: tableColumns{
"torrent_id SERIAL PRIMARY KEY",
"torrent_name TEXT",
"torrent_hash TEXT NOT NULL",
"category INTEGER NOT NULL",
"sub_category INTEGER NOT NULL",
"status INTEGER NOT NULL",
"date TIMESTAMP WITHOUT TIME ZONE",
fmt.Sprintf("uploader INTEGER NOT NULL REFERENCES %s (user_id)", tableUsers),
"downloads INTEGER",
"stardom INTEGER NOT NULL",
"filesize BIGINT",
"description TEXT NOT NULL",
"website_link TEXT",
"deleted_at TIMESTAMP WITHOUT TIME ZONE",
"seeders INTEGER",
"leechers INTEGER",
"completed INTEGER",
"last_scrape TIMESTAMP WITHOUT TIME ZONE",
},
postCreate: []sqlQuery{
createIndex(tableTorrents, "torrent_id"),
createIndex(tableTorrents, "deleted_at"),
createIndex(tableTorrents, "uploader"),
createTrigraph(tableTorrents, "category", "torrent_name"),
createTrigraph(tableTorrents, "sub_category", "torrent_name"),
createTrigraph(tableTorrents, "status", "torrent_name"),
createTrigraph(tableTorrents, "torrent_name"),
},
},
// new comments table
{
name: tableComments,
columns: tableColumns{
"comment_id SERIAL PRIMARY KEY",
fmt.Sprintf("torrent_id INTEGER REFERENCES %s (torrent_id)", tableTorrents),
fmt.Sprintf("user_id INTEGER REFERENCES %s (user_id)", tableUsers),
"content TEXT NOT NULL",
"created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL",
"updated_at TIMESTAMP WITHOUT TIME ZONE",
"deleted_at TIMESTAMP WITH TIME ZONE",
},
postCreate: []sqlQuery{
createIndex(tableComments, "torrent_id"),
},
},
// old comments table
{
name: tableOldComments,
columns: tableColumns{
fmt.Sprintf("torrent_id INTEGER NOT NULL REFERENCES %s (torrent_id)", tableTorrents),
"username TEXT NOT NULL",
"content TEXT NOT NULL",
"date TIMESTAMP WITHOUT TIME ZONE NOT NULL",
},
},
// torrent reports table
{
name: tableTorrentReports,
columns: tableColumns{
"torrent_report_id SERIAL PRIMARY KEY",
"type TEXT",
fmt.Sprintf("torrent_id INTEGER REFERENCES %s (torrent_id)", tableTorrents),
"user_id INTEGER",
"created_at TIMESTAMP WITH TIME ZONE",
},
postCreate: []sqlQuery{
createIndex(tableTorrentReports, "torrent_report_id"),
},
},
// user follows table
{
name: tableUserFollows,
columns: tableColumns{
"user_id INTEGER NOT NULL",
"following INTEGER NOT NULL",
"PRIMARY KEY(user_id, following)",
},
},
// old uploads table
{
name: tableOldUserUploads,
columns: tableColumns{
"username TEXT IS NOT NULL",
fmt.Sprintf("torrent_id INTEGER IS NOT NULL REFERENCES %s (torrent_id)", tableTorrents),
"PRIMARY KEY (torrent_id)",
},
},
}

Voir le fichier

@ -1,33 +0,0 @@
package postgres
import (
"github.com/NyaaPantsu/nyaa/model"
"database/sql"
)
func (db *Database) GetAllTorrents(offset, limit uint32) (torrents []model.Torrent, err error) {
err = db.queryWithPrepared(queryGetAllTorrents, func(rows *sql.Rows) error {
torrents = make([]model.Torrent, 0, limit)
var idx uint64
for rows.Next() {
rows.Scan(torrents[idx])
}
return nil
}, offset, limit)
return
}
func (db *Database) GetTorrentByID(id uint32) (torrent model.Torrent, has bool, err error) {
err = db.queryWithPrepared(queryGetTorrentByID, func(rows *sql.Rows) error {
rows.Next()
scanTorrentColumnsFull(rows, &torrent)
has = true
return nil
}, id)
return
}
func (db *Database) UpsertTorrent(t *model.Torrent) (err error) {
return
}

Voir le fichier

@ -1,153 +0,0 @@
package postgres
import (
"database/sql"
"fmt"
"github.com/NyaaPantsu/nyaa/common"
"github.com/NyaaPantsu/nyaa/model"
)
func userParamToSelectQuery(p *common.UserParam) (q sqlQuery) {
q.query = fmt.Sprintf("SELECT %s FROM %s ", userSelectColumnsFull, tableUsers)
counter := 1
if p.Max > 0 {
q.query += fmt.Sprintf("LIMIT $%d ", counter)
q.params = append(q.params, p.Max)
counter++
}
if p.Offset > 0 {
q.query += fmt.Sprintf("OFFSET $%d ", counter)
q.params = append(q.params, p.Offset)
counter++
}
return
}
func (db *Database) UserFollows(a, b uint32) (follows bool, err error) {
err = db.queryWithPrepared(queryUserFollows, func(rows *sql.Rows) error {
follows = true
return nil
}, a, b)
return
}
func (db *Database) AddUserFollowing(a, b uint32) (err error) {
_, err = db.getPrepared(queryUserFollowsUpsert).Exec(a, b)
return
}
func (db *Database) DeleteUserFollowing(a, b uint32) (deleted bool, err error) {
var affected uint32
affected, err = db.execQuery(queryDeleteUserFollowing, a, b)
deleted = affected > 0
return
}
func (db *Database) getUserByQuery(name string, p interface{}) (user model.User, has bool, err error) {
err = db.queryWithPrepared(name, func(rows *sql.Rows) error {
rows.Next()
scanUserColumnsFull(rows, &user)
has = true
return nil
}, p)
return
}
func (db *Database) GetUserByAPIToken(token string) (user model.User, has bool, err error) {
user, has, err = db.getUserByQuery(queryGetUserByApiToken, token)
return
}
func (db *Database) GetUsersByEmail(email string) (users []model.User, err error) {
err = db.queryWithPrepared(queryGetUserByEmail, func(rows *sql.Rows) error {
for rows.Next() {
var user model.User
scanUserColumnsFull(rows, &user)
users = append(users, user)
}
return nil
}, email)
return
}
func (db *Database) GetUserByName(name string) (user model.User, has bool, err error) {
user, has, err = db.getUserByQuery(queryGetUserByName, name)
return
}
func (db *Database) GetUserByID(id uint32) (user model.User, has bool, err error) {
user, has, err = db.getUserByQuery(queryGetUserByID, id)
return
}
func (db *Database) InsertUser(u *model.User) (err error) {
_, err = db.getPrepared(queryInsertUser).Exec(u.Username, u.Password, u.Email, u.Status, u.CreatedAt, u.UpdatedAt, u.APIToken, u.APITokenExpiry, u.Language, u.MD5)
return
}
func (db *Database) UpdateUser(u *model.User) (err error) {
_, err = db.getPrepared(queryUpdateUser).Exec(u.ID, u.Username, u.Password, u.Email, u.Status, u.UpdatedAt, u.APIToken, u.APITokenExpiry, u.Language, u.MD5)
return
}
func (db *Database) GetUsersWhere(param *common.UserParam) (users []model.User, err error) {
var user model.User
var has bool
if len(param.Email) > 0 {
users, err = db.GetUsersByEmail(param.Email)
} else if len(param.Name) > 0 {
user, has, err = db.GetUserByName(param.Name)
if has {
users = append(users, user)
}
} else if len(param.APIToken) > 0 {
user, has, err = db.GetUserByAPIToken(param.APIToken)
if has {
users = append(users, user)
}
} else if param.ID > 0 {
user, has, err = db.GetUserByID(param.ID)
if has {
users = append(users, user)
}
} else {
q := userParamToSelectQuery(param)
if param.Max > 0 {
users = make([]model.User, 0, param.Max)
} else {
users = make([]model.User, 0, 64)
}
err = q.Query(db.conn, func(rows *sql.Rows) error {
for rows.Next() {
var user model.User
scanUserColumnsFull(rows, &user)
users = append(users, user)
}
return nil
})
}
return
}
func (db *Database) DeleteUsersWhere(param *common.UserParam) (deleted uint32, err error) {
var queryName string
var p interface{}
if param.ID > 0 {
queryName = queryDeleteUserByID
p = param.ID
} else if len(param.Email) > 0 {
queryName = queryDeleteUserByEmail
p = param.Email
} else if len(param.APIToken) > 0 {
queryName = queryDeleteUserByToken
p = param.APIToken
} else {
// delete nothing
return
}
deleted, err = db.execQuery(queryName, p)
return
}

Voir le fichier

@ -1,164 +0,0 @@
package postgres
import (
"database/sql"
"fmt"
"strings"
"github.com/NyaaPantsu/nyaa/common"
"github.com/NyaaPantsu/nyaa/config"
"github.com/NyaaPantsu/nyaa/model"
)
// build sql query from SearchParam for torrent search
func searchParamToTorrentQuery(param *common.TorrentParam) (q sqlQuery) {
counter := 1
q.query = fmt.Sprintf("SELECT %s FROM %s ", torrentSelectColumnsFull, tableTorrents)
if len(param.Category) > 0 {
conditionsOr := make([]string, len(param.Category))
for key, val := range param.Category {
conditionsOr[key] = fmt.Sprintf("(category = $%d AND sub_category = $%d)", counter, counter+1)
q.params = append(q.params, val.Main, val.Sub)
counter += 2
}
q.query += "WHERE " + strings.Join(conditionsOr, " OR ")
}
if counter > 1 {
q.query += "AND "
} else {
q.query += "WHERE "
}
q.query += fmt.Sprintf("status >= $%d ", counter)
q.params = append(q.params, param.Status)
counter++
if param.UserID > 0 {
q.query += fmt.Sprintf("AND uploader = $%d ", counter)
q.params = append(q.params, param.UserID)
counter++
}
notnulls := strings.Split(param.NotNull, ",")
for idx := range notnulls {
k := strings.ToLower(strings.TrimSpace(notnulls[idx]))
switch k {
case "date":
case "downloads":
case "filesize":
case "website_link":
case "deleted_at":
case "seeders":
case "leechers":
case "completed":
case "last_scrape":
q.query += fmt.Sprintf("AND %s IS NOT NULL ", k)
default:
}
}
nulls := strings.Split(param.Null, ",")
for idx := range nulls {
k := strings.ToLower(strings.TrimSpace(nulls[idx]))
switch k {
case "date":
case "downloads":
case "filesize":
case "website_link":
case "deleted_at":
case "seeders":
case "leechers":
case "completed":
case "last_scrape":
q.query += fmt.Sprintf("AND %s IS NULL ", k)
default:
}
}
nameLikes := strings.Split(param.NameLike, ",")
for idx := range nameLikes {
q.query += fmt.Sprintf("AND torrent_name ILIKE $%d", counter)
q.params = append(q.params, strings.TrimSpace(nameLikes[idx]))
counter++
}
var sort string
switch param.Sort {
case common.Name:
sort = "torrent_name"
case common.Date:
sort = "date"
case common.Downloads:
sort = "downloads"
case common.Size:
sort = "filesize"
case common.Seeders:
sort = "seeders"
case common.Leechers:
sort = "leechers"
case common.Completed:
sort = "completed"
case common.ID:
default:
sort = config.Conf.Torrents.Order
}
q.query += fmt.Sprintf("ORDER BY %s ", sort)
if param.Order {
q.query += "ASC "
} else {
q.query += "DESC "
}
if param.Max > 0 {
q.query += fmt.Sprintf("LIMIT $%d ", counter)
q.params = append(q.params, param.Max)
counter++
}
if param.Offset > 0 {
q.query += fmt.Sprintf("OFFSET $%d ", counter)
q.params = append(q.params, param.Offset)
counter++
}
return
}
func (db *Database) GetTorrentsWhere(param *common.TorrentParam) (torrents []model.Torrent, err error) {
if param.TorrentID > 0 {
var torrent model.Torrent
var has bool
torrent, has, err = db.GetTorrentByID(param.TorrentID)
if has {
torrents = append(torrents, torrent)
}
return
}
if param.All {
torrents, err = db.GetAllTorrents(param.Offset, param.Max)
return
}
q := searchParamToTorrentQuery(param)
err = q.Query(db.conn, func(rows *sql.Rows) error {
if param.Max == 0 {
torrents = make([]model.Torrent, 0, 128)
} else {
torrents = make([]model.Torrent, 0, param.Max)
}
for rows.Next() {
var t model.Torrent
scanTorrentColumnsFull(rows, &t)
torrents = append(torrents, t)
// grow as needed
if len(torrents) >= cap(torrents) {
newtorrents := make([]model.Torrent, cap(torrents), cap(torrents)*3/2) // XXX: adjust as needed
copy(newtorrents, torrents)
torrents = newtorrents
}
}
return nil
})
return
}

Voir le fichier

@ -1,32 +0,0 @@
package sqlite
import (
"database/sql"
_ "github.com/mattn/go-sqlite3" // Need for sqlite
)
// queryEvent is a queued event to be executed in a pipeline to ensure that sqlite access is done from 1 goroutine
type queryEvent struct {
query string
param []interface{}
handleResult func(*sql.Rows, error)
}
// New : Create a new database
func New(param string) (db *Database, err error) {
db = new(Database)
db.conn, err = sql.Open("sqlite3", param)
if err == nil {
db.query = make(chan *queryEvent, 128)
} else {
db = nil
}
return
}
// Database structure
type Database struct {
conn *sql.DB
query chan *queryEvent
}

Voir le fichier

@ -1,3 +0,0 @@
PANTSU_EXTERNAL_PORT=9999
PANTSU_INTERNAL_PORT=9999
PANTSU_POSTGRES_DBFILE=nyaa_psql.backup

Voir le fichier

@ -1,5 +0,0 @@
FROM golang:1.8.1
RUN mkdir -p /nyaa
WORKDIR /nyaa

Voir le fichier

@ -1,170 +0,0 @@
# Deployment
## Docker
The docker-compose files can be used to quickly deploy the website without
needing other dependencies than docker and docker-compose.
We offer two database back-end (but that might change to only postgresql later).
> NOTE: Use the *-prod* version to deploy in production. See the section
> [production](#production).
### Usage
The first step depends on the back-end chosen.
For the **mysql** back-end, you need the database file inside the project's
top-level directory and named *nyaa.db*.
For the **postgresql** back-end, you need the database dump inside the project's
top-level directory and named nyaa\_psql.backup.
You may now start the container as such.
```
$ export GOPATH=$HOME/.go
$ mkdir -p $HOME/.go/src/github.com/NyaaPantsu
$ cd $HOME/.go/src/github.com/NyaaPantsu
$ git clone https://github.com/NyaaPantsu/nyaa
$ cd nyaa/deploy
$ docker-compose -f <docker_compose_file> up
```
The website will be available at [http://localhost:9999](http://localhost:9999).
> NOTE: The website might crash if the database takes longer than the amount of
> time sleeping in the [init.sh](init.sh) script.
> NOTE: The docker-compose file uses version 3, but doesn't yet use any feature
> from the version 3. If you're getting an error because your version of docker
> is too low, you can try changing the version to '2' in the compose file.
### Production
This is specific to the
[docker-compose.postgres-prod.yml](docker-compose.postgres-prod.yml) compose
file. This should be used in production.
This setup uses an external postgresql database configured on the host machine
instead of using a container. You must therefore install and configure
postgresql in order to use this compose file.
Set the correct database parameters in [postgres-prod.env](postgres-prod.env).
You can then follow the steps above.
### Cleaning docker containers
Docker can end up taking a lot of space very quickly. The script
[prune\_docker.sh](prune_docker.sh) will get rid of unused docker images and
volumes.
## Ansible
> IMPORTANT: Make sure the website connects to pgpool's port. Otherwise, no
> caching will be done. Ansible assume you have a user on the remote that has
> sudo (no password).
You'll have to change a few variables in [hosts](host). Replace the host:ip
address to the host:ip of the target server. You can also change the user
ansible uses to connect to the server. The user needs to have sudo ALL.
You'll also maybe have to tweak a few variables in
[group_vars/all](group_vars/all) such as the database password, etc (but should
probably be left like this).
### Setup server playbook
This playbook installs and configure:
- postgresql (It also includes pgpool for caching)
- firewalld
- golang
- elasticsearch
- backup system (uses cronjob to do daily backup of the database)
> NOTE: The backup script needs to have access to a GPG key to sign the dumps.
> It also needs a file with the passphrase, see
> [group_vars/all](group_vars/all).
```
$ ansible-playbook -i hosts setup_server.yml
```
### Restore Database Playbook
This playbook restores a database from dump. The dump has to be named
nyaa_psql.backup and needs to be placed in the toplevel project directory *on
your local host*. The database will be copied to the remote host and then will
be restored.
```
$ ansible-playbook -i hosts restore_database.yml
```
### Elasticsearch Index Playbooks
We are using index aliases for a zero downtime and index hotswapping. You can
find more information [here](https://www.elastic.co/guide/en/elasticsearch/guide/current/index-aliases.html).
I'll assume you already have an index named `nyaapantsu_old` that is aliased to
`nyaapantsu` and you want to create a new index to become the new `nyaapantsu`.
First you'll need to modify the variables in the [group_vars/all](group_vars/all)
file.
The new index will be called `nyaapantsu_new`.
```yaml
nyaapantsu_elasticsearch_alias: nyaapantsu
nyaapantsu_elasticsearch_index: nyaapantsu_new
nyaapantsu_elasticsearch_old_index: nyaapantsu_old
```
Now you need to run three playbooks.
```bash
# Creates the new index
$ ansible-playbook -i hosts create_elasticsearch_index.yml
# Populate the new index and disable the reindexing cron job. This avoid
# losing new entries.
$ ansible-playbook -i hosts populate_elasticsearch_index.yml
# Remove the alias nyaapantsu from nyaapantsu_old and adds it to nyaapantsu_new
$ ansible-playbook -i hosts swap_elasticsearch_index.yml
```
Nyaa can now access the new index `nyaapantsu_new` by using the alias
`nyaapantsu`.
## Playbook Testing
You can easily test these playbooks by using vagrant. Once you have vagrant
installed:
```
# Download centos/7 image
$ vagrant init centos/7
# Create and boot the vm
$ vagrant up
$ vagrant ssh
```
Now you have to setup your host to be able to connect to the vm using ssh. One
way is to copy your public ssh key to the `~/.ssh/authorized_keys` file. Once
that is done, your local host should be able to connect to the vm using ssh.
You can now tests the playbooks.
## TODOs
- Delete .torrents after X days
- Add public keys to db (?)
- Show public keys and link to .torrents on the website
- Tuning elasticsearch indexing / analyzer

Voir le fichier

@ -1,18 +0,0 @@
- name: Create elasticsearch index
hosts: dbs
roles:
- common
- elasticsearch
tasks:
- name: Read configuration file
shell: cat "{{ nyaapantsu_directory }}/elasticsearch_settings.yml"
register: config
- name: Configure elasticsearch index
uri:
headers:
Content-Type: application/yaml
url: "http://localhost:9200/{{ nyaapantsu_elasticsearch_index }}"
method: PUT
body: "{{ config.stdout }}"

Voir le fichier

@ -1,17 +0,0 @@
nyaapantsu_dbname: nyaapantsu
nyaapantsu_user: nyaapantsu
nyaapantsu_password: nyaapantsu
nyaapantsu_pgpool_port: 9998
nyaapantsu_directory: /nyaapantsu/
nyaapantsu_gpg_passphrase_file: "{{ nyaapantsu_directory }}/passphrase"
nyaapantsu_build_directory: go_nyaa/
nyaapantsu_elasticsearch_alias: nyaapantsu
# nyaapantsu_elasticsearch_alias: sukebei
nyaapantsu_elasticsearch_index: nyaapantsu_v1
# nyaapantsu_elasticsearch_index: nyaapantsu_sukebei_v1
nyaapantsu_elasticsearch_old_index: CHANGETHIS
nyaapantsu_torrent_tablename: torrents
# nyaapantsu_torrent_tablename: torrents_sukebei
nyaapantsu_max_open_files: 200000
nyaapantsu_jvm_heapsize_gb: 8
# vim: ft=yaml

Voir le fichier

@ -1,5 +0,0 @@
[webservers]
127.0.0.1:2200 ansible_connection=ssh ansible_ssh_user=vagrant
[dbs]
127.0.0.1:2200 ansible_connection=ssh ansible_ssh_user=vagrant

Voir le fichier

@ -1,6 +0,0 @@
# Should only be used for the dev machine until we get a nice setup to install
# both sukebei AND nyaa.
- name: Install nyaa
hosts: webservers
roles:
- nyaapantsu

Voir le fichier

@ -1,21 +0,0 @@
- name: Populate elasticsearch index from database
hosts: dbs
roles:
- common
- postgresql
- elasticsearch
tasks:
# Needed otherwise we'll lose information when people upload when we're
# populating to the new index.
# DONT FORGET TO RE-ENABLE THE CRON JOB AFTER
- name: Disable reindex cron job
command: mv "/etc/cron.d/reindex_{{ nyaapantsu_torrent_tablename }}" /tmp/
become: true
- name: Index the database into elasticsearch
command: python "{{ nyaapantsu_directory }}/index_nyaapantsu.py"
environment:
PANTSU_DBPARAMS: "host=localhost port={{ nyaapantsu_pgpool_port }} user={{ nyaapantsu_user }} dbname={{ nyaapantsu_dbname }} sslmode=disable password={{ nyaapantsu_password }}"
PANTSU_ELASTICSEARCH_INDEX: "{{ nyaapantsu_elasticsearch_index }}"
PANTSU_TORRENT_TABLENAME: "{{ nyaapantsu_torrent_tablename }}"

Voir le fichier

@ -1,14 +0,0 @@
- name: Restore database
hosts: dbs
roles:
- common
- postgresql
tasks:
- name: Copy backup database
copy:
src: ../../nyaa_psql.backup
dest: "{{ nyaapantsu_directory }}"
- name: Restore database from backup
command: pg_restore -U "{{ nyaapantsu_user }}" -d "{{ nyaapantsu_dbname }}" "{{ nyaapantsu_directory }}/nyaa_psql.backup"

Voir le fichier

@ -1,32 +0,0 @@
# Create a backup from the database
#!/bin/bash
set -eu
NYAAPANTSU_USERNAME="$1"
NYAAPANTSU_PASSWORD="$2"
NYAAPANTSU_DB="$3"
NYAAPANTSU_PASSPHRASE_FILE="$4"
NYAAPANTSU_TRACKER="$5"
NYAAPANTSU_DOWNLOADED_DIR="$6"
NYAAPANTSU_WATCH_DIR="$7"
dump_file="${NYAAPANTSU_DB}_$(date +'%Y_%m_%d_%H_%M').backup"
pg_dump -U "${NYAAPANTSU_USERNAME}" -Fc --exclude-table-data=users -f "${dump_file}"
xz -z "${dump_file}"
compressed_dump_file="${dump_file}.xz"
signature_file="${compressed_dump_file}.sig"
gpg2 --batch --yes --passphrase-fd 0 \
--output "${signature_file}" \
--detach-sig "${compressed_dump_file}" < "${NYAAPANTSU_PASSPHRASE_FILE}"
mktorrent -a "${NYAAPANTSU_TRACKER}" \
-c "Official nyaapantsu database release ($(date +'%Y-%m-%d'))" \
"${compressed_dump_file}" "${signature_file}"
mv "${compressed_dump_file}" "${signature_file}" "${NYAAPANTSU_DOWNLOADED_DIR}"
mv "${compressed_dump_file}.torrent" "${NYAAPANTSU_WATCH_DIR}/"

Voir le fichier

@ -1,72 +0,0 @@
# TODO Allow autogenerating of GPG keys
- name: Make sure there is a passphrase file
stat:
path: "{{ nyaapantsu_gpg_passphrase_file }}"
register: pass_file
- name: Copy backup script
copy:
src: backup.sh
dest: "{{ backup_script }}"
mode: 0755
become: true
when: pass_file.stat.exists == true
- name: Create (if not exists) cronjob
file:
path: "{{ backup_cron_job }}"
state: touch
become: true
when: pass_file.stat.exists == true
- name: Add epel repository
yum_repository:
name: epel
description: EPEL YUM repo
baseurl: https://download.fedoraproject.org/pub/epel/$releasever/$basearch/
gpgcheck: 1
gpgkey: https://getfedora.org/static/352C64E5.txt
become: true
- name: Install mktorrent and rtorrent
yum:
name: "{{ item }}"
state: present
become: true
with_items:
- mktorrent
- rtorrent
- name: Create download and watch directory
file:
path: "{{ item }}"
state: directory
with_items:
- "~/.rtorrent_session/"
- "{{ torrent_downloaded_directory }}"
- "{{ torrent_watch_directory }}"
- name: Configure rtorrent
template:
src: rtorrent.rc.j2
dest: ~/.rtorrent.rc
- name: Copy systemd rtorrent service
template:
src: rtorrent.service.j2
dest: /etc/systemd/system/rtorrent.service
become: true
- name: Enable and start rtorrent service
systemd:
enabled: yes
name: rtorrent
state: started
become: true
- name: Setup backup cron
template:
src: backup_cron.j2
dest: "{{ backup_cron_job }}"
become: true
when: pass_file.stat.exists == true

Voir le fichier

@ -1 +0,0 @@
0 {{ backup_cron_hours }} * * * {{ backup_script }} {{ nyaapantsu_user }} {{ nyaapantsu_password }} {{ nyaapantsu_dbname }} {{ nyaapantsu_gpg_passphrase_file }} {{ torrent_tracker }} {{ torrent_downloaded_directory }} {{ torrent_watch_directory }}

Voir le fichier

@ -1,29 +0,0 @@
port_range = {{ torrent_port_range }}
min_peers = 40
max_peers = 52
min_peers_seed = 10
max_peers_seed = 52
max_uploads = 15
upload_rate = 0
# Default directory to save the downloaded torrents.
directory = {{ torrent_downloaded_directory }}
# Watch a directory for new torrents, and stop those that have been
# deleted.
schedule = watch_directory,5,5,load_start={{ torrent_watch_directory }}/*.torrent
#schedule = low_diskspace,5,60,close_low_diskspace=100M
check_hash = yes
# Set whether the client should try to connect to UDP trackers.
#use_udp_trackers = yes
# Should be force it ?
encryption = allow_incoming,enable_retry,prefer_plaintext
dht = auto
dht_port = 6881
peer_exchange = yes
# vim: ft=cfg

Voir le fichier

@ -1,17 +0,0 @@
[Unit]
Description=rTorrent
After=network.target
[Service]
Type=forking
KillMode=none
User={{ ansible_ssh_user }}
# FIXME Start-pre gives an 'Operation not supported error'
#ExecStartPre=/usr/bin/bash -c "if test -e %h/.rtorrent_session/rtorrent.lock && test -z `pidof rtorrent`; then rm -f %h/.rtorrent_session/rtorrent.lock; fi"
ExecStart=/usr/bin/screen -dmfa -S rtorrent /usr/bin/rtorrent
ExecStop=/usr/bin/bash -c "test `pidof rtorrent` && killall -w -s 2 /usr/bin/rtorrent"
WorkingDirectory=%h
Restart=on-failure
[Install]
WantedBy=multi-user.target

Voir le fichier

@ -1,8 +0,0 @@
# INFO Could probably put backup script in nyaapantsu_directory / bin as well
backup_script: /usr/local/bin/nyaapantsu_backup.sh
backup_cron_hours: 0
backup_cron_job: /etc/cron.d/nyaapantsu_backup
torrent_tracker: udp://tracker.doko.moe:6969
torrent_downloaded_directory: "{{ nyaapantsu_directory }}/downloaded/"
torrent_watch_directory: "{{ nyaapantsu_directory }}/watch/"
torrent_port_range: 49164-49164

Voir le fichier

@ -1,13 +0,0 @@
- name: Create nyaapantsu directory
file:
path: "{{ nyaapantsu_directory }}"
state: directory
mode: 0755
owner: "{{ ansible_ssh_user }}"
become: true
- name: Install useful stuff
yum:
name: lsof
state: present
become: true

Voir le fichier

@ -1,31 +0,0 @@
# TODO Get from https://download.docker.com/linux/centos/docker-ce.repo instead
# of hardcoding information here
- name: Add Docker CE repository
yum_repository:
name: docker-ce-edge
enabled: yes
description: Docker CE Edge - $basearch
baseurl: https://download.docker.com/linux/centos/7/$basearch/edge
gpgcheck: 1
gpgkey: https://download.docker.com/linux/centos/gpg
become: true
- name: Install Docker CE
yum:
name: docker-ce
state: present
become: true
- name: Install docker-compose
get_url:
url: "https://github.com/docker/compose/releases/download/{{ release_version }}/docker-compose-{{ ansible_system }}-{{ ansible_architecture }}"
dest: /usr/bin/docker-compose
mode: 0755
become: true
- name: Start docker and enable at boot
systemd:
enabled: yes
name: docker
state: started
become: true

Voir le fichier

@ -1 +0,0 @@
release_version: 1.13.0

Voir le fichier

@ -1,88 +0,0 @@
---
settings:
analysis:
analyzer:
# Don't use ngram for search otherwise 'horribleexample' would match
# 'horriblesubs'
nyaapantsu_search_analyzer:
tokenizer: standard
filter:
- standard
- lowercase
char_filter:
- underscore_to_space
- dash_to_underscore
nyaapantsu_index_analyzer:
tokenizer: standard
filter:
- standard
- lowercase
- e_ngram_filter
char_filter:
- underscore_to_space
- dash_to_underscore
filter:
e_ngram_filter:
type: edge_ngram
min_gram: 1
max_gram: 16
char_filter:
dash_to_underscore:
type: pattern_replace
pattern: "([^\\s]+)-(?=[^\\s]+)"
replacement: "$1_"
underscore_to_space:
type: mapping
mappings:
- "_ => \\u0020"
index:
number_of_shards: 1
number_of_replicas: 0
max_result_window: 5000000
mappings:
torrents:
properties:
# TODO Consistent ID's type in TorrentJSON
id:
type: long
name:
type: text
analyzer: nyaapantsu_index_analyzer
fields:
raw:
type: keyword
category:
type: text
description:
type: text
sub_category:
type: text
status:
type: long
hash:
type: text
date:
type: date
uploader_id:
type: long
downloads:
type: long
seeders:
type: long
hidden:
type: boolean
leechers:
type: long
completed:
type: long
filesize:
type: long
language:
type: text
analyzer: simple

Voir le fichier

@ -1,62 +0,0 @@
# coding: utf-8
from elasticsearch import Elasticsearch, helpers
import psycopg2, pprint, sys, time, os
CHUNK_SIZE = 10000
def getEnvOrExit(var):
environment = ''
try:
environment = os.environ[var]
except:
print('[Error]: Environment variable ' + var + ' not defined.')
sys.exit(1)
return environment
dbparams = getEnvOrExit('PANTSU_DBPARAMS')
pantsu_index = getEnvOrExit('PANTSU_ELASTICSEARCH_INDEX')
torrent_tablename = getEnvOrExit('PANTSU_TORRENT_TABLENAME')
es = Elasticsearch()
pgconn = psycopg2.connect(dbparams)
cur = pgconn.cursor()
cur.execute("""SELECT torrent_id, torrent_name, description, hidden, category, sub_category, status,
torrent_hash, date, uploader, downloads, filesize, seeders, leechers, completed, language
FROM {torrent_tablename}
WHERE deleted_at IS NULL""".format(torrent_tablename=torrent_tablename))
fetches = cur.fetchmany(CHUNK_SIZE)
while fetches:
actions = list()
for torrent_id, torrent_name, description, hidden, category, sub_category, status, torrent_hash, date, uploader, downloads, filesize, seeders, leechers, completed, language in fetches:
doc = {
'id': torrent_id,
'name': torrent_name.decode('utf-8'),
'category': str(category),
'sub_category': str(sub_category),
'status': status,
'hash': torrent_hash,
'hidden': hidden,
'description': description,
'date': date,
'uploader_id': uploader,
'downloads': downloads,
'filesize': filesize,
'seeders': seeders,
'leechers': leechers,
'completed': completed,
'language': language
}
action = {
'_index': pantsu_index,
'_type': 'torrents',
'_id': torrent_id,
'_source': doc
}
actions.append(action)
helpers.bulk(es, actions, chunk_size=CHUNK_SIZE, request_timeout=120)
del(fetches)
fetches = cur.fetchmany(CHUNK_SIZE)
cur.close()
pgconn.close()

Voir le fichier

@ -1,74 +0,0 @@
# coding: utf-8
from elasticsearch import Elasticsearch, helpers
import psycopg2, pprint, sys, time, os
CHUNK_SIZE = 10000
def getEnvOrExit(var):
environment = ''
try:
environment = os.environ[var]
except:
print('[Error]: Environment variable ' + var + ' not defined.')
sys.exit(1)
return environment
dbparams = getEnvOrExit('PANTSU_DBPARAMS')
pantsu_index = getEnvOrExit('PANTSU_ELASTICSEARCH_INDEX')
torrent_tablename = getEnvOrExit('PANTSU_TORRENT_TABLENAME')
es = Elasticsearch()
pgconn = psycopg2.connect(dbparams)
cur = pgconn.cursor()
# We MUST use NO QUERY CACHE because the values are insert on triggers and
# not through pgppool.
cur.execute('/*NO QUERY CACHE*/ SELECT reindex_torrents_id, torrent_id, action FROM reindex_{torrent_tablename}'.format(torrent_tablename=torrent_tablename))
fetches = cur.fetchmany(CHUNK_SIZE)
while fetches:
actions = list()
delete_cur = pgconn.cursor()
for reindex_id, torrent_id, action in fetches:
new_action = {
'_op_type': action,
'_index': pantsu_index,
'_type': 'torrents',
'_id': torrent_id
}
if action == 'index':
select_cur = pgconn.cursor()
select_cur.execute("""SELECT torrent_id, torrent_name, description, hidden, category, sub_category, status,
torrent_hash, date, uploader, downloads, filesize, seeders, leechers, completed, language
FROM {torrent_tablename}
WHERE torrent_id = {torrent_id}""".format(torrent_id=torrent_id, torrent_tablename=torrent_tablename))
torrent_id, torrent_name, description, hidden, category, sub_category, status, torrent_hash, date, uploader, downloads, filesize, seeders, leechers, completed, language = select_cur.fetchone()
doc = {
'id': torrent_id,
'name': torrent_name.decode('utf-8'),
'category': str(category),
'sub_category': str(sub_category),
'status': status,
'hidden': hidden,
'description': description,
'hash': torrent_hash,
'date': date,
'uploader_id': uploader,
'downloads': downloads,
'filesize': filesize,
'seeders': seeders,
'leechers': leechers,
'completed': completed,
'language': language
}
new_action['_source'] = doc
select_cur.close()
delete_cur.execute('DELETE FROM reindex_{torrent_tablename} WHERE reindex_torrents_id = {reindex_id}'.format(reindex_id=reindex_id,torrent_tablename=torrent_tablename))
actions.append(new_action)
pgconn.commit() # Commit the deletes transaction
delete_cur.close()
helpers.bulk(es, actions, chunk_size=CHUNK_SIZE, request_timeout=120)
del(fetches)
fetches = cur.fetchmany(CHUNK_SIZE)
cur.close()
pgconn.close()

Voir le fichier

@ -1,94 +0,0 @@
- name: Add elasticsearch key
rpm_key:
state: present
key: https://artifacts.elastic.co/GPG-KEY-elasticsearch
become: true
- name: Add elasticsearch repo
yum_repository:
name: elasticsearch-5.x
baseurl: https://artifacts.elastic.co/packages/5.x/yum
gpgcheck: 1
gpgkey: https://artifacts.elastic.co/GPG-KEY-elasticsearch
enabled: 1
description: Elastic search repository for 5.x packages
become: true
- name: Install java and elasticsearch
yum:
name: "{{ item }}"
state: present
become: true
with_items:
- java-1.8.0-openjdk
- elasticsearch
- python-elasticsearch # INFO Needed for index_nyaapantsu.py script
- name: Copy files to remote
copy:
src: "{{ item }}"
dest: "{{ nyaapantsu_directory }}"
with_items:
- elasticsearch_settings.yml
- index_nyaapantsu.py
- name: Increase system max open files
lineinfile:
path: /etc/sysctl.conf
regexp: '^fs.file-max.*'
line: 'fs.file-max = {{ nyaapantsu_max_open_files }}'
become: true
- name: Increase JVM heapsize
lineinfile:
path: /etc/sysconfig/elasticsearch
regexp: '^ES_JAVA_OPTS=.*'
line: 'ES_JAVA_OPTS="-Xms{{ nyaapantsu_jvm_heapsize_gb }}g -Xmx{{ nyaapantsu_jvm_heapsize_gb }}g"'
become: true
- name: Lock elasticsearch memory
lineinfile:
path: /etc/elasticsearch/elasticsearch.yml
regexp: '^bootstrap.memory_lock: true'
line: 'bootstrap.memory_lock: true'
become: true
- name: Create override folder
file:
path: /etc/systemd/system/elasticsearch.service.d/
state: directory
mode: 0755
become: true
- name: Add override service file
template:
src: elasticsearch.override.j2
dest: /etc/systemd/system/elasticsearch.service.d/override.conf
become: true
- name: Enable and start elasticsearch
systemd:
enabled: yes
name: elasticsearch
state: started
daemon_reload: yes
become: true
- name: Copy reindexing triggers
template:
src: reindex_triggers.sql.j2
dest: "{{ nyaapantsu_directory }}/reindex_triggers.sql"
- name: Apply reindexing triggers
shell: psql -U "{{ nyaapantsu_user }}" "{{ nyaapantsu_dbname }}" < "{{ nyaapantsu_directory }}/reindex_triggers.sql"
- name: Copy reindexing script
copy:
src: reindex_nyaapantsu.py
dest: "{{ elasticsearch_reindex_script }}"
- name: Setup reindexing cron job
template:
src: reindex_cron.j2
dest: "/etc/cron.d/reindex_{{ nyaapantsu_torrent_tablename }}"
become: true

Voir le fichier

@ -1,6 +0,0 @@
[Service]
LimitNOFILE=
LimitNOFILE={{ nyaapantsu_max_open_files }}
LimitMEMLOCK=
LimitMEMLOCK=infinity

Voir le fichier

@ -1,5 +0,0 @@
PANTSU_DBPARAMS="host=localhost port={{ nyaapantsu_pgpool_port }} user={{ nyaapantsu_user }} password={{ nyaapantsu_password }} sslmode=disable"
PANTSU_ELASTICSEARCH_INDEX="{{ nyaapantsu_elasticsearch_alias }}"
PANTSU_TORRENT_TABLENAME="{{ nyaapantsu_torrent_tablename }}"
{{ elasticsearch_cron_minutes }} * * * * {{ ansible_ssh_user }} python {{ elasticsearch_reindex_script }}

Voir le fichier

@ -1,38 +0,0 @@
-- Matches the _op_type values from elasticsearch bulk API
-- https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html
CREATE TYPE torrents_action AS ENUM ('index', 'delete');
CREATE TABLE IF NOT EXISTS reindex_{{ nyaapantsu_torrent_tablename }} (
reindex_torrents_id SERIAL,
torrent_id int,
action torrents_action
);
CREATE OR REPLACE FUNCTION add_reindex_{{ nyaapantsu_torrent_tablename }}_action() RETURNS TRIGGER AS $$
BEGIN
IF (TG_OP = 'INSERT') THEN
INSERT INTO reindex_{{ nyaapantsu_torrent_tablename }} (torrent_id, action) VALUES (NEW.torrent_id, 'index');
RETURN NEW;
ELSIF (TG_OP = 'UPDATE') THEN
IF (NEW.deleted_at IS NOT NULL) THEN
INSERT INTO reindex_{{ nyaapantsu_torrent_tablename }} (torrent_id, action) VALUES (OLD.torrent_id, 'delete');
RETURN NEW;
ELSE
INSERT INTO reindex_{{ nyaapantsu_torrent_tablename }} (torrent_id, action) VALUES (NEW.torrent_id, 'index');
RETURN NEW;
END IF;
ELSIF (TG_OP = 'DELETE') THEN
INSERT INTO reindex_{{ nyaapantsu_torrent_tablename }} (torrent_id, action) VALUES (OLD.torrent_id, 'delete');
RETURN OLD;
END IF;
RETURN NULL; -- result is ignored since this is an AFTER trigger
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS trigger_reindex_{{ nyaapantsu_torrent_tablename }} ON {{ nyaapantsu_torrent_tablename }};
CREATE TRIGGER trigger_reindex_{{ nyaapantsu_torrent_tablename }}
AFTER INSERT OR UPDATE OR DELETE ON {{ nyaapantsu_torrent_tablename }}
FOR EACH ROW EXECUTE PROCEDURE add_reindex_{{ nyaapantsu_torrent_tablename }}_action();
-- vim: ft=sql

Voir le fichier

@ -1,3 +0,0 @@
# Run job every 5 minutes
elasticsearch_cron_minutes: "*/5"
elasticsearch_reindex_script: "{{ nyaapantsu_directory }}/reindex_nyaapantsu.py"

Voir le fichier

@ -1,23 +0,0 @@
- name: Install firewalld
yum:
name: firewalld
state: present
- name: Start firewalld
systemd:
name: firewalld
enabled: yes
state: started
become: true
- name: Add firewalld http and https service
firewalld:
service: "{{ item }}"
permanent: true
state: enabled
zone: public
immediate: true
with_items:
- http
- https
become: true

Voir le fichier

@ -1,26 +0,0 @@
- name: Download Golang
get_url:
url: "https://storage.googleapis.com/golang/go{{ golang_version }}.linux-amd64.tar.gz"
dest: /tmp/golang.tar.gz
- name: Unzip golang
unarchive:
src: /tmp/golang.tar.gz
dest: /usr/local/
remote_src: True
become: true
- name: Add go to PATH
lineinfile:
path: ~/.bashrc
regexp: 'export PATH=.*go.*'
line: 'export PATH=$PATH:/usr/local/go/bin'
- name: Install git for go get command
yum:
name: "{{ item }}"
state: present
become: true
with_items:
- git
- gcc

Voir le fichier

@ -1 +0,0 @@
golang_version: 1.8.1

Voir le fichier

@ -1,38 +0,0 @@
- name: Make directory
file:
state: directory
path: "{{ nyaapantsu_build_directory }}/src/github.com/NyaaPantsu"
- name: Register nyaa directory
stat:
path: "{{ nyaapantsu_build_directory }}/src/github.com/NyaaPantsu/nyaa"
register: nyaa_directory
- name: Register old nyaa directory
stat:
path: "{{ nyaapantsu_build_directory }}/src/github.com/NyaaPantsu/nyaa_old"
register: old_nyaa_directory
- name: Remove old nyaa directory
file:
path: "{{ nyaapantsu_build_directory }}/src/github.com/NyaaPantsu/nyaa_old"
state: absent
when: old_nyaa_directory.stat.exists == true
become: true # FIXME Need super user because public/dumps gives permission error
- name: Backup nyaa to old nyaa directory
command: mv -Tf "{{ nyaapantsu_build_directory }}/src/github.com/NyaaPantsu/nyaa/" "{{ nyaapantsu_build_directory }}/src/github.com/NyaaPantsu/nyaa_old/"
when: nyaa_directory.stat.exists == true
# TODO ability to get specify commit
- name: Get latest nyaa
git:
repo: 'https://github.com/NyaaPantsu/nyaa'
dest: "{{ nyaapantsu_build_directory }}/src/github.com/NyaaPantsu/nyaa"
- name: Go build
shell: go build
args:
chdir: "{{ nyaapantsu_build_directory }}/src/github.com/NyaaPantsu/nyaa"
environment:
GOPATH: "/home/{{ ansible_ssh_user }}/{{ nyaapantsu_build_directory }}"

Voir le fichier

@ -1,124 +0,0 @@
- name: Download postgresql rpm
get_url:
url: https://download.postgresql.org/pub/repos/yum/9.6/redhat/rhel-7-x86_64/pgdg-centos96-9.6-3.noarch.rpm
dest: /tmp/postgresql.rpm
- name: Install postgresql.rpm
yum:
name: "{{ item }}"
state: present
become: true
with_items:
- /tmp/postgresql.rpm
- name: Install postgresql
yum:
name: "{{ item }}"
state: present
become: true
with_items:
- postgresql96-server
- postgresql96-contrib # Needed for extensions (ie: btree_gin)
- pgpool-II-96
- name: Add go to PATH
lineinfile:
path: ~/.bashrc
regexp: 'export PATH=.*pgsql.*'
line: 'export PATH=$PATH:/usr/pgsql-9.6/bin'
- name: Initialize postgresql
command: /usr/pgsql-9.6/bin/initdb -D /var/lib/pgsql/9.6/data
# Will error when database has already been initialized so just ignore it
ignore_errors: yes
become: true
become_user: postgres
- name: Copy configuration file for postgresql
template:
src: postgresql.conf.j2
dest: /var/lib/pgsql/9.6/data/postgresql.conf
become: true
become_user: postgres
- name: Install adapter for python
yum:
name: python-psycopg2
state: present
become: true
- name: Configure pgpool
template:
src: pgpool.conf.j2
dest: /etc/pgpool-II-96/pgpool.conf
become: true
- name: Create oiddir for pgpool
file:
path: "{{ memqcache_oiddir }}"
state: directory
become: true
- name: Fix bug where pid is in wrong pool
file:
src: /var/run/pgpool-II-96
path: /var/run/pgpool
state: link
force: yes
become: true
- name: Start postgresql, pgpool and enable at boot
systemd:
enabled: yes
name: "{{ item }}"
state: started
become: true
with_items:
- postgresql-9.6
- pgpool-II-96
- name: Create nyaapantsu database
postgresql_db:
name: "{{ nyaapantsu_dbname }}"
become: true
become_user: postgres
# TODO Probably better idea to not set SUPERUSER
- name: Create nyaapantsu user
postgresql_user:
db: "{{ nyaapantsu_dbname }}"
name: "{{ nyaapantsu_user }}"
password: "{{ nyaapantsu_password }}"
role_attr_flags: SUPERUSER,LOGIN
become: true
become_user: postgres
- name: Grant privileges to user
postgresql_privs:
db: "{{ nyaapantsu_dbname }}"
priv: ALL
roles: "{{ nyaapantsu_user }}"
state: present
type: database
become: true
become_user: postgres
- name: Add custom pg_hba.conf
template:
src: pg_hba.conf.j2
dest: /var/lib/pgsql/9.6/data/pg_hba.conf
become: true
become_user: postgres
- name: Add .pgpass
template:
src: pgpass.j2
dest: ~/.pgpass
mode: 0600
- name: Reload postgres
systemd:
name: postgresql-9.6
state: reloaded
become: true

Voir le fichier

@ -1,9 +0,0 @@
# TYPE DATABASE USER ADDRESS METHOD
# For debugging purposes
local {{ nyaapantsu_dbname }} {{ nyaapantsu_user }} md5
local all all peer
# IPv4 local connections:
host {{ nyaapantsu_dbname }} {{ nyaapantsu_user }} 127.0.0.1/32 md5
# IPv6 local connections:
# INFO: For some reason, we need IPv6 for pgpool-ii
host {{ nyaapantsu_dbname }} {{ nyaapantsu_user }} ::1/128 md5

Voir le fichier

@ -1 +0,0 @@
localhost:5432:{{ nyaapantsu_dbname }}:{{ nyaapantsu_user }}:{{ nyaapantsu_password }}

Voir le fichier

@ -1,604 +0,0 @@
listen_addresses = 'localhost'
port = {{ nyaapantsu_pgpool_port }}
socket_dir = '/tmp'
listen_backlog_multiplier = 2
# Set the backlog parameter of listen(2) to
# num_init_children * listen_backlog_multiplier.
# (change requires restart)
pcp_listen_addresses = '*'
pcp_port = 9898
pcp_socket_dir = '/tmp'
backend_hostname0 = 'localhost'
backend_port0 = 5432
backend_weight0 = 1
backend_data_directory0 = '/var/lib/pgsql/9.6/data'
backend_flag0 = 'ALLOW_TO_FAILOVER'
# Controls various backend behavior
# ALLOW_TO_FAILOVER or DISALLOW_TO_FAILOVER
enable_pool_hba = off
# Use pool_hba.conf for client authentication
pool_passwd = ''
# File name of pool_passwd for md5 authentication.
# "" disables pool_passwd.
# (change requires restart)
authentication_timeout = 60
# Delay in seconds to complete client authentication
# 0 means no timeout.
# - SSL Connections -
ssl = off
# Enable SSL support
# (change requires restart)
#ssl_key = './server.key'
# Path to the SSL private key file
# (change requires restart)
#ssl_cert = './server.cert'
# Path to the SSL public certificate file
# (change requires restart)
#ssl_ca_cert = ''
# Path to a single PEM format file
# containing CA root certificate(s)
# (change requires restart)
#ssl_ca_cert_dir = ''
# Directory containing CA root certificate(s)
# (change requires restart)
#------------------------------------------------------------------------------
# POOLS
#------------------------------------------------------------------------------
# - Pool size -
num_init_children = 32
# Number of pools
# (change requires restart)
max_pool = 4
# Number of connections per pool
# (change requires restart)
# - Life time -
child_life_time = {{ child_life_time }}
# Pool exits after being idle for this many seconds
child_max_connections = 0
# Pool exits after receiving that many connections
# 0 means no exit
connection_life_time = 0
# Connection to backend closes after being idle for this many seconds
# 0 means no close
client_idle_limit = 0
# Client is disconnected after being idle for that many seconds
# (even inside an explicit transactions!)
# 0 means no disconnection
#------------------------------------------------------------------------------
# LOGS
#------------------------------------------------------------------------------
# - Where to log -
log_destination = 'stderr'
# Where to log
# Valid values are combinations of stderr,
# and syslog. Default to stderr.
# - What to log -
log_line_prefix = '%t: pid %p: ' # printf-style string to output at beginning of each log line.
log_connections = off
# Log connections
log_hostname = off
# Hostname will be shown in ps status
# and in logs if connections are logged
log_statement = off
# Log all statements
log_per_node_statement = on
# Log all statements
# with node and backend informations
log_standby_delay = 'none'
# Log standby delay
# Valid values are combinations of always,
# if_over_threshold, none
# - Syslog specific -
syslog_facility = 'LOCAL0'
# Syslog local facility. Default to LOCAL0
syslog_ident = 'pgpool'
# Syslog program identification string
# Default to 'pgpool'
# - Debug -
debug_level = 0
# Debug message verbosity level
# 0 means no message, 1 or more mean verbose
#log_error_verbosity = default # terse, default, or verbose messages
#client_min_messages = notice # values in order of decreasing detail:
# debug5
# debug4
# debug3
# debug2
# debug1
# log
# notice
# warning
# error
#log_min_messages = warning # values in order of decreasing detail:
# debug5
# debug4
# debug3
# debug2
# debug1
# info
# notice
# warning
# error
# log
# fatal
# panic
#------------------------------------------------------------------------------
# FILE LOCATIONS
#------------------------------------------------------------------------------
pid_file_name = '/var/run/pgpool/pgpool.pid'
# PID file name
# (change requires restart)
logdir = '/var/log/pgpool'
# Directory of pgPool status file
# (change requires restart)
#------------------------------------------------------------------------------
# CONNECTION POOLING
#------------------------------------------------------------------------------
connection_cache = on
# Activate connection pools
# (change requires restart)
# Semicolon separated list of queries
# to be issued at the end of a session
# The default is for 8.3 and later
reset_query_list = 'ABORT; DISCARD ALL'
# The following one is for 8.2 and before
#reset_query_list = 'ABORT; RESET ALL; SET SESSION AUTHORIZATION DEFAULT'
#------------------------------------------------------------------------------
# REPLICATION MODE
#------------------------------------------------------------------------------
replication_mode = off
# Activate replication mode
# (change requires restart)
replicate_select = off
# Replicate SELECT statements
# when in replication mode
# replicate_select is higher priority than
# load_balance_mode.
insert_lock = on
# Automatically locks a dummy row or a table
# with INSERT statements to keep SERIAL data
# consistency
# Without SERIAL, no lock will be issued
lobj_lock_table = ''
# When rewriting lo_creat command in
# replication mode, specify table name to
# lock
# - Degenerate handling -
replication_stop_on_mismatch = off
# On disagreement with the packet kind
# sent from backend, degenerate the node
# which is most likely "minority"
# If off, just force to exit this session
failover_if_affected_tuples_mismatch = off
# On disagreement with the number of affected
# tuples in UPDATE/DELETE queries, then
# degenerate the node which is most likely
# "minority".
# If off, just abort the transaction to
# keep the consistency
#------------------------------------------------------------------------------
# LOAD BALANCING MODE
#------------------------------------------------------------------------------
load_balance_mode = off
# Activate load balancing mode
# (change requires restart)
ignore_leading_white_space = on
# Ignore leading white spaces of each query
white_function_list = ''
# Comma separated list of function names
# that don't write to database
# Regexp are accepted
black_function_list = 'nextval,setval,nextval,setval'
# Comma separated list of function names
# that write to database
# Regexp are accepted
database_redirect_preference_list = ''
# comma separated list of pairs of database and node id.
# example: postgres:primary,mydb[0-4]:1,mydb[5-9]:2'
# valid for streaming replicaton mode only.
app_name_redirect_preference_list = ''
# comma separated list of pairs of app name and node id.
# example: 'psql:primary,myapp[0-4]:1,myapp[5-9]:standby'
# valid for streaming replicaton mode only.
allow_sql_comments = off
# if on, ignore SQL comments when judging if load balance or
# query cache is possible.
# If off, SQL comments effectively prevent the judgment
# (pre 3.4 behavior).
#------------------------------------------------------------------------------
# MASTER/SLAVE MODE
#------------------------------------------------------------------------------
master_slave_mode = off
# Activate master/slave mode
# (change requires restart)
master_slave_sub_mode = 'slony'
# Master/slave sub mode
# Valid values are combinations slony or
# stream. Default is slony.
# (change requires restart)
# - Streaming -
sr_check_period = 0
# Streaming replication check period
# Disabled (0) by default
sr_check_user = 'nobody'
# Streaming replication check user
# This is necessary even if you disable
# streaming replication delay check with
# sr_check_period = 0
sr_check_password = ''
# Password for streaming replication check user
delay_threshold = 0
# Threshold before not dispatching query to standby node
# Unit is in bytes
# Disabled (0) by default
# - Special commands -
follow_master_command = ''
# Executes this command after master failover
# Special values:
# %d = node id
# %h = host name
# %p = port number
# %D = database cluster path
# %m = new master node id
# %H = hostname of the new master node
# %M = old master node id
# %P = old primary node id
# %r = new master port number
# %R = new master database cluster path
# %% = '%' character
#------------------------------------------------------------------------------
# HEALTH CHECK
#------------------------------------------------------------------------------
health_check_period = 0
# Health check period
# Disabled (0) by default
health_check_timeout = 20
# Health check timeout
# 0 means no timeout
health_check_user = 'nobody'
# Health check user
health_check_password = ''
# Password for health check user
health_check_max_retries = 0
# Maximum number of times to retry a failed health check before giving up.
health_check_retry_delay = 1
# Amount of time to wait (in seconds) between retries.
connect_timeout = 10000
# Timeout value in milliseconds before giving up to connect to backend.
# Default is 10000 ms (10 second). Flaky network user may want to increase
# the value. 0 means no timeout.
# Note that this value is not only used for health check,
# but also for ordinary conection to backend.
#------------------------------------------------------------------------------
# FAILOVER AND FAILBACK
#------------------------------------------------------------------------------
failover_command = ''
# Executes this command at failover
# Special values:
# %d = node id
# %h = host name
# %p = port number
# %D = database cluster path
# %m = new master node id
# %H = hostname of the new master node
# %M = old master node id
# %P = old primary node id
# %r = new master port number
# %R = new master database cluster path
# %% = '%' character
failback_command = ''
# Executes this command at failback.
# Special values:
# %d = node id
# %h = host name
# %p = port number
# %D = database cluster path
# %m = new master node id
# %H = hostname of the new master node
# %M = old master node id
# %P = old primary node id
# %r = new master port number
# %R = new master database cluster path
# %% = '%' character
fail_over_on_backend_error = on
# Initiates failover when reading/writing to the
# backend communication socket fails
# If set to off, pgpool will report an
# error and disconnect the session.
search_primary_node_timeout = 10
# Timeout in seconds to search for the
# primary node when a failover occurs.
# 0 means no timeout, keep searching
# for a primary node forever.
#------------------------------------------------------------------------------
# ONLINE RECOVERY
#------------------------------------------------------------------------------
recovery_user = 'nobody'
# Online recovery user
recovery_password = ''
# Online recovery password
recovery_1st_stage_command = ''
# Executes a command in first stage
recovery_2nd_stage_command = ''
# Executes a command in second stage
recovery_timeout = 90
# Timeout in seconds to wait for the
# recovering node's postmaster to start up
# 0 means no wait
client_idle_limit_in_recovery = 0
# Client is disconnected after being idle
# for that many seconds in the second stage
# of online recovery
# 0 means no disconnection
# -1 means immediate disconnection
#------------------------------------------------------------------------------
# WATCHDOG
#------------------------------------------------------------------------------
# - Enabling -
use_watchdog = off
# Activates watchdog
# (change requires restart)
# -Connection to up stream servers -
trusted_servers = ''
# trusted server list which are used
# to confirm network connection
# (hostA,hostB,hostC,...)
# (change requires restart)
ping_path = '/bin'
# ping command path
# (change requires restart)
# - Watchdog communication Settings -
wd_hostname = ''
# Host name or IP address of this watchdog
# (change requires restart)
wd_port = 9000
# port number for watchdog service
# (change requires restart)
wd_authkey = ''
# Authentication key for watchdog communication
# (change requires restart)
# - Virtual IP control Setting -
delegate_IP = ''
# delegate IP address
# If this is empty, virtual IP never bring up.
# (change requires restart)
ifconfig_path = '/sbin'
# ifconfig command path
# (change requires restart)
if_up_cmd = 'ifconfig eth0:0 inet $_IP_$ netmask 255.255.255.0'
# startup delegate IP command
# (change requires restart)
if_down_cmd = 'ifconfig eth0:0 down'
# shutdown delegate IP command
# (change requires restart)
arping_path = '/usr/sbin' # arping command path
# (change requires restart)
arping_cmd = 'arping -U $_IP_$ -w 1'
# arping command
# (change requires restart)
# - Behaivor on escalation Setting -
clear_memqcache_on_escalation = on
# Clear all the query cache on shared memory
# when standby pgpool escalate to active pgpool
# (= virtual IP holder).
# This should be off if client connects to pgpool
# not using virtual IP.
# (change requires restart)
wd_escalation_command = ''
# Executes this command at escalation on new active pgpool.
# (change requires restart)
# - Lifecheck Setting -
# -- common --
wd_lifecheck_method = 'heartbeat'
# Method of watchdog lifecheck ('heartbeat' or 'query')
# (change requires restart)
wd_interval = 10
# lifecheck interval (sec) > 0
# (change requires restart)
# -- heartbeat mode --
wd_heartbeat_port = 9694
# Port number for receiving heartbeat signal
# (change requires restart)
wd_heartbeat_keepalive = 2
# Interval time of sending heartbeat signal (sec)
# (change requires restart)
wd_heartbeat_deadtime = 30
# Deadtime interval for heartbeat signal (sec)
# (change requires restart)
heartbeat_destination0 = 'host0_ip1'
# Host name or IP address of destination 0
# for sending heartbeat signal.
# (change requires restart)
heartbeat_destination_port0 = 9694
# Port number of destination 0 for sending
# heartbeat signal. Usually this is the
# same as wd_heartbeat_port.
# (change requires restart)
heartbeat_device0 = ''
# Name of NIC device (such like 'eth0')
# used for sending/receiving heartbeat
# signal to/from destination 0.
# This works only when this is not empty
# and pgpool has root privilege.
# (change requires restart)
#heartbeat_destination1 = 'host0_ip2'
#heartbeat_destination_port1 = 9694
#heartbeat_device1 = ''
# -- query mode --
wd_life_point = 3
# lifecheck retry times
# (change requires restart)
wd_lifecheck_query = 'SELECT 1'
# lifecheck query to pgpool from watchdog
# (change requires restart)
wd_lifecheck_dbname = 'template1'
# Database name connected for lifecheck
# (change requires restart)
wd_lifecheck_user = 'nobody'
# watchdog user monitoring pgpools in lifecheck
# (change requires restart)
wd_lifecheck_password = ''
# Password for watchdog user in lifecheck
# (change requires restart)
# - Other pgpool Connection Settings -
#other_pgpool_hostname0 = 'host0'
# Host name or IP address to connect to for other pgpool 0
# (change requires restart)
#other_pgpool_port0 = 5432
# Port number for othet pgpool 0
# (change requires restart)
#other_wd_port0 = 9000
# Port number for othet watchdog 0
# (change requires restart)
#other_pgpool_hostname1 = 'host1'
#other_pgpool_port1 = 5432
#other_wd_port1 = 9000
#------------------------------------------------------------------------------
# OTHERS
#------------------------------------------------------------------------------
relcache_expire = 0
# Life time of relation cache in seconds.
# 0 means no cache expiration(the default).
# The relation cache is used for cache the
# query result against PostgreSQL system
# catalog to obtain various information
# including table structures or if it's a
# temporary table or not. The cache is
# maintained in a pgpool child local memory
# and being kept as long as it survives.
# If someone modify the table by using
# ALTER TABLE or some such, the relcache is
# not consistent anymore.
# For this purpose, cache_expiration
# controls the life time of the cache.
relcache_size = 256
# Number of relation cache
# entry. If you see frequently:
# "pool_search_relcache: cache replacement happend"
# in the pgpool log, you might want to increate this number.
check_temp_table = on
# If on, enable temporary table check in SELECT statements.
# This initiates queries against system catalog of primary/master
# thus increases load of master.
# If you are absolutely sure that your system never uses temporary tables
# and you want to save access to primary/master, you could turn this off.
# Default is on.
check_unlogged_table = on
# If on, enable unlogged table check in SELECT statements.
# This initiates queries against system catalog of primary/master
# thus increases load of master.
# If you are absolutely sure that your system never uses unlogged tables
# and you want to save access to primary/master, you could turn this off.
# Default is on.
#------------------------------------------------------------------------------
# IN MEMORY QUERY MEMORY CACHE
#------------------------------------------------------------------------------
memory_cache_enabled = on
memqcache_method = 'shmem'
memqcache_memcached_host = 'localhost'
memqcache_memcached_port = 11211
# In bytes
memqcache_total_size = {{ memqcache_total_size_byte }}
memqcache_max_num_cache = 1000000
# Total number of cache entries. Mandatory
# if memqcache_method = 'shmem'.
# Each cache entry consumes 48 bytes on shared memory.
# Defaults to 1,000,000(45.8MB).
# (change requires restart)
# Never expire cache entries
memqcache_expire = 0
memqcache_auto_cache_invalidation = on
# Max size of each query
memqcache_maxcache = {{ memqcache_maxcache_byte }}
# TODO Assert cache_block_size > maxcache
memqcache_cache_block_size = {{ memqcache_cache_block_size_byte }}
memqcache_oiddir = '{{ memqcache_oiddir }}'
white_memqcache_table_list = ''
black_memqcache_table_list = ''
# vim: ft=cfg

Voir le fichier

@ -1,641 +0,0 @@
# -----------------------------
# PostgreSQL configuration file
# -----------------------------
#
# This file consists of lines of the form:
#
# name = value
#
# (The "=" is optional.) Whitespace may be used. Comments are introduced with
# "#" anywhere on a line. The complete list of parameter names and allowed
# values can be found in the PostgreSQL documentation.
#
# The commented-out settings shown in this file represent the default values.
# Re-commenting a setting is NOT sufficient to revert it to the default value;
# you need to reload the server.
#
# This file is read on server startup and when the server receives a SIGHUP
# signal. If you edit the file on a running system, you have to SIGHUP the
# server for the changes to take effect, or use "pg_ctl reload". Some
# parameters, which are marked below, require a server shutdown and restart to
# take effect.
#
# Any parameter can also be given as a command-line option to the server, e.g.,
# "postgres -c log_connections=on". Some parameters can be changed at run time
# with the "SET" SQL command.
#
# Memory units: kB = kilobytes Time units: ms = milliseconds
# MB = megabytes s = seconds
# GB = gigabytes min = minutes
# TB = terabytes h = hours
# d = days
#------------------------------------------------------------------------------
# FILE LOCATIONS
#------------------------------------------------------------------------------
# The default values of these variables are driven from the -D command-line
# option or PGDATA environment variable, represented here as ConfigDir.
#data_directory = 'ConfigDir' # use data in another directory
# (change requires restart)
#hba_file = 'ConfigDir/pg_hba.conf' # host-based authentication file
# (change requires restart)
#ident_file = 'ConfigDir/pg_ident.conf' # ident configuration file
# (change requires restart)
# If external_pid_file is not explicitly set, no extra PID file is written.
#external_pid_file = '' # write an extra PID file
# (change requires restart)
#------------------------------------------------------------------------------
# CONNECTIONS AND AUTHENTICATION
#------------------------------------------------------------------------------
# - Connection Settings -
#listen_addresses = 'localhost' # what IP address(es) to listen on;
# comma-separated list of addresses;
# defaults to 'localhost'; use '*' for all
# (change requires restart)
#port = 5432 # (change requires restart)
max_connections = 100 # (change requires restart)
#superuser_reserved_connections = 3 # (change requires restart)
#unix_socket_directories = '/var/run/postgresql, /tmp' # comma-separated list of directories
# (change requires restart)
#unix_socket_group = '' # (change requires restart)
#unix_socket_permissions = 0777 # begin with 0 to use octal notation
# (change requires restart)
#bonjour = off # advertise server via Bonjour
# (change requires restart)
#bonjour_name = '' # defaults to the computer name
# (change requires restart)
# - Security and Authentication -
#authentication_timeout = 1min # 1s-600s
#ssl = off # (change requires restart)
#ssl_ciphers = 'HIGH:MEDIUM:+3DES:!aNULL' # allowed SSL ciphers
# (change requires restart)
#ssl_prefer_server_ciphers = on # (change requires restart)
#ssl_ecdh_curve = 'prime256v1' # (change requires restart)
#ssl_cert_file = 'server.crt' # (change requires restart)
#ssl_key_file = 'server.key' # (change requires restart)
#ssl_ca_file = '' # (change requires restart)
#ssl_crl_file = '' # (change requires restart)
#password_encryption = on
#db_user_namespace = off
#row_security = on
# GSSAPI using Kerberos
#krb_server_keyfile = ''
#krb_caseins_users = off
# - TCP Keepalives -
# see "man 7 tcp" for details
#tcp_keepalives_idle = 0 # TCP_KEEPIDLE, in seconds;
# 0 selects the system default
#tcp_keepalives_interval = 0 # TCP_KEEPINTVL, in seconds;
# 0 selects the system default
#tcp_keepalives_count = 0 # TCP_KEEPCNT;
# 0 selects the system default
#------------------------------------------------------------------------------
# RESOURCE USAGE (except WAL)
#------------------------------------------------------------------------------
# - Memory -
shared_buffers = 128MB # min 128kB
# (change requires restart)
#huge_pages = try # on, off, or try
# (change requires restart)
#temp_buffers = 8MB # min 800kB
#max_prepared_transactions = 0 # zero disables the feature
# (change requires restart)
# Caution: it is not advisable to set max_prepared_transactions nonzero unless
# you actively intend to use prepared transactions.
#work_mem = 4MB # min 64kB
#maintenance_work_mem = 64MB # min 1MB
#replacement_sort_tuples = 150000 # limits use of replacement selection sort
#autovacuum_work_mem = -1 # min 1MB, or -1 to use maintenance_work_mem
#max_stack_depth = 2MB # min 100kB
dynamic_shared_memory_type = posix # the default is the first option
# supported by the operating system:
# posix
# sysv
# windows
# mmap
# use none to disable dynamic shared memory
# - Disk -
#temp_file_limit = -1 # limits per-process temp file space
# in kB, or -1 for no limit
# - Kernel Resource Usage -
#max_files_per_process = 1000 # min 25
# (change requires restart)
#shared_preload_libraries = '' # (change requires restart)
# - Cost-Based Vacuum Delay -
#vacuum_cost_delay = 0 # 0-100 milliseconds
#vacuum_cost_page_hit = 1 # 0-10000 credits
#vacuum_cost_page_miss = 10 # 0-10000 credits
#vacuum_cost_page_dirty = 20 # 0-10000 credits
#vacuum_cost_limit = 200 # 1-10000 credits
# - Background Writer -
#bgwriter_delay = 200ms # 10-10000ms between rounds
#bgwriter_lru_maxpages = 100 # 0-1000 max buffers written/round
#bgwriter_lru_multiplier = 2.0 # 0-10.0 multiplier on buffers scanned/round
#bgwriter_flush_after = 512kB # measured in pages, 0 disables
# - Asynchronous Behavior -
#effective_io_concurrency = 1 # 1-1000; 0 disables prefetching
#max_worker_processes = 8 # (change requires restart)
#max_parallel_workers_per_gather = 0 # taken from max_worker_processes
#old_snapshot_threshold = -1 # 1min-60d; -1 disables; 0 is immediate
# (change requires restart)
#backend_flush_after = 0 # measured in pages, 0 disables
#------------------------------------------------------------------------------
# WRITE AHEAD LOG
#------------------------------------------------------------------------------
# - Settings -
#wal_level = minimal # minimal, replica, or logical
# (change requires restart)
#fsync = on # flush data to disk for crash safety
# (turning this off can cause
# unrecoverable data corruption)
#synchronous_commit = on # synchronization level;
# off, local, remote_write, remote_apply, or on
#wal_sync_method = fsync # the default is the first option
# supported by the operating system:
# open_datasync
# fdatasync (default on Linux)
# fsync
# fsync_writethrough
# open_sync
#full_page_writes = on # recover from partial page writes
#wal_compression = off # enable compression of full-page writes
#wal_log_hints = off # also do full page writes of non-critical updates
# (change requires restart)
#wal_buffers = -1 # min 32kB, -1 sets based on shared_buffers
# (change requires restart)
#wal_writer_delay = 200ms # 1-10000 milliseconds
#wal_writer_flush_after = 1MB # measured in pages, 0 disables
#commit_delay = 0 # range 0-100000, in microseconds
#commit_siblings = 5 # range 1-1000
# - Checkpoints -
#checkpoint_timeout = 5min # range 30s-1d
#max_wal_size = 1GB
#min_wal_size = 80MB
#checkpoint_completion_target = 0.5 # checkpoint target duration, 0.0 - 1.0
#checkpoint_flush_after = 256kB # measured in pages, 0 disables
#checkpoint_warning = 30s # 0 disables
# - Archiving -
#archive_mode = off # enables archiving; off, on, or always
# (change requires restart)
#archive_command = '' # command to use to archive a logfile segment
# placeholders: %p = path of file to archive
# %f = file name only
# e.g. 'test ! -f /mnt/server/archivedir/%f && cp %p /mnt/server/archivedir/%f'
#archive_timeout = 0 # force a logfile segment switch after this
# number of seconds; 0 disables
#------------------------------------------------------------------------------
# REPLICATION
#------------------------------------------------------------------------------
# - Sending Server(s) -
# Set these on the master and on any standby that will send replication data.
#max_wal_senders = 0 # max number of walsender processes
# (change requires restart)
#wal_keep_segments = 0 # in logfile segments, 16MB each; 0 disables
#wal_sender_timeout = 60s # in milliseconds; 0 disables
#max_replication_slots = 0 # max number of replication slots
# (change requires restart)
#track_commit_timestamp = off # collect timestamp of transaction commit
# (change requires restart)
# - Master Server -
# These settings are ignored on a standby server.
#synchronous_standby_names = '' # standby servers that provide sync rep
# number of sync standbys and comma-separated list of application_name
# from standby(s); '*' = all
#vacuum_defer_cleanup_age = 0 # number of xacts by which cleanup is delayed
# - Standby Servers -
# These settings are ignored on a master server.
#hot_standby = off # "on" allows queries during recovery
# (change requires restart)
#max_standby_archive_delay = 30s # max delay before canceling queries
# when reading WAL from archive;
# -1 allows indefinite delay
#max_standby_streaming_delay = 30s # max delay before canceling queries
# when reading streaming WAL;
# -1 allows indefinite delay
#wal_receiver_status_interval = 10s # send replies at least this often
# 0 disables
#hot_standby_feedback = off # send info from standby to prevent
# query conflicts
#wal_receiver_timeout = 60s # time that receiver waits for
# communication from master
# in milliseconds; 0 disables
#wal_retrieve_retry_interval = 5s # time to wait before retrying to
# retrieve WAL after a failed attempt
#------------------------------------------------------------------------------
# QUERY TUNING
#------------------------------------------------------------------------------
# - Planner Method Configuration -
#enable_bitmapscan = on
#enable_hashagg = on
#enable_hashjoin = on
#enable_indexscan = on
#enable_indexonlyscan = on
#enable_material = on
#enable_mergejoin = on
#enable_nestloop = on
#enable_seqscan = on
#enable_sort = on
#enable_tidscan = on
# - Planner Cost Constants -
#seq_page_cost = 1.0 # measured on an arbitrary scale
#random_page_cost = 4.0 # same scale as above
#cpu_tuple_cost = 0.01 # same scale as above
#cpu_index_tuple_cost = 0.005 # same scale as above
#cpu_operator_cost = 0.0025 # same scale as above
#parallel_tuple_cost = 0.1 # same scale as above
#parallel_setup_cost = 1000.0 # same scale as above
#min_parallel_relation_size = 8MB
#effective_cache_size = 4GB
# - Genetic Query Optimizer -
#geqo = on
#geqo_threshold = 12
#geqo_effort = 5 # range 1-10
#geqo_pool_size = 0 # selects default based on effort
#geqo_generations = 0 # selects default based on effort
#geqo_selection_bias = 2.0 # range 1.5-2.0
#geqo_seed = 0.0 # range 0.0-1.0
# - Other Planner Options -
#default_statistics_target = 100 # range 1-10000
#constraint_exclusion = partition # on, off, or partition
#cursor_tuple_fraction = 0.1 # range 0.0-1.0
#from_collapse_limit = 8
#join_collapse_limit = 8 # 1 disables collapsing of explicit
# JOIN clauses
#force_parallel_mode = off
#------------------------------------------------------------------------------
# ERROR REPORTING AND LOGGING
#------------------------------------------------------------------------------
# - Where to Log -
log_destination = 'stderr' # Valid values are combinations of
# stderr, csvlog, syslog, and eventlog,
# depending on platform. csvlog
# requires logging_collector to be on.
# This is used when logging to stderr:
logging_collector = on # Enable capturing of stderr and csvlog
# into log files. Required to be on for
# csvlogs.
# (change requires restart)
# These are only used if logging_collector is on:
log_directory = 'pg_log' # directory where log files are written,
# can be absolute or relative to PGDATA
log_filename = 'postgresql-%a.log' # log file name pattern,
# can include strftime() escapes
#log_file_mode = 0600 # creation mode for log files,
# begin with 0 to use octal notation
log_truncate_on_rotation = on # If on, an existing log file with the
# same name as the new log file will be
# truncated rather than appended to.
# But such truncation only occurs on
# time-driven rotation, not on restarts
# or size-driven rotation. Default is
# off, meaning append to existing files
# in all cases.
log_rotation_age = 1d # Automatic rotation of logfiles will
# happen after that time. 0 disables.
log_rotation_size = 0 # Automatic rotation of logfiles will
# happen after that much log output.
# 0 disables.
# These are relevant when logging to syslog:
#syslog_facility = 'LOCAL0'
#syslog_ident = 'postgres'
#syslog_sequence_numbers = on
#syslog_split_messages = on
# This is only relevant when logging to eventlog (win32):
#event_source = 'PostgreSQL'
# - When to Log -
#client_min_messages = notice # values in order of decreasing detail:
# debug5
# debug4
# debug3
# debug2
# debug1
# log
# notice
# warning
# error
#log_min_messages = warning # values in order of decreasing detail:
# debug5
# debug4
# debug3
# debug2
# debug1
# info
# notice
# warning
# error
# log
# fatal
# panic
#log_min_error_statement = error # values in order of decreasing detail:
# debug5
# debug4
# debug3
# debug2
# debug1
# info
# notice
# warning
# error
# log
# fatal
# panic (effectively off)
#log_min_duration_statement = -1 # -1 is disabled, 0 logs all statements
# and their durations, > 0 logs only
# statements running at least this number
# of milliseconds
# - What to Log -
#debug_print_parse = off
#debug_print_rewritten = off
#debug_print_plan = off
#debug_pretty_print = on
#log_checkpoints = off
#log_connections = off
#log_disconnections = off
#log_duration = off
#log_error_verbosity = default # terse, default, or verbose messages
#log_hostname = off
log_line_prefix = '< %m > ' # special values:
# %a = application name
# %u = user name
# %d = database name
# %r = remote host and port
# %h = remote host
# %p = process ID
# %t = timestamp without milliseconds
# %m = timestamp with milliseconds
# %n = timestamp with milliseconds (as a Unix epoch)
# %i = command tag
# %e = SQL state
# %c = session ID
# %l = session line number
# %s = session start timestamp
# %v = virtual transaction ID
# %x = transaction ID (0 if none)
# %q = stop here in non-session
# processes
# %% = '%'
# e.g. '<%u%%%d> '
#log_lock_waits = off # log lock waits >= deadlock_timeout
#log_statement = 'none' # none, ddl, mod, all
#log_replication_commands = off
#log_temp_files = -1 # log temporary files equal or larger
# than the specified size in kilobytes;
# -1 disables, 0 logs all temp files
log_timezone = 'UTC'
# - Process Title -
#cluster_name = '' # added to process titles if nonempty
# (change requires restart)
#update_process_title = on
#------------------------------------------------------------------------------
# RUNTIME STATISTICS
#------------------------------------------------------------------------------
# - Query/Index Statistics Collector -
#track_activities = on
#track_counts = on
#track_io_timing = off
#track_functions = none # none, pl, all
#track_activity_query_size = 1024 # (change requires restart)
#stats_temp_directory = 'pg_stat_tmp'
# - Statistics Monitoring -
#log_parser_stats = off
#log_planner_stats = off
#log_executor_stats = off
#log_statement_stats = off
#------------------------------------------------------------------------------
# AUTOVACUUM PARAMETERS
#------------------------------------------------------------------------------
#autovacuum = on # Enable autovacuum subprocess? 'on'
# requires track_counts to also be on.
#log_autovacuum_min_duration = -1 # -1 disables, 0 logs all actions and
# their durations, > 0 logs only
# actions running at least this number
# of milliseconds.
#autovacuum_max_workers = 3 # max number of autovacuum subprocesses
# (change requires restart)
#autovacuum_naptime = 1min # time between autovacuum runs
#autovacuum_vacuum_threshold = 50 # min number of row updates before
# vacuum
#autovacuum_analyze_threshold = 50 # min number of row updates before
# analyze
#autovacuum_vacuum_scale_factor = 0.2 # fraction of table size before vacuum
#autovacuum_analyze_scale_factor = 0.1 # fraction of table size before analyze
#autovacuum_freeze_max_age = 200000000 # maximum XID age before forced vacuum
# (change requires restart)
#autovacuum_multixact_freeze_max_age = 400000000 # maximum multixact age
# before forced vacuum
# (change requires restart)
#autovacuum_vacuum_cost_delay = 20ms # default vacuum cost delay for
# autovacuum, in milliseconds;
# -1 means use vacuum_cost_delay
#autovacuum_vacuum_cost_limit = -1 # default vacuum cost limit for
# autovacuum, -1 means use
# vacuum_cost_limit
#------------------------------------------------------------------------------
# CLIENT CONNECTION DEFAULTS
#------------------------------------------------------------------------------
# - Statement Behavior -
#search_path = '"$user", public' # schema names
#default_tablespace = '' # a tablespace name, '' uses the default
#temp_tablespaces = '' # a list of tablespace names, '' uses
# only default tablespace
#check_function_bodies = on
#default_transaction_isolation = 'read committed'
#default_transaction_read_only = off
#default_transaction_deferrable = off
#session_replication_role = 'origin'
#statement_timeout = 0 # in milliseconds, 0 is disabled
#lock_timeout = 0 # in milliseconds, 0 is disabled
#idle_in_transaction_session_timeout = 0 # in milliseconds, 0 is disabled
#vacuum_freeze_min_age = 50000000
#vacuum_freeze_table_age = 150000000
#vacuum_multixact_freeze_min_age = 5000000
#vacuum_multixact_freeze_table_age = 150000000
#bytea_output = 'hex' # hex, escape
#xmlbinary = 'base64'
#xmloption = 'content'
#gin_fuzzy_search_limit = 0
#gin_pending_list_limit = 4MB
# - Locale and Formatting -
datestyle = 'iso, mdy'
#intervalstyle = 'postgres'
timezone = 'UTC'
#timezone_abbreviations = 'Default' # Select the set of available time zone
# abbreviations. Currently, there are
# Default
# Australia (historical usage)
# India
# You can create your own file in
# share/timezonesets/.
#extra_float_digits = 0 # min -15, max 3
#client_encoding = sql_ascii # actually, defaults to database
# encoding
# These settings are initialized by initdb, but they can be changed.
lc_messages = 'en_US.UTF-8' # locale for system error message
# strings
lc_monetary = 'en_US.UTF-8' # locale for monetary formatting
lc_numeric = 'en_US.UTF-8' # locale for number formatting
lc_time = 'en_US.UTF-8' # locale for time formatting
# default configuration for text search
default_text_search_config = 'pg_catalog.english'
# - Other Defaults -
#dynamic_library_path = '$libdir'
#local_preload_libraries = ''
#session_preload_libraries = ''
#------------------------------------------------------------------------------
# LOCK MANAGEMENT
#------------------------------------------------------------------------------
#deadlock_timeout = 1s
#max_locks_per_transaction = 64 # min 10
# (change requires restart)
#max_pred_locks_per_transaction = 64 # min 10
# (change requires restart)
#------------------------------------------------------------------------------
# VERSION/PLATFORM COMPATIBILITY
#------------------------------------------------------------------------------
# - Previous PostgreSQL Versions -
#array_nulls = on
#backslash_quote = safe_encoding # on, off, or safe_encoding
#default_with_oids = off
#escape_string_warning = on
#lo_compat_privileges = off
#operator_precedence_warning = off
#quote_all_identifiers = off
#sql_inheritance = on
#standard_conforming_strings = on
#synchronize_seqscans = on
# - Other Platforms and Clients -
#transform_null_equals = off
#------------------------------------------------------------------------------
# ERROR HANDLING
#------------------------------------------------------------------------------
#exit_on_error = off # terminate session on any error?
#restart_after_crash = on # reinitialize after backend crash?
#------------------------------------------------------------------------------
# CONFIG FILE INCLUDES
#------------------------------------------------------------------------------
# These options allow settings to be loaded from files other than the
# default postgresql.conf.
#include_dir = 'conf.d' # include files ending in '.conf' from
# directory 'conf.d'
#include_if_exists = 'exists.conf' # include file only if it exists
#include = 'special.conf' # include file
#------------------------------------------------------------------------------
# CUSTOMIZED OPTIONS
#------------------------------------------------------------------------------
# Add settings for extensions here

Voir le fichier

@ -1,10 +0,0 @@
memqcache_oiddir: '/var/log/pgpool/oiddir'
# TODO Check the machine has enough RAM
# ~10 GB
memqcache_total_size_byte: 10737418240
# 131 mb
memqcache_maxcache_byte: 131072000
# Has to be bigger or equal to maxcache_byte
memqcache_cache_block_size_byte: 131072000
# 20 seconds idle max (application shouldn't even make idle connections)
child_life_time: 20

Voir le fichier

@ -1,15 +0,0 @@
- name: Set up webserver
hosts: webservers
roles:
- common
- firewalld
#- docker
- golang
- name: Set up databases
hosts: dbs
roles:
- common
- backup
- postgresql
- elasticsearch

Voir le fichier

@ -1,28 +0,0 @@
# We're not putting elasticsearch role because we don't want to re-enable the
# reindexing cron job before we've actually made the swap
- name: Populate elasticsearch index from database
hosts: dbs
roles:
- common
- postgresql
tasks:
- name: Swap elasticsearch index
uri:
url: "http://localhost:9200/_aliases"
method: POST
body_format: json
body:
actions:
- remove:
index: "{{ nyaapantsu_elasticsearch_old_index }}"
alias: "{{ nyaapantsu_elasticsearch_alias }}"
- add:
index: "{{ nyaapantsu_elasticsearch_index }}"
alias: "{{ nyaapantsu_elasticsearch_alias }}"
- name: Re-enable reindex cron job
command: mv "/tmp/reindex_{{ nyaapantsu_torrent_tablename }}" /etc/cron.d/
become: true
ignore_errors: yes # Can ignore error here if the file had previously been
# moved.

Voir le fichier

@ -1,20 +0,0 @@
version: '3'
services:
pantsu:
build:
context: ..
dockerfile: deploy/Dockerfile
command: ./deploy/init.sh
env_file:
- postgres-prod.env
environment:
- PANTSU_INTERNAL_PORT=${PANTSU_INTERNAL_PORT}
network_mode: "host"
ports:
# 0.0.0.0 makes it accessible to the network
# You may want to remove it to make pantsu available only
# to localhost
- 0.0.0.0:${PANTSU_EXTERNAL_PORT}:${PANTSU_INTERNAL_PORT}
volumes:
- "${GOPATH}:/go/"
- "./..:/nyaa"

Voir le fichier

@ -1,34 +0,0 @@
version: '3'
services:
pantsu:
build:
context: ..
dockerfile: deploy/Dockerfile
command: ./deploy/init.sh
depends_on:
- pantsu_db
env_file:
- postgres.env
environment:
- PANTSU_INTERNAL_PORT=${PANTSU_INTERNAL_PORT}
links:
- pantsu_db
ports:
# 0.0.0.0 makes it accessible to the network
# You may want to remove it to make pantsu available only
# to localhost
- 0.0.0.0:${PANTSU_EXTERNAL_PORT}:${PANTSU_INTERNAL_PORT}
volumes:
- "${GOPATH}:/go/"
- "./..:/nyaa"
pantsu_db:
env_file:
- postgres.env
environment:
- PANTSU_POSTGRES_DBFILE=${PANTSU_POSTGRES_DBFILE}
image: postgres:9.6.2
volumes:
# restore.sh will be sourced after initial initdb
- "./restore.sh:/docker-entrypoint-initdb.d/restore.sh"
- "./../${PANTSU_POSTGRES_DBFILE}:/${PANTSU_POSTGRES_DBFILE}"

Voir le fichier

@ -1,19 +0,0 @@
version: '3'
services:
pantsu:
build:
context: ..
dockerfile: deploy/Dockerfile
command: ./deploy/init.sh
env_file:
- sqlite.env
environment:
- PANTSU_INTERNAL_PORT=${PANTSU_INTERNAL_PORT}
ports:
# 0.0.0.0 makes it accessible to the network
# You may want to remove it to make pantsu available only
# to localhost
- 0.0.0.0:${PANTSU_EXTERNAL_PORT}:${PANTSU_INTERNAL_PORT}
volumes:
- "${GOPATH}:/go/"
- "./..:/nyaa"

Voir le fichier

@ -1,16 +0,0 @@
#!/bin/bash
set -eux
# TODO Doesn't scale, find another way to wait until db is ready
if [[ "${PANTSU_DBTYPE}" = "postgres" ]]; then
echo 'Waiting for the database to be ready...'
sleep 40
fi
go get github.com/NyaaPantsu/nyaa
go build
./nyaa -host 0.0.0.0 \
-port "${PANTSU_INTERNAL_PORT}" \
-dbtype "${PANTSU_DBTYPE}" \
-dbparams "${PANTSU_DBPARAMS}"

Voir le fichier

@ -1,9 +0,0 @@
POSTGRES_USER=nyaapantsu
POSTGRES_PASSWORD=nyaapantsu
POSTGRES_DB=nyaapantsu
PANTSU_DBTYPE=postgres
# TODO Would prefer to use the line below but docker doesn't seem to accept
# that.
#PANTSU_DBPARAMS=host=pantsu_db user=${POSTGRES_USER} dbname=${POSTGRES_DB} sslmode=disable password=${POSTGRES_PASSWORD}
PANTSU_DBPARAMS=host=localhost port=9998 user=nyaapantsu dbname=nyaapantsu sslmode=disable password=nyaapantsu

Voir le fichier

@ -1,9 +0,0 @@
POSTGRES_USER=nyaapantsu
POSTGRES_PASSWORD=nyaapantsu
POSTGRES_DB=nyaapantsu
PANTSU_DBTYPE=postgres
# TODO Would prefer to use the line below but docker doesn't seem to accept
# that.
#PANTSU_DBPARAMS=host=pantsu_db user=${POSTGRES_USER} dbname=${POSTGRES_DB} sslmode=disable password=${POSTGRES_PASSWORD}
PANTSU_DBPARAMS=host=pantsu_db user=nyaapantsu dbname=nyaapantsu sslmode=disable password=nyaapantsu

Voir le fichier

@ -1,11 +0,0 @@
# Prune images and volumes
#
# Docker tends to take a lot of space. This will remove dangling images and
# volumes not used by at least container.
# WARNING: You might not want to run this if you have stuff that are dangling
# that you want to keep.
#
#!/bin/bash
docker image prune -f
docker volume prune -f

Voir le fichier

@ -1,12 +0,0 @@
#!/bin/bash
# Restore the database from a postgres dump
pg_restore --username "${POSTGRES_USER}" -d ${POSTGRES_DB} "/${PANTSU_POSTGRES_DBFILE}"
last_nyaa_torrent_id=923001
# Setting the sequence to start from after the latest nyaa torrent.
psql -U postgres "${POSTGRES_DB}" <<EOF
alter sequence torrents_torrent_id_seq start
${last_nyaa_torrent_id} restart
EOF

Voir le fichier

@ -1,2 +0,0 @@
PANTSU_DBTYPE=sqlite3
PANTSU_DBPARAMS=./nyaa.db?cache_size=50

Voir le fichier

@ -1,11 +0,0 @@
<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=5,IE=9" ><![endif]-->
<!DOCTYPE html>
<html>
<head>
<title>Nyaapantsu_models</title>
<meta charset="utf-8"/>
</head>
<body><div class="mxgraph" style="max-width:100%;border:1px solid transparent;" data-mxgraph="{&quot;highlight&quot;:&quot;#0000ff&quot;,&quot;nav&quot;:true,&quot;resize&quot;:true,&quot;toolbar&quot;:&quot;zoom layers lightbox&quot;,&quot;edit&quot;:&quot;_blank&quot;,&quot;xml&quot;:&quot;&lt;mxfile userAgent=\&quot;Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101 Firefox/38.0\&quot; version=\&quot;6.6.3\&quot; editor=\&quot;www.draw.io\&quot; type=\&quot;google\&quot;&gt;&lt;diagram name=\&quot;Page-1\&quot;&gt;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&lt;/diagram&gt;&lt;/mxfile&gt;&quot;}"></div>
<script type="text/javascript" src="https://www.draw.io/embed2.js?s=er&"></script>
</body>
</html>

Voir le fichier

@ -1,2 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<mxfile userAgent="Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101 Firefox/38.0" version="6.6.3" editor="www.draw.io" type="google"><diagram name="Page-1">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</diagram></mxfile>

Fichier binaire non affiché.

Voir le fichier

@ -1,91 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/PR-SVG-20010719/DTD/svg10.dtd">
<svg width="54cm" height="22cm" viewBox="9 44 1077 424" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g>
<rect style="fill: #ffffff" x="10" y="50" width="248.7" height="44"/>
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="10" y="50" width="248.7" height="44"/>
<text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:monospace;font-style:normal;font-weight:normal" x="134.35" y="66">&lt;&lt;object&gt;&gt;</text>
<text font-size="16" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="134.35" y="85">API</text>
<rect style="fill: #ffffff" x="10" y="94" width="248.7" height="52"/>
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="10" y="94" width="248.7" height="52"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="13" y="108">+torrents: list</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="13" y="124">+queryRecordCount: number = int</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="13" y="140">+totalRecordCount: number = int</text>
<rect style="fill: #ffffff" x="10" y="146" width="248.7" height="8"/>
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="10" y="146" width="248.7" height="8"/>
</g>
<g>
<rect style="fill: #ffffff" x="330" y="45" width="341.1" height="44"/>
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="330" y="45" width="341.1" height="44"/>
<text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:monospace;font-style:normal;font-weight:normal" x="500.55" y="61">&lt;&lt;object&gt;&gt;</text>
<text font-size="16" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="500.55" y="80">Torrent</text>
<rect style="fill: #ffffff" x="330" y="89" width="341.1" height="356"/>
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="330" y="89" width="341.1" height="356"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="333" y="103">+id: string = int</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="333" y="119">+name: string</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="333" y="135">+status: number = int</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="333" y="151">+hash: string = SHA-1 Hex Digest</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="333" y="167">+date: string = ISO 8601 Date + Time</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="333" y="183">+filesize: number = int</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="333" y="199">+description: string = HTML</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="333" y="215">+comments: list</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="333" y="231">+sub_category: string = int</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="333" y="247">+category: string = int</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="333" y="263">+downloads: number = int</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="333" y="279">+uploader_id: number = int</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="333" y="295">+uploader_name: string</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="333" y="311">+uploader_old: string</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="333" y="327">+website_link: string = URL</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="333" y="343">+magnet: string = magnet URI</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="333" y="359">+torrent: string = URL</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="333" y="375">+seeders: number = int</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="333" y="391">+leechers: number = int</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="333" y="407">+completed: number = int</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="333" y="423">+last_scrape: string = ISO 8601 Date + Time</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="333" y="439">+file_list: list</text>
<rect style="fill: #ffffff" x="330" y="445" width="341.1" height="8"/>
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="330" y="445" width="341.1" height="8"/>
</g>
<g>
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="284.879,102 301.854,102 301.854,67 330,67 "/>
<polygon style="fill: #ffffff" points="259.708,102 273.708,97.2 287.708,102 273.708,106.8 "/>
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="259.708,102 273.708,97.2 287.708,102 273.708,106.8 "/>
</g>
<g>
<rect style="fill: #ffffff" x="797" y="97" width="287.2" height="44"/>
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="797" y="97" width="287.2" height="44"/>
<text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:monospace;font-style:normal;font-weight:normal" x="940.6" y="113">&lt;&lt;object&gt;&gt;</text>
<text font-size="16" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="940.6" y="132">Comment</text>
<rect style="fill: #ffffff" x="797" y="141" width="287.2" height="84"/>
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="797" y="141" width="287.2" height="84"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="800" y="155">+username: string</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="800" y="171">+user_id: number = int</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="800" y="187">+user_avatar: string = URL</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="800" y="203">+content: string = HTML</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="800" y="219">+date: string = ISO 8601 Date + Time</text>
<rect style="fill: #ffffff" x="797" y="225" width="287.2" height="8"/>
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="797" y="225" width="287.2" height="8"/>
</g>
<g>
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="696.272,211 741.05,211 741.05,119 797,119 "/>
<polygon style="fill: #ffffff" points="671.1,211 685.1,206.2 699.1,211 685.1,215.8 "/>
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="671.1,211 685.1,206.2 699.1,211 685.1,215.8 "/>
</g>
<g>
<rect style="fill: #ffffff" x="781" y="352" width="117.8" height="44"/>
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="781" y="352" width="117.8" height="44"/>
<text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:monospace;font-style:normal;font-weight:normal" x="839.9" y="368">&lt;&lt;object&gt;&gt;</text>
<text font-size="16" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="839.9" y="387">File</text>
<rect style="fill: #ffffff" x="781" y="396" width="117.8" height="36"/>
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="781" y="396" width="117.8" height="36"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="784" y="410">+path: string</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="784" y="426">+filesize: int</text>
<rect style="fill: #ffffff" x="781" y="432" width="117.8" height="8"/>
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="781" y="432" width="117.8" height="8"/>
</g>
<g>
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="696.272,435 733.05,435 733.05,374 781,374 "/>
<polygon style="fill: #ffffff" points="671.1,435 685.1,430.2 699.1,435 685.1,439.8 "/>
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="671.1,435 685.1,430.2 699.1,435 685.1,439.8 "/>
</g>
</svg>

Avant

Largeur:  |  Hauteur:  |  Taille: 10 KiB

Fichier binaire non affiché.

Voir le fichier

@ -1,428 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/PR-SVG-20010719/DTD/svg10.dtd">
<svg width="59cm" height="35cm" viewBox="14 -53 1174 688" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g>
<rect style="fill: #ffffff" x="472" y="63" width="203.1" height="18"/>
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="472" y="63" width="203.1" height="18"/>
<text font-size="11.2" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="573.55" y="77">torrents</text>
<rect style="fill: #ffffff" x="472" y="81" width="203.1" height="292"/>
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="472" y="81" width="203.1" height="292"/>
<polygon style="fill: #000000" points="475,91 477,94 479,91 477,88 "/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="481" y="97">torrent_id</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="583.4" y="97">int</text>
<line style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x1="481" y1="98.6" x2="606.5" y2="98.6"/>
<ellipse style="fill: none; fill-opacity:0; stroke-width: 0.2; stroke: #000000" cx="477" cy="107" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="481" y="113">torrent_name</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="583.4" y="113">text</text>
<ellipse style="fill: #000000" cx="477" cy="123" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="481" y="129">torrent_hash</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="583.4" y="129">text</text>
<ellipse style="fill: #000000" cx="477" cy="139" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="481" y="145">category</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="583.4" y="145">int</text>
<ellipse style="fill: #000000" cx="477" cy="155" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="481" y="161">sub_category</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="583.4" y="161">int</text>
<ellipse style="fill: #000000" cx="477" cy="171" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="481" y="177">status</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="583.4" y="177">int</text>
<ellipse style="fill: none; fill-opacity:0; stroke-width: 0.2; stroke: #000000" cx="477" cy="187" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="481" y="193">date</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="583.4" y="193">timestamp</text>
<ellipse style="fill: #000000" cx="477" cy="203" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="481" y="209">uploader</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="583.4" y="209">int</text>
<ellipse style="fill: none; fill-opacity:0; stroke-width: 0.2; stroke: #000000" cx="477" cy="219" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="481" y="225">downloads</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="583.4" y="225">int</text>
<ellipse style="fill: #000000" cx="477" cy="235" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="481" y="241">stardom</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="583.4" y="241">int</text>
<ellipse style="fill: none; fill-opacity:0; stroke-width: 0.2; stroke: #000000" cx="477" cy="251" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="481" y="257">filesize</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="583.4" y="257">bigint</text>
<ellipse style="fill: #000000" cx="477" cy="267" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="481" y="273">description</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="583.4" y="273">text</text>
<ellipse style="fill: none; fill-opacity:0; stroke-width: 0.2; stroke: #000000" cx="477" cy="283" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="481" y="289">website_link</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="583.4" y="289">text</text>
<ellipse style="fill: none; fill-opacity:0; stroke-width: 0.2; stroke: #000000" cx="477" cy="299" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="481" y="305">deleted_at</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="583.4" y="305">timestamp</text>
<ellipse style="fill: none; fill-opacity:0; stroke-width: 0.2; stroke: #000000" cx="477" cy="315" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="481" y="321">seeders</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="583.4" y="321">int</text>
<ellipse style="fill: none; fill-opacity:0; stroke-width: 0.2; stroke: #000000" cx="477" cy="331" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="481" y="337">leechers</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="583.4" y="337">int</text>
<ellipse style="fill: none; fill-opacity:0; stroke-width: 0.2; stroke: #000000" cx="477" cy="347" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="481" y="353">completed</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="583.4" y="353">int</text>
<ellipse style="fill: none; fill-opacity:0; stroke-width: 0.2; stroke: #000000" cx="477" cy="363" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="481" y="369">last_scrape</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="583.4" y="369">timestamptz</text>
</g>
<g>
<rect style="fill: #ffffff" x="16" y="-52" width="187.7" height="18"/>
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="16" y="-52" width="187.7" height="18"/>
<text font-size="11.2" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="109.85" y="-38">comments</text>
<rect style="fill: #ffffff" x="16" y="-34" width="187.7" height="116"/>
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="16" y="-34" width="187.7" height="116"/>
<polygon style="fill: #000000" points="19,-24 21,-21 23,-24 21,-27 "/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="25" y="-18">comment_id</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="112" y="-18">int</text>
<line style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x1="25" y1="-16.4" x2="135.1" y2="-16.4"/>
<ellipse style="fill: #000000" cx="21" cy="-8" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="25" y="-2">torrent_id</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="112" y="-2">int</text>
<ellipse style="fill: #000000" cx="21" cy="8" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="25" y="14">user_id</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="112" y="14">int</text>
<ellipse style="fill: #000000" cx="21" cy="24" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="25" y="30">content</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="112" y="30">text</text>
<ellipse style="fill: #000000" cx="21" cy="40" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="25" y="46">created_at</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="112" y="46">timestamp</text>
<ellipse style="fill: none; fill-opacity:0; stroke-width: 0.2; stroke: #000000" cx="21" cy="56" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="25" y="62">updated_at</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="112" y="62">timestamp</text>
<ellipse style="fill: none; fill-opacity:0; stroke-width: 0.2; stroke: #000000" cx="21" cy="72" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="25" y="78">deleted_at</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="112" y="78">timestamptz</text>
</g>
<g>
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="203.7,-8 337.85,-8 337.85,91 467.528,91 "/>
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="459.764,96 469.764,91 459.764,86 "/>
<text font-size="9.6" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="207.7" y="-12">n</text>
<text font-size="9.6" style="fill: #000000;text-anchor:end;font-family:monospace;font-style:normal;font-weight:normal" x="468" y="87">1</text>
</g>
<g>
<rect style="fill: #ffffff" x="35" y="529" width="149.2" height="18"/>
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="35" y="529" width="149.2" height="18"/>
<text font-size="11.2" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="109.6" y="543">files</text>
<rect style="fill: #ffffff" x="35" y="547" width="149.2" height="68"/>
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="35" y="547" width="149.2" height="68"/>
<polygon style="fill: #000000" points="38,557 40,560 42,557 40,554 "/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="44" y="563">file_id</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="131" y="563">int</text>
<line style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x1="44" y1="564.6" x2="154.1" y2="564.6"/>
<ellipse style="fill: none; fill-opacity:0; stroke-width: 0.2; stroke: #000000" cx="40" cy="573" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="44" y="579">torrent_id</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="131" y="579">int</text>
<ellipse style="fill: none; fill-opacity:0; stroke-width: 0.2; stroke: #000000" cx="40" cy="589" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="44" y="595">path</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="131" y="595">text</text>
<ellipse style="fill: none; fill-opacity:0; stroke-width: 0.2; stroke: #000000" cx="40" cy="605" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="44" y="611">filesize</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="131" y="611">bigint</text>
</g>
<g>
<rect style="fill: #ffffff" x="361" y="390" width="218.5" height="18"/>
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="361" y="390" width="218.5" height="18"/>
<text font-size="11.2" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="470.25" y="404">users</text>
<rect style="fill: #ffffff" x="361" y="408" width="218.5" height="228"/>
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="361" y="408" width="218.5" height="228"/>
<polygon style="fill: #000000" points="364,418 366,421 368,418 366,415 "/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="370" y="424">user_id</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="503.2" y="424">int</text>
<line style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x1="370" y1="425.6" x2="526.3" y2="425.6"/>
<ellipse style="fill: #000000" cx="366" cy="434" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="370" y="440">username</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="503.2" y="440">text</text>
<ellipse style="fill: #000000" cx="366" cy="450" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="370" y="456">password</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="503.2" y="456">text</text>
<ellipse style="fill: none; fill-opacity:0; stroke-width: 0.2; stroke: #000000" cx="366" cy="466" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="370" y="472">email</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="503.2" y="472">text</text>
<ellipse style="fill: #000000" cx="366" cy="482" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="370" y="488">status</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="503.2" y="488">int</text>
<ellipse style="fill: #000000" cx="366" cy="498" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="370" y="504">created_at</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="503.2" y="504">timestamp</text>
<ellipse style="fill: none; fill-opacity:0; stroke-width: 0.2; stroke: #000000" cx="366" cy="514" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="370" y="520">updated_at</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="503.2" y="520">timestamp</text>
<ellipse style="fill: none; fill-opacity:0; stroke-width: 0.2; stroke: #000000" cx="366" cy="530" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="370" y="536">last_login_at</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="503.2" y="536">timestamp</text>
<ellipse style="fill: none; fill-opacity:0; stroke-width: 0.2; stroke: #000000" cx="366" cy="546" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="370" y="552">last_login_ip</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="503.2" y="552">text</text>
<ellipse style="fill: none; fill-opacity:0; stroke-width: 0.2; stroke: #000000" cx="366" cy="562" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="370" y="568">api_token</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="503.2" y="568">text</text>
<ellipse style="fill: #000000" cx="366" cy="578" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="370" y="584">api_token_expiry</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="503.2" y="584">timestamp</text>
<ellipse style="fill: none; fill-opacity:0; stroke-width: 0.2; stroke: #000000" cx="366" cy="594" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="370" y="600">language</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="503.2" y="600">text</text>
<ellipse style="fill: none; fill-opacity:0; stroke-width: 0.2; stroke: #000000" cx="366" cy="610" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="370" y="616">md5</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="503.2" y="616">text</text>
<ellipse style="fill: none; fill-opacity:0; stroke-width: 0.2; stroke: #000000" cx="366" cy="626" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="370" y="632">settings</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="503.2" y="632">text</text>
</g>
<g>
<rect style="fill: #ffffff" x="209" y="439.5" width="133.8" height="18"/>
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="209" y="439.5" width="133.8" height="18"/>
<text font-size="11.2" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="275.9" y="453.5">notifications</text>
<rect style="fill: #ffffff" x="209" y="457.5" width="133.8" height="100"/>
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="209" y="457.5" width="133.8" height="100"/>
<polygon style="fill: #000000" points="212,467.5 214,470.5 216,467.5 214,464.5 "/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="218" y="473.5">id</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="305" y="473.5">int</text>
<line style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x1="218" y1="475.1" x2="328.1" y2="475.1"/>
<ellipse style="fill: none; fill-opacity:0; stroke-width: 0.2; stroke: #000000" cx="214" cy="483.5" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="218" y="489.5">content</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="305" y="489.5">text</text>
<ellipse style="fill: none; fill-opacity:0; stroke-width: 0.2; stroke: #000000" cx="214" cy="499.5" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="218" y="505.5">read</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="305" y="505.5">bool</text>
<ellipse style="fill: none; fill-opacity:0; stroke-width: 0.2; stroke: #000000" cx="214" cy="515.5" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="218" y="521.5">identifier</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="305" y="521.5">text</text>
<ellipse style="fill: none; fill-opacity:0; stroke-width: 0.2; stroke: #000000" cx="214" cy="531.5" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="218" y="537.5">url</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="305" y="537.5">text</text>
<ellipse style="fill: none; fill-opacity:0; stroke-width: 0.2; stroke: #000000" cx="214" cy="547.5" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="218" y="553.5">user_id</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="305" y="553.5">int</text>
</g>
<g>
<rect style="fill: #ffffff" x="14.7" y="103.5" width="187.7" height="18"/>
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="14.7" y="103.5" width="187.7" height="18"/>
<text font-size="11.2" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="108.55" y="117.5">sukebei_comments</text>
<rect style="fill: #ffffff" x="14.7" y="121.5" width="187.7" height="116"/>
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="14.7" y="121.5" width="187.7" height="116"/>
<polygon style="fill: #000000" points="17.7,131.5 19.7,134.5 21.7,131.5 19.7,128.5 "/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="23.7" y="137.5">comment_id</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="110.7" y="137.5">int</text>
<line style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x1="23.7" y1="139.1" x2="133.8" y2="139.1"/>
<ellipse style="fill: #000000" cx="19.7" cy="147.5" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="23.7" y="153.5">torrent_id</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="110.7" y="153.5">int</text>
<ellipse style="fill: #000000" cx="19.7" cy="163.5" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="23.7" y="169.5">user_id</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="110.7" y="169.5">int</text>
<ellipse style="fill: #000000" cx="19.7" cy="179.5" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="23.7" y="185.5">content</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="110.7" y="185.5">text</text>
<ellipse style="fill: #000000" cx="19.7" cy="195.5" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="23.7" y="201.5">created_at</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="110.7" y="201.5">timestamp</text>
<ellipse style="fill: none; fill-opacity:0; stroke-width: 0.2; stroke: #000000" cx="19.7" cy="211.5" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="23.7" y="217.5">updated_at</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="110.7" y="217.5">timestamp</text>
<ellipse style="fill: none; fill-opacity:0; stroke-width: 0.2; stroke: #000000" cx="19.7" cy="227.5" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="23.7" y="233.5">deleted_at</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="110.7" y="233.5">timestamptz</text>
</g>
<g>
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="202.4,147.5 338,147.5 338,91 472,91 "/>
<text font-size="9.59961" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="206.4" y="143.5">n</text>
<text font-size="9.59961" style="fill: #000000;text-anchor:end;font-family:monospace;font-style:normal;font-weight:normal" x="468" y="87">1</text>
</g>
<g>
<rect style="fill: #ffffff" x="595" y="402.75" width="226.2" height="18"/>
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="595" y="402.75" width="226.2" height="18"/>
<text font-size="11.2" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="708.1" y="416.75">torrent_reports</text>
<rect style="fill: #ffffff" x="595" y="420.75" width="226.2" height="84"/>
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="595" y="420.75" width="226.2" height="84"/>
<polygon style="fill: #000000" points="598,430.75 600,433.75 602,430.75 600,427.75 "/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="604" y="436.75">torrent_report_id</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="744.9" y="436.75">int</text>
<line style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x1="604" y1="438.35" x2="768" y2="438.35"/>
<ellipse style="fill: none; fill-opacity:0; stroke-width: 0.2; stroke: #000000" cx="600" cy="446.75" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="604" y="452.75">type</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="744.9" y="452.75">text</text>
<ellipse style="fill: none; fill-opacity:0; stroke-width: 0.2; stroke: #000000" cx="600" cy="462.75" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="604" y="468.75">torrent_id</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="744.9" y="468.75">int</text>
<ellipse style="fill: none; fill-opacity:0; stroke-width: 0.2; stroke: #000000" cx="600" cy="478.75" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="604" y="484.75">user_id</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="744.9" y="484.75">int</text>
<ellipse style="fill: none; fill-opacity:0; stroke-width: 0.2; stroke: #000000" cx="600" cy="494.75" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="604" y="500.75">created_at</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="744.9" y="500.75">timestamp</text>
</g>
<g>
<rect style="fill: #ffffff" x="35" y="452.25" width="118.4" height="18"/>
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="35" y="452.25" width="118.4" height="18"/>
<text font-size="11.2" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="94.2" y="466.25">user_follows</text>
<rect style="fill: #ffffff" x="35" y="470.25" width="118.4" height="36"/>
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="35" y="470.25" width="118.4" height="36"/>
<polygon style="fill: #000000" points="38,480.25 40,483.25 42,480.25 40,477.25 "/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="44" y="486.25">user_id</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="123.3" y="486.25">int</text>
<line style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x1="44" y1="487.85" x2="146.4" y2="487.85"/>
<ellipse style="fill: #000000" cx="40" cy="496.25" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="44" y="502.25">following</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="123.3" y="502.25">int</text>
</g>
<g>
<rect style="fill: #ffffff" x="18" y="259.25" width="133.8" height="18"/>
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="18" y="259.25" width="133.8" height="18"/>
<text font-size="11.2" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="84.9" y="273.25">user_uploads_old</text>
<rect style="fill: #ffffff" x="18" y="277.25" width="133.8" height="36"/>
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="18" y="277.25" width="133.8" height="36"/>
<ellipse style="fill: #000000" cx="23" cy="287.25" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="27" y="293.25">username</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="114" y="293.25">text</text>
<ellipse style="fill: #000000" cx="23" cy="303.25" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="27" y="309.25">torrent_id</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="114" y="309.25">int</text>
</g>
<g>
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="151.8,303.25 338,303.25 338,91 472,91 "/>
<text font-size="9.59961" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="155.8" y="299.25">n</text>
<text font-size="9.59961" style="fill: #000000;text-anchor:end;font-family:monospace;font-style:normal;font-weight:normal" x="468" y="87">1</text>
</g>
<g>
<rect style="fill: #ffffff" x="734.7" y="63.5" width="203.1" height="18"/>
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="734.7" y="63.5" width="203.1" height="18"/>
<text font-size="11.2" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="836.25" y="77.5">sukebei_torrents</text>
<rect style="fill: #ffffff" x="734.7" y="81.5" width="203.1" height="292"/>
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="734.7" y="81.5" width="203.1" height="292"/>
<polygon style="fill: #000000" points="737.7,91.5 739.7,94.5 741.7,91.5 739.7,88.5 "/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="743.7" y="97.5">torrent_id</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="846.1" y="97.5">int</text>
<line style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x1="743.7" y1="99.1" x2="869.2" y2="99.1"/>
<ellipse style="fill: none; fill-opacity:0; stroke-width: 0.2; stroke: #000000" cx="739.7" cy="107.5" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="743.7" y="113.5">torrent_name</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="846.1" y="113.5">text</text>
<ellipse style="fill: #000000" cx="739.7" cy="123.5" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="743.7" y="129.5">torrent_hash</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="846.1" y="129.5">text</text>
<ellipse style="fill: #000000" cx="739.7" cy="139.5" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="743.7" y="145.5">category</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="846.1" y="145.5">int</text>
<ellipse style="fill: #000000" cx="739.7" cy="155.5" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="743.7" y="161.5">sub_category</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="846.1" y="161.5">int</text>
<ellipse style="fill: #000000" cx="739.7" cy="171.5" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="743.7" y="177.5">status</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="846.1" y="177.5">int</text>
<ellipse style="fill: none; fill-opacity:0; stroke-width: 0.2; stroke: #000000" cx="739.7" cy="187.5" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="743.7" y="193.5">date</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="846.1" y="193.5">timestamp</text>
<ellipse style="fill: #000000" cx="739.7" cy="203.5" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="743.7" y="209.5">uploader</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="846.1" y="209.5">int</text>
<ellipse style="fill: none; fill-opacity:0; stroke-width: 0.2; stroke: #000000" cx="739.7" cy="219.5" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="743.7" y="225.5">downloads</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="846.1" y="225.5">int</text>
<ellipse style="fill: #000000" cx="739.7" cy="235.5" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="743.7" y="241.5">stardom</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="846.1" y="241.5">int</text>
<ellipse style="fill: none; fill-opacity:0; stroke-width: 0.2; stroke: #000000" cx="739.7" cy="251.5" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="743.7" y="257.5">filesize</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="846.1" y="257.5">bigint</text>
<ellipse style="fill: #000000" cx="739.7" cy="267.5" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="743.7" y="273.5">description</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="846.1" y="273.5">text</text>
<ellipse style="fill: none; fill-opacity:0; stroke-width: 0.2; stroke: #000000" cx="739.7" cy="283.5" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="743.7" y="289.5">website_link</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="846.1" y="289.5">text</text>
<ellipse style="fill: none; fill-opacity:0; stroke-width: 0.2; stroke: #000000" cx="739.7" cy="299.5" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="743.7" y="305.5">deleted_at</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="846.1" y="305.5">timestamp</text>
<ellipse style="fill: none; fill-opacity:0; stroke-width: 0.2; stroke: #000000" cx="739.7" cy="315.5" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="743.7" y="321.5">seeders</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="846.1" y="321.5">int</text>
<ellipse style="fill: none; fill-opacity:0; stroke-width: 0.2; stroke: #000000" cx="739.7" cy="331.5" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="743.7" y="337.5">leechers</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="846.1" y="337.5">int</text>
<ellipse style="fill: none; fill-opacity:0; stroke-width: 0.2; stroke: #000000" cx="739.7" cy="347.5" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="743.7" y="353.5">completed</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="846.1" y="353.5">int</text>
<ellipse style="fill: none; fill-opacity:0; stroke-width: 0.2; stroke: #000000" cx="739.7" cy="363.5" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="743.7" y="369.5">last_scrape</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="846.1" y="369.5">timestamptz</text>
</g>
<g>
<rect style="fill: #ffffff" x="843.7" y="395.75" width="149.2" height="18"/>
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="843.7" y="395.75" width="149.2" height="18"/>
<text font-size="11.2" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="918.3" y="409.75">sukebei_files</text>
<rect style="fill: #ffffff" x="843.7" y="413.75" width="149.2" height="68"/>
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="843.7" y="413.75" width="149.2" height="68"/>
<polygon style="fill: #000000" points="846.7,423.75 848.7,426.75 850.7,423.75 848.7,420.75 "/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="852.7" y="429.75">file_id</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="939.7" y="429.75">int</text>
<line style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x1="852.7" y1="431.35" x2="962.8" y2="431.35"/>
<ellipse style="fill: none; fill-opacity:0; stroke-width: 0.2; stroke: #000000" cx="848.7" cy="439.75" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="852.7" y="445.75">torrent_id</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="939.7" y="445.75">int</text>
<ellipse style="fill: none; fill-opacity:0; stroke-width: 0.2; stroke: #000000" cx="848.7" cy="455.75" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="852.7" y="461.75">path</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="939.7" y="461.75">text</text>
<ellipse style="fill: none; fill-opacity:0; stroke-width: 0.2; stroke: #000000" cx="848.7" cy="471.75" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="852.7" y="477.75">filesize</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="939.7" y="477.75">bigint</text>
</g>
<g>
<rect style="fill: #ffffff" x="857.7" y="516" width="226.2" height="18"/>
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="857.7" y="516" width="226.2" height="18"/>
<text font-size="11.2" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="970.8" y="530">sukebei_torrent_reports</text>
<rect style="fill: #ffffff" x="857.7" y="534" width="226.2" height="84"/>
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="857.7" y="534" width="226.2" height="84"/>
<polygon style="fill: #000000" points="860.7,544 862.7,547 864.7,544 862.7,541 "/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="866.7" y="550">torrent_report_id</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="1007.6" y="550">int</text>
<line style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x1="866.7" y1="551.6" x2="1030.7" y2="551.6"/>
<ellipse style="fill: none; fill-opacity:0; stroke-width: 0.2; stroke: #000000" cx="862.7" cy="560" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="866.7" y="566">type</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="1007.6" y="566">text</text>
<ellipse style="fill: none; fill-opacity:0; stroke-width: 0.2; stroke: #000000" cx="862.7" cy="576" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="866.7" y="582">torrent_id</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="1007.6" y="582">int</text>
<ellipse style="fill: none; fill-opacity:0; stroke-width: 0.2; stroke: #000000" cx="862.7" cy="592" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="866.7" y="598">user_id</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="1007.6" y="598">int</text>
<ellipse style="fill: none; fill-opacity:0; stroke-width: 0.2; stroke: #000000" cx="862.7" cy="608" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="866.7" y="614">created_at</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="1007.6" y="614">timestamp</text>
</g>
<g>
<rect style="fill: #ffffff" x="16.006" y="352.25" width="172.3" height="18"/>
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="16.006" y="352.25" width="172.3" height="18"/>
<text font-size="11.2" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="102.156" y="366.25">comments_old</text>
<rect style="fill: #ffffff" x="16.006" y="370.25" width="172.3" height="68"/>
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="16.006" y="370.25" width="172.3" height="68"/>
<ellipse style="fill: #000000" cx="21.006" cy="380.25" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="25.006" y="386.25">torrent_id</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="112.006" y="386.25">int</text>
<ellipse style="fill: #000000" cx="21.006" cy="396.25" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="25.006" y="402.25">username</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="112.006" y="402.25">text</text>
<ellipse style="fill: #000000" cx="21.006" cy="412.25" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="25.006" y="418.25">content</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="112.006" y="418.25">text</text>
<ellipse style="fill: #000000" cx="21.006" cy="428.25" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="25.006" y="434.25">date</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="112.006" y="434.25">timestamp</text>
</g>
<g>
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="188.306,380.25 338.006,380.25 338.006,91 472,91 "/>
<text font-size="9.59961" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="192.306" y="376.25">n</text>
<text font-size="9.59961" style="fill: #000000;text-anchor:end;font-family:monospace;font-style:normal;font-weight:normal" x="468" y="87">1</text>
</g>
<g>
<rect style="fill: #ffffff" x="1015.71" y="405.75" width="172.55" height="18"/>
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="1015.71" y="405.75" width="172.55" height="18"/>
<text font-size="11.2" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="1101.98" y="419.75">sukebei_user_uploads_old</text>
<rect style="fill: #ffffff" x="1015.71" y="423.75" width="172.55" height="36"/>
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="1015.71" y="423.75" width="172.55" height="36"/>
<ellipse style="fill: #000000" cx="1020.71" cy="433.75" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="1024.71" y="439.75">username</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="1111.71" y="439.75">text</text>
<ellipse style="fill: #000000" cx="1020.71" cy="449.75" rx="2" ry="2"/>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="1024.71" y="455.75">torrent_id</text>
<text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="1111.71" y="455.75">int</text>
</g>
</svg>

Avant

Largeur:  |  Hauteur:  |  Taille: 51 KiB

Voir le fichier

@ -9,7 +9,6 @@ import (
"os"
"time"
"github.com/NyaaPantsu/nyaa/cache"
"github.com/NyaaPantsu/nyaa/config"
"github.com/NyaaPantsu/nyaa/db"
"github.com/NyaaPantsu/nyaa/network"
@ -122,7 +121,6 @@ func main() {
processFlags := conf.BindFlags()
defaults := flag.Bool("print-defaults", false, "print the default configuration file on stdout")
mode := flag.String("mode", "webapp", "which mode to run daemon in, either webapp, scraper or metainfo_fetcher")
flag.Float64Var(&conf.Cache.Size, "c", config.Conf.Cache.Size, "size of the search cache in MB")
flag.Parse()
if *defaults {
@ -150,10 +148,6 @@ func main() {
if err != nil {
log.Fatal(err.Error())
}
err = cache.Configure(&conf.Cache)
if err != nil {
log.Fatal(err.Error())
}
err = search.Configure(&conf.Search)
if err != nil {
log.Fatal(err.Error())

Voir le fichier

@ -17,7 +17,6 @@ import (
"strconv"
"strings"
"github.com/NyaaPantsu/nyaa/cache"
"github.com/NyaaPantsu/nyaa/config"
"github.com/NyaaPantsu/nyaa/model"
"github.com/NyaaPantsu/nyaa/service"
@ -348,7 +347,6 @@ func (r *TorrentRequest) ExtractInfo(req *http.Request) error {
return err
}
cache.Impl.ClearAll()
defer req.Body.Close()
err = r.ExtractCategory(req)

Voir le fichier

@ -8,7 +8,6 @@ import (
"unicode"
"unicode/utf8"
"github.com/NyaaPantsu/nyaa/cache"
"github.com/NyaaPantsu/nyaa/common"
"github.com/NyaaPantsu/nyaa/config"
"github.com/NyaaPantsu/nyaa/db"
@ -258,17 +257,14 @@ func searchByQueryPostgres(r *http.Request, pagenum int, countAll bool, withUser
log.Infof("SQL query is :: %s\n", parameters.Conditions)
tor, count, err = cache.Impl.Get(search, func() (tor []model.Torrent, count int, err error) {
if deleted {
tor, count, err = torrentService.GetDeletedTorrents(&parameters, orderBy, int(search.Max), int(search.Max)*(search.Page-1))
} else if countAll && !withUser {
tor, count, err = torrentService.GetTorrentsOrderBy(&parameters, orderBy, int(search.Max), int(search.Max)*(search.Page-1))
} else if withUser {
tor, count, err = torrentService.GetTorrentsWithUserOrderBy(&parameters, orderBy, int(search.Max), int(search.Max)*(search.Page-1))
} else {
tor, err = torrentService.GetTorrentsOrderByNoCount(&parameters, orderBy, int(search.Max), int(search.Max)*(search.Page-1))
}
return
})
if deleted {
tor, count, err = torrentService.GetDeletedTorrents(&parameters, orderBy, int(search.Max), int(search.Max)*(search.Page-1))
} else if countAll && !withUser {
tor, count, err = torrentService.GetTorrentsOrderBy(&parameters, orderBy, int(search.Max), int(search.Max)*(search.Page-1))
} else if withUser {
tor, count, err = torrentService.GetTorrentsWithUserOrderBy(&parameters, orderBy, int(search.Max), int(search.Max)*(search.Page-1))
} else {
tor, err = torrentService.GetTorrentsOrderByNoCount(&parameters, orderBy, int(search.Max), int(search.Max)*(search.Page-1))
}
return
}