HEAVY: migrate accounts and logs to service/repo architecture
This commit is contained in:
parent
5c255fb34b
commit
49e14b5bc5
37 changed files with 1019 additions and 687 deletions
|
|
@ -1,135 +0,0 @@
|
|||
package controller
|
||||
|
||||
import (
|
||||
"arimelody-web/model"
|
||||
"strings"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
func GetAllAccounts(db *sqlx.DB) ([]model.Account, error) {
|
||||
var accounts = []model.Account{}
|
||||
|
||||
err := db.Select(&accounts, "SELECT * FROM account ORDER BY created_at ASC")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return accounts, nil
|
||||
}
|
||||
|
||||
func GetAccountByID(db *sqlx.DB, id string) (*model.Account, error) {
|
||||
var account = model.Account{}
|
||||
|
||||
err := db.Get(&account, "SELECT * FROM account WHERE id=$1", id)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "no rows") {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &account, nil
|
||||
}
|
||||
|
||||
func GetAccountByUsername(db *sqlx.DB, username string) (*model.Account, error) {
|
||||
var account = model.Account{}
|
||||
|
||||
err := db.Get(&account, "SELECT * FROM account WHERE username=$1", username)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "no rows") {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &account, nil
|
||||
}
|
||||
|
||||
func GetAccountByEmail(db *sqlx.DB, email string) (*model.Account, error) {
|
||||
var account = model.Account{}
|
||||
|
||||
err := db.Get(&account, "SELECT * FROM account WHERE email=$1", email)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "no rows") {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &account, nil
|
||||
}
|
||||
|
||||
func GetAccountBySession(db *sqlx.DB, sessionToken string) (*model.Account, error) {
|
||||
if sessionToken == "" { return nil, nil }
|
||||
|
||||
account := model.Account{}
|
||||
|
||||
err := db.Get(&account, "SELECT account.* FROM account JOIN token ON id=account WHERE token=$1", sessionToken)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "no rows") {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &account, nil
|
||||
}
|
||||
|
||||
func CreateAccount(db *sqlx.DB, account *model.Account) error {
|
||||
err := db.Get(
|
||||
&account.ID,
|
||||
"INSERT INTO account (username, password, email, avatar_url) " +
|
||||
"VALUES ($1, $2, $3, $4) " +
|
||||
"RETURNING id",
|
||||
account.Username,
|
||||
account.Password,
|
||||
account.Email,
|
||||
account.AvatarURL,
|
||||
)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func UpdateAccount(db *sqlx.DB, account *model.Account) error {
|
||||
_, err := db.Exec(
|
||||
"UPDATE account " +
|
||||
"SET username=$2,password=$3,email=$4,avatar_url=$5 " +
|
||||
"WHERE id=$1",
|
||||
account.ID,
|
||||
account.Username,
|
||||
account.Password,
|
||||
account.Email,
|
||||
account.AvatarURL,
|
||||
)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func DeleteAccount(db *sqlx.DB, accountID string) error {
|
||||
_, err := db.Exec("DELETE FROM account WHERE id=$1", accountID)
|
||||
return err
|
||||
}
|
||||
|
||||
func IncrementAccountFails(db *sqlx.DB, accountID string) (bool, error) {
|
||||
failAttempts := 0
|
||||
err := db.Get(&failAttempts, "UPDATE account SET fail_attempts = fail_attempts + 1 WHERE id=$1 RETURNING fail_attempts", accountID)
|
||||
if err != nil { return false, err }
|
||||
locked := false
|
||||
if failAttempts >= model.MAX_LOGIN_FAIL_ATTEMPTS {
|
||||
err = LockAccount(db, accountID)
|
||||
if err != nil { return false, err }
|
||||
locked = true
|
||||
}
|
||||
return locked, err
|
||||
}
|
||||
|
||||
func LockAccount(db *sqlx.DB, accountID string) error {
|
||||
_, err := db.Exec("UPDATE account SET locked = true WHERE id=$1", accountID)
|
||||
return err
|
||||
}
|
||||
|
||||
func UnlockAccount(db *sqlx.DB, accountID string) error {
|
||||
_, err := db.Exec("UPDATE account SET locked = false, fail_attempts = 0 WHERE id=$1", accountID)
|
||||
return err
|
||||
}
|
||||
|
|
@ -1,28 +1,28 @@
|
|||
package controller
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"arimelody-web/model"
|
||||
"arimelody-web/model/app"
|
||||
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
)
|
||||
|
||||
func GetConfig() model.Config {
|
||||
func GetConfig() app.Config {
|
||||
configFile := os.Getenv("ARIMELODY_CONFIG")
|
||||
if configFile == "" {
|
||||
configFile = "config.toml"
|
||||
}
|
||||
|
||||
config := model.Config{
|
||||
config := app.Config{
|
||||
BaseUrl: "https://arimelody.space",
|
||||
Host: "0.0.0.0",
|
||||
Port: 8080,
|
||||
TrustedProxies: []string{ "127.0.0.1" },
|
||||
DB: model.DBConfig{
|
||||
DB: app.DBConfig{
|
||||
Host: "127.0.0.1",
|
||||
Port: 5432,
|
||||
User: "arimelody",
|
||||
|
|
@ -53,7 +53,7 @@ func GetConfig() model.Config {
|
|||
return config
|
||||
}
|
||||
|
||||
func handleConfigOverrides(config *model.Config) error {
|
||||
func handleConfigOverrides(config *app.Config) error {
|
||||
var err error
|
||||
|
||||
if env, has := os.LookupEnv("ARIMELODY_BASE_URL"); has { config.BaseUrl = env }
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
package controller
|
||||
|
||||
import (
|
||||
"arimelody-web/model"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strings"
|
||||
"arimelody-web/model/app"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Returns the request's original IP address, resolving the `x-forwarded-for`
|
||||
// header if the request originates from a trusted proxy.
|
||||
func ResolveIP(app *model.AppState, r *http.Request) string {
|
||||
func ResolveIP(app *app.AppState, r *http.Request) string {
|
||||
addr := strings.Split(r.RemoteAddr, ":")[0]
|
||||
if slices.Contains(app.Config.TrustedProxies, addr) {
|
||||
forwardedFor := r.Header.Get("x-forwarded-for")
|
||||
|
|
|
|||
|
|
@ -1,102 +0,0 @@
|
|||
package controller
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
const DB_VERSION int = 4
|
||||
|
||||
func CheckDBVersionAndMigrate(db *sqlx.DB) {
|
||||
db.MustExec("CREATE SCHEMA IF NOT EXISTS arimelody")
|
||||
db.MustExec("SET search_path TO arimelody, public")
|
||||
db.MustExec(
|
||||
"CREATE TABLE IF NOT EXISTS arimelody.schema_version (" +
|
||||
"version INTEGER PRIMARY KEY, " +
|
||||
"applied_at TIMESTAMP DEFAULT current_timestamp)",
|
||||
)
|
||||
|
||||
oldDBVersion := 0
|
||||
schemaVersionCount := 0
|
||||
err := db.Get(&schemaVersionCount, "SELECT COUNT(*) FROM schema_version")
|
||||
if err != nil { panic(err) }
|
||||
if schemaVersionCount > 0 {
|
||||
err := db.Get(&oldDBVersion, "SELECT MAX(version) FROM schema_version")
|
||||
if err != nil { panic(err) }
|
||||
}
|
||||
|
||||
for oldDBVersion < DB_VERSION {
|
||||
switch oldDBVersion {
|
||||
case 0:
|
||||
// default case; assume no database exists
|
||||
ApplyMigration(db, "000-init")
|
||||
oldDBVersion = DB_VERSION
|
||||
|
||||
case 1:
|
||||
// the irony is i actually have to awkwardly shove schema_version
|
||||
// into the old database in order for this to work LOL
|
||||
ApplyMigration(db, "001-pre-versioning")
|
||||
oldDBVersion = 2
|
||||
|
||||
case 2:
|
||||
ApplyMigration(db, "002-audit-logs")
|
||||
oldDBVersion = 3
|
||||
|
||||
case 3:
|
||||
ApplyMigration(db, "003-fail-lock")
|
||||
oldDBVersion = 4
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("Database schema up to date.\n")
|
||||
}
|
||||
|
||||
//go:embed "schema-migration"
|
||||
var schemaFS embed.FS
|
||||
|
||||
func ApplyMigration(db *sqlx.DB, scriptFile string) {
|
||||
fmt.Printf("Applying schema migration %s...\n", scriptFile)
|
||||
|
||||
bytes, err := schemaFS.ReadFile("schema-migration/" + scriptFile + ".sql")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "FATAL: Failed to open schema file \"%s\": %v\n", scriptFile, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
script := string(bytes)
|
||||
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "FATAL: Failed to begin migration: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
_, err = tx.Exec(script)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
fmt.Fprintf(os.Stderr, "FATAL: Failed to apply migration: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
_, err = tx.Exec(
|
||||
"INSERT INTO schema_version (version, applied_at) " +
|
||||
"VALUES ($1, $2)",
|
||||
DB_VERSION,
|
||||
time.Now(),
|
||||
)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
fmt.Fprintf(os.Stderr, "FATAL: Failed to update schema version: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "FATAL: Failed to commit transaction: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,143 +0,0 @@
|
|||
--
|
||||
-- Tables
|
||||
--
|
||||
|
||||
-- Audit logs
|
||||
CREATE TABLE arimelody.auditlog (
|
||||
id UUID DEFAULT gen_random_uuid(),
|
||||
level int NOT NULL DEFAULT 0,
|
||||
type TEXT NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT current_timestamp
|
||||
);
|
||||
|
||||
-- Accounts
|
||||
CREATE TABLE arimelody.account (
|
||||
id UUID DEFAULT gen_random_uuid(),
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
password TEXT NOT NULL,
|
||||
email TEXT,
|
||||
avatar_url TEXT,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT current_timestamp,
|
||||
fail_attempts INT NOT NULL DEFAULT 0,
|
||||
locked BOOLEAN DEFAULT false
|
||||
);
|
||||
ALTER TABLE arimelody.account ADD CONSTRAINT account_pk PRIMARY KEY (id);
|
||||
|
||||
-- Privilege
|
||||
CREATE TABLE arimelody.privilege (
|
||||
account UUID NOT NULL,
|
||||
privilege TEXT NOT NULL
|
||||
);
|
||||
ALTER TABLE arimelody.privilege ADD CONSTRAINT privilege_pk PRIMARY KEY (account, privilege);
|
||||
|
||||
-- Invites
|
||||
CREATE TABLE arimelody.invite (
|
||||
code text NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT current_timestamp,
|
||||
expires_at TIMESTAMP NOT NULL
|
||||
);
|
||||
ALTER TABLE arimelody.invite ADD CONSTRAINT invite_pk PRIMARY KEY (code);
|
||||
|
||||
-- Sessions
|
||||
CREATE TABLE arimelody.session (
|
||||
token TEXT,
|
||||
user_agent TEXT NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT current_timestamp,
|
||||
expires_at TIMESTAMP DEFAULT NULL,
|
||||
account UUID,
|
||||
attempt_account UUID,
|
||||
message TEXT,
|
||||
error TEXT
|
||||
);
|
||||
ALTER TABLE arimelody.session ADD CONSTRAINT session_pk PRIMARY KEY (token);
|
||||
|
||||
-- TOTP methods
|
||||
CREATE TABLE arimelody.totp (
|
||||
name TEXT NOT NULL,
|
||||
account UUID NOT NULL,
|
||||
secret TEXT,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT current_timestamp,
|
||||
confirmed BOOLEAN DEFAULT false
|
||||
);
|
||||
ALTER TABLE arimelody.totp ADD CONSTRAINT totp_pk PRIMARY KEY (account, name);
|
||||
|
||||
|
||||
|
||||
-- Artists (should be applicable to all art)
|
||||
CREATE TABLE arimelody.artist (
|
||||
id character varying(64),
|
||||
name text NOT NULL,
|
||||
website text,
|
||||
avatar text
|
||||
);
|
||||
ALTER TABLE arimelody.artist ADD CONSTRAINT artist_pk PRIMARY KEY (id);
|
||||
|
||||
-- Music releases
|
||||
CREATE TABLE arimelody.musicrelease (
|
||||
id character varying(64) NOT NULL,
|
||||
visible bool DEFAULT false,
|
||||
title text NOT NULL,
|
||||
description text,
|
||||
type text,
|
||||
release_date TIMESTAMP NOT NULL,
|
||||
artwork text,
|
||||
buyname text,
|
||||
buylink text,
|
||||
copyright text,
|
||||
copyrightURL text,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT current_timestamp
|
||||
);
|
||||
ALTER TABLE arimelody.musicrelease ADD CONSTRAINT musicrelease_pk PRIMARY KEY (id);
|
||||
|
||||
-- Music links (external platform links under a release)
|
||||
CREATE TABLE arimelody.musiclink (
|
||||
release character varying(64) NOT NULL,
|
||||
name text NOT NULL,
|
||||
url text NOT NULL
|
||||
);
|
||||
ALTER TABLE arimelody.musiclink ADD CONSTRAINT musiclink_pk PRIMARY KEY (release, name);
|
||||
|
||||
-- Music credits (artist credits under a release)
|
||||
CREATE TABLE arimelody.musiccredit (
|
||||
release character varying(64) NOT NULL,
|
||||
artist character varying(64) NOT NULL,
|
||||
role text NOT NULL,
|
||||
is_primary boolean DEFAULT false
|
||||
);
|
||||
ALTER TABLE arimelody.musiccredit ADD CONSTRAINT musiccredit_pk PRIMARY KEY (release, artist);
|
||||
|
||||
-- Music tracks (tracks under a release)
|
||||
CREATE TABLE arimelody.musictrack (
|
||||
id uuid DEFAULT gen_random_uuid(),
|
||||
title text NOT NULL,
|
||||
description text,
|
||||
lyrics text,
|
||||
preview_url text
|
||||
);
|
||||
ALTER TABLE arimelody.musictrack ADD CONSTRAINT musictrack_pk PRIMARY KEY (id);
|
||||
|
||||
-- Music release/track pairs
|
||||
CREATE TABLE arimelody.musicreleasetrack (
|
||||
release character varying(64) NOT NULL,
|
||||
track uuid NOT NULL,
|
||||
number integer NOT NULL
|
||||
);
|
||||
ALTER TABLE arimelody.musicreleasetrack ADD CONSTRAINT musicreleasetrack_pk PRIMARY KEY (release, track);
|
||||
|
||||
|
||||
|
||||
--
|
||||
-- Foreign keys
|
||||
--
|
||||
|
||||
ALTER TABLE arimelody.privilege ADD CONSTRAINT privilege_account_fk FOREIGN KEY (account) REFERENCES account(id) ON DELETE CASCADE;
|
||||
ALTER TABLE arimelody.session ADD CONSTRAINT session_account_fk FOREIGN KEY (account) REFERENCES account(id) ON DELETE CASCADE;
|
||||
ALTER TABLE arimelody.session ADD CONSTRAINT session_attempt_account_fk FOREIGN KEY (account) REFERENCES account(id) ON DELETE CASCADE;
|
||||
ALTER TABLE arimelody.totp ADD CONSTRAINT totp_account_fk FOREIGN KEY (account) REFERENCES account(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE arimelody.musiccredit ADD CONSTRAINT musiccredit_artist_fk FOREIGN KEY (artist) REFERENCES artist(id) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE arimelody.musiccredit ADD CONSTRAINT musiccredit_release_fk FOREIGN KEY (release) REFERENCES musicrelease(id) ON DELETE CASCADE;
|
||||
ALTER TABLE arimelody.musiclink ADD CONSTRAINT musiclink_release_fk FOREIGN KEY (release) REFERENCES musicrelease(id) ON UPDATE CASCADE ON DELETE CASCADE;
|
||||
ALTER TABLE arimelody.musicreleasetrack ADD CONSTRAINT music_pair_trackref_fk FOREIGN KEY (release) REFERENCES musicrelease(id) ON DELETE CASCADE;
|
||||
ALTER TABLE arimelody.musicreleasetrack ADD CONSTRAINT music_pair_releaseref_fk FOREIGN KEY (track) REFERENCES musictrack(id) ON DELETE CASCADE;
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
--
|
||||
-- New items
|
||||
--
|
||||
|
||||
-- Accounts
|
||||
CREATE TABLE arimelody.account (
|
||||
id UUID DEFAULT gen_random_uuid(),
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
password TEXT NOT NULL,
|
||||
email TEXT,
|
||||
avatar_url TEXT,
|
||||
created_at TIMESTAMP DEFAULT current_timestamp
|
||||
);
|
||||
ALTER TABLE arimelody.account ADD CONSTRAINT account_pk PRIMARY KEY (id);
|
||||
|
||||
-- Privilege
|
||||
CREATE TABLE arimelody.privilege (
|
||||
account UUID NOT NULL,
|
||||
privilege TEXT NOT NULL
|
||||
);
|
||||
ALTER TABLE arimelody.privilege ADD CONSTRAINT privilege_pk PRIMARY KEY (account, privilege);
|
||||
|
||||
-- Invites
|
||||
CREATE TABLE arimelody.invite (
|
||||
code text NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT current_timestamp,
|
||||
expires_at TIMESTAMP NOT NULL
|
||||
);
|
||||
ALTER TABLE arimelody.invite ADD CONSTRAINT invite_pk PRIMARY KEY (code);
|
||||
|
||||
-- Sessions
|
||||
CREATE TABLE arimelody.session (
|
||||
token TEXT,
|
||||
user_agent TEXT NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT current_timestamp,
|
||||
expires_at TIMESTAMP DEFAULT NULL,
|
||||
account UUID,
|
||||
attempt_account UUID,
|
||||
message TEXT,
|
||||
error TEXT
|
||||
);
|
||||
ALTER TABLE arimelody.session ADD CONSTRAINT session_pk PRIMARY KEY (token);
|
||||
|
||||
-- TOTP methods
|
||||
CREATE TABLE arimelody.totp (
|
||||
name TEXT NOT NULL,
|
||||
account UUID NOT NULL,
|
||||
secret TEXT,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT current_timestamp,
|
||||
confirmed BOOLEAN DEFAULT false
|
||||
);
|
||||
ALTER TABLE arimelody.totp ADD CONSTRAINT totp_pk PRIMARY KEY (account, name);
|
||||
|
||||
-- Foreign keys
|
||||
ALTER TABLE arimelody.privilege ADD CONSTRAINT privilege_account_fk FOREIGN KEY (account) REFERENCES account(id) ON DELETE CASCADE;
|
||||
ALTER TABLE arimelody.session ADD CONSTRAINT session_account_fk FOREIGN KEY (account) REFERENCES account(id) ON DELETE CASCADE;
|
||||
ALTER TABLE arimelody.session ADD CONSTRAINT session_attempt_account_fk FOREIGN KEY (account) REFERENCES account(id) ON DELETE CASCADE;
|
||||
ALTER TABLE arimelody.totp ADD CONSTRAINT totp_account_fk FOREIGN KEY (account) REFERENCES account(id) ON DELETE CASCADE;
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
-- Audit logs
|
||||
CREATE TABLE arimelody.auditlog (
|
||||
id UUID DEFAULT gen_random_uuid(),
|
||||
level int NOT NULL DEFAULT 0,
|
||||
type TEXT NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT current_timestamp
|
||||
);
|
||||
|
||||
-- Need moar timestamps
|
||||
ALTER TABLE arimelody.musicrelease ADD COLUMN created_at TIMESTAMP NOT NULL DEFAULT current_timestamp;
|
||||
ALTER TABLE arimelody.account ALTER COLUMN created_at SET NOT NULL;
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
-- it would be nice to prevent brute-forcing
|
||||
ALTER TABLE arimelody.account ADD COLUMN fail_attempts INT NOT NULL DEFAULT 0;
|
||||
ALTER TABLE arimelody.account ADD COLUMN locked BOOLEAN DEFAULT false;
|
||||
|
|
@ -1,21 +1,21 @@
|
|||
package controller
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"arimelody-web/log"
|
||||
"arimelody-web/model"
|
||||
"arimelody-web/model"
|
||||
"arimelody-web/model/app"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
const TOKEN_LEN = 64
|
||||
|
||||
func GetSessionFromRequest(app *model.AppState, r *http.Request) (*model.Session, error) {
|
||||
func GetSessionFromRequest(app *app.AppState, r *http.Request) (*model.Session, error) {
|
||||
sessionCookie, err := r.Cookie(model.COOKIE_TOKEN)
|
||||
if err != nil && err != http.ErrNoCookie {
|
||||
return nil, fmt.Errorf("Failed to retrieve session cookie: %v", err)
|
||||
|
|
@ -25,7 +25,7 @@ func GetSessionFromRequest(app *model.AppState, r *http.Request) (*model.Session
|
|||
|
||||
if sessionCookie != nil {
|
||||
// fetch existing session
|
||||
session, err = GetSession(app.DB, sessionCookie.Value)
|
||||
session, err = GetSession(app, sessionCookie.Value)
|
||||
|
||||
if err != nil && !strings.Contains(err.Error(), "no rows") {
|
||||
return nil, fmt.Errorf("Failed to retrieve session: %v", err)
|
||||
|
|
@ -35,13 +35,13 @@ func GetSessionFromRequest(app *model.AppState, r *http.Request) (*model.Session
|
|||
if session.UserAgent != r.UserAgent() {
|
||||
msg := "Session user agent mismatch. A cookie may have been hijacked!"
|
||||
if session.Account != nil {
|
||||
account, _ := GetAccountByID(app.DB, session.Account.ID)
|
||||
account, _ := app.AccountService.GetByID(session.Account.ID)
|
||||
msg += " (Account \"" + account.Username + "\")"
|
||||
}
|
||||
app.Log.Warn(log.TYPE_ACCOUNT, msg)
|
||||
app.Log.Warn(model.LOG_ACCOUNT, msg)
|
||||
err = DeleteSession(app.DB, session.Token)
|
||||
if err != nil {
|
||||
app.Log.Warn(log.TYPE_ACCOUNT, "Failed to delete affected session")
|
||||
app.Log.Warn(model.LOG_ACCOUNT, "Failed to delete affected session")
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
|
@ -137,7 +137,7 @@ func SetSessionError(db *sqlx.DB, session *model.Session, message string) error
|
|||
return err
|
||||
}
|
||||
|
||||
func GetSession(db *sqlx.DB, token string) (*model.Session, error) {
|
||||
func GetSession(app *app.AppState, token string) (*model.Session, error) {
|
||||
type dbSession struct {
|
||||
model.Session
|
||||
AttemptAccountID sql.NullString `db:"attempt_account"`
|
||||
|
|
@ -145,7 +145,7 @@ func GetSession(db *sqlx.DB, token string) (*model.Session, error) {
|
|||
}
|
||||
|
||||
session := dbSession{}
|
||||
err := db.Get(
|
||||
err := app.DB.Get(
|
||||
&session,
|
||||
"SELECT * FROM session WHERE token=$1",
|
||||
token,
|
||||
|
|
@ -155,14 +155,14 @@ func GetSession(db *sqlx.DB, token string) (*model.Session, error) {
|
|||
}
|
||||
|
||||
if session.AccountID.Valid {
|
||||
session.Account, err = GetAccountByID(db, session.AccountID.String)
|
||||
session.Account, err = app.AccountService.GetByID(session.AccountID.String)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if session.AttemptAccountID.Valid {
|
||||
session.AttemptAccount, err = GetAccountByID(db, session.AttemptAccountID.String)
|
||||
session.AttemptAccount, err = app.AccountService.GetByID(session.AttemptAccountID.String)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
package controller
|
||||
|
||||
import (
|
||||
"arimelody-web/model"
|
||||
"arimelody-web/model/app"
|
||||
"arimelody-web/model/twitch"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
|
@ -11,13 +12,13 @@ import (
|
|||
|
||||
const TWITCH_API_BASE = "https://api.twitch.tv/helix/"
|
||||
|
||||
func TwitchSetup(app *model.AppState) error {
|
||||
app.Twitch = &model.TwitchState{}
|
||||
func TwitchSetup(app *app.AppState) error {
|
||||
app.Twitch = &twitch.State{}
|
||||
err := RefreshTwitchToken(app)
|
||||
return err
|
||||
}
|
||||
|
||||
func RefreshTwitchToken(app *model.AppState) error {
|
||||
func RefreshTwitchToken(app *app.AppState) error {
|
||||
if app.Twitch != nil && app.Twitch.Token != nil && time.Now().UTC().After(app.Twitch.Token.ExpiresAt) {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -45,7 +46,7 @@ func RefreshTwitchToken(app *model.AppState) error {
|
|||
return err
|
||||
}
|
||||
|
||||
app.Twitch.Token = &model.TwitchOAuthToken{
|
||||
app.Twitch.Token = &twitch.OAuthToken{
|
||||
AccessToken: oauthResponse.AccessToken,
|
||||
ExpiresAt: time.Now().UTC().Add(time.Second * time.Duration(oauthResponse.ExpiresIn)).UTC(),
|
||||
TokenType: oauthResponse.TokenType,
|
||||
|
|
@ -54,10 +55,10 @@ func RefreshTwitchToken(app *model.AppState) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
var lastStreamState *model.TwitchStreamInfo
|
||||
var lastStreamState *twitch.StreamInfo
|
||||
var lastStreamStateAt time.Time
|
||||
|
||||
func GetTwitchStatus(app *model.AppState, broadcaster string) (*model.TwitchStreamInfo, error) {
|
||||
func GetTwitchStatus(app *app.AppState, broadcaster string) (*twitch.StreamInfo, error) {
|
||||
if lastStreamState != nil && time.Now().UTC().Before(lastStreamStateAt.Add(time.Minute)) {
|
||||
return lastStreamState, nil
|
||||
}
|
||||
|
|
@ -76,7 +77,7 @@ func GetTwitchStatus(app *model.AppState, broadcaster string) (*model.TwitchStre
|
|||
}
|
||||
|
||||
type StreamsResponse struct {
|
||||
Data []model.TwitchStreamInfo `json:"data"`
|
||||
Data []twitch.StreamInfo `json:"data"`
|
||||
}
|
||||
streamInfo := StreamsResponse{}
|
||||
err = json.NewDecoder(res.Body).Decode(&streamInfo)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue