package storage import ( "database/sql" "errors" "fmt" "log" "git.sp4ke.com/sp4ke/bit4sat/db" "github.com/jmoiron/sqlx" "github.com/mattn/go-sqlite3" "github.com/mediocregopher/radix/v3" ) var DB = db.DB const ( DBUploadSchema = ` CREATE TABLE IF NOT EXISTS upload ( id serial PRIMARY KEY, upload_id varchar(9) NOT NULL, sha256 varchar(64) NOT NULL, file_name varchar(255) NOT NULL, file_type varchar(255) DEFAULT '', file_size integer NOT NULL, file_ext varchar(255) DEFAULT '', status integer DEFAULT 0 REFERENCES upload_status(type), UNIQUE (upload_id, sha256) ); ` DBUploadView = ` CREATE OR REPLACE VIEW upload_with_status AS SELECT upload.upload_id, upload.sha256, upload_status.status, upload.file_name, upload.file_type, upload.file_size, upload.file_ext FROM upload JOIN upload_status ON upload.status = upload_status.type ` DBUploadStatusSchema = `CREATE TABLE IF NOT EXISTS upload_status ( type INT NOT NULL PRIMARY KEY, status CHAR(255) NOT NULL UNIQUE ) ` QNewUpload = `INSERT INTO upload (upload_id, sha256, file_name, file_type, file_size, file_ext, status) VALUES (:upload_id, :sha256, :file_name, :file_type, :file_size, :file_ext, :status)` QSetStatus = `UPDATE upload SET status = :status WHERE upload_id = :upload_id ` QGetByHashID = `SELECT upload_id, sha256, file_name, file_type, file_size, file_ext, status FROM upload WHERE sha256 = $1 AND upload_id = $2` ) const ( UpNew = iota UpStored UpWaitingPayment UpReady ) var UploadStatus = map[int]string{ UpNew: "new upload", UpStored: "stored", UpWaitingPayment: "waiting payment", UpReady: "ready", } var ( ErrDoesNotExist = errors.New("does not exist") ErrAlreadyExists = errors.New("already exists") ) type Upload struct { ID string `db:"upload_id"` SHA256 string `db:"sha256"` FileName string `db:"file_name"` FileType string `db:"file_type"` FileSize int64 `db:"file_size"` FileExt string `db:"file_ext"` Status int `db:"status"` } func CacheSetUploadStatus(id string, status int) error { key := fmt.Sprintf("upload_status_%s", id) return DB.Redis.Do(radix.FlatCmd(nil, "SET", key, status)) } // Returns true if id exists in DB func IdExists(id string) (exists bool, err error) { key := fmt.Sprintf("upload_status_%s", id) err = DB.Redis.Do(radix.Cmd(&exists, "EXISTS", key)) return } // Get a file by upload id and hash func GetByHashID(sha256 string, id string) (*Upload, error) { var up Upload err := DB.Sql.Get(&up, QGetByHashID, sha256, id) if err == sql.ErrNoRows { return nil, ErrDoesNotExist } if err != nil { return nil, err } return &up, nil } func (u *Upload) TxSetState(tx *sqlx.Tx, status int) error { _, err := tx.NamedExec(QSetStatus, u) if err != nil { return err } return nil } func (u *Upload) TxWrite(tx *sqlx.Tx) error { _, err := tx.NamedExec(QNewUpload, u) sqlErr, isSqlErr := err.(sqlite3.Error) if isSqlErr && sqlErr.Code == sqlite3.ErrConstraint { return ErrAlreadyExists } if err != nil { return err } return nil } func (u *Upload) Write() error { _, err := DB.Sql.NamedExec(QNewUpload, u) sqlErr, isSqlErr := err.(sqlite3.Error) if isSqlErr && sqlErr.Code == sqlite3.ErrConstraint { return ErrAlreadyExists } if err != nil { return err } return nil } func init() { _, err := DB.Sql.Exec(DBUploadStatusSchema) if err != nil { log.Fatal(err) } _, err = DB.Sql.Exec(DBUploadSchema) if err != nil { log.Fatal(err) } _, err = DB.Sql.Exec(DBUploadView) if err != nil { log.Fatal(err) } // Populate status types query := `INSERT INTO upload_status (type, status) VALUES($1,$2) ON CONFLICT (type) DO UPDATE SET status = $3` for k, v := range UploadStatus { _, err := DB.Sql.Exec(query, k, v, v) if err != nil { sqlErr, ok := err.(sqlite3.Error) if ok && sqlErr.ExtendedCode == sqlite3.ErrConstraintUnique { log.Panic(err) } if !ok { log.Panic(err) } } } }