mirror of
https://github.com/idanoo/GoScrobble
synced 2025-07-02 06:02:19 +00:00
Add user table + funcs
This commit is contained in:
parent
c6ef84cd5a
commit
671254223a
12 changed files with 172 additions and 571 deletions
|
@ -1,5 +0,0 @@
|
|||
package goscrobble
|
||||
|
||||
var (
|
||||
DBVersion = 1
|
||||
)
|
|
@ -10,15 +10,10 @@ import (
|
|||
_ "github.com/go-sql-driver/mysql"
|
||||
"github.com/golang-migrate/migrate"
|
||||
"github.com/golang-migrate/migrate/database/mysql"
|
||||
_ "github.com/golang-migrate/migrate/v4/source/file"
|
||||
_ "github.com/golang-migrate/migrate/source/file"
|
||||
)
|
||||
|
||||
type dbItem struct {
|
||||
conn *sql.DB
|
||||
version *int
|
||||
}
|
||||
|
||||
var db dbItem
|
||||
var db *sql.DB
|
||||
|
||||
// InitDb - Boots up a DB connection
|
||||
func InitDb() {
|
||||
|
@ -27,7 +22,7 @@ func InitDb() {
|
|||
dbPass := os.Getenv("MYSQL_PASS")
|
||||
dbName := os.Getenv("MYSQL_DB")
|
||||
|
||||
dbConn, err := sql.Open("mysql", dbUser+":"+dbPass+"@tcp("+dbHost+")/"+dbName)
|
||||
dbConn, err := sql.Open("mysql", dbUser+":"+dbPass+"@tcp("+dbHost+")/"+dbName+"?multiStatements=true")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -41,73 +36,38 @@ func InitDb() {
|
|||
panic(err)
|
||||
}
|
||||
|
||||
var vers int = 0
|
||||
db = dbItem{
|
||||
conn: dbConn,
|
||||
version: &vers,
|
||||
}
|
||||
|
||||
if !checkIfDbUpToDate() {
|
||||
fmt.Printf("Database not up to date.. triggering migrations!\n")
|
||||
} else {
|
||||
fmt.Printf("Database up to date!\n")
|
||||
|
||||
}
|
||||
db = dbConn
|
||||
|
||||
runMigrations()
|
||||
}
|
||||
|
||||
// CloseDbConn - Closes DB connection
|
||||
func CloseDbConn() {
|
||||
db.conn.Close()
|
||||
db.Close()
|
||||
}
|
||||
|
||||
// checkIfDbUpToDate - Checks if we need to run migrations
|
||||
func checkIfDbUpToDate() bool {
|
||||
fmt.Printf("Code version: %v. ", DBVersion)
|
||||
|
||||
dbVers := db.getDbVersion()
|
||||
fmt.Printf("DB version: %v.\n", dbVers)
|
||||
|
||||
if dbVers == DBVersion {
|
||||
return true
|
||||
} else if dbVers > DBVersion {
|
||||
panic("!!Warning!! Your database is newer than the code. Please update!")
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// getDbVersion - Gets version of schema or generate basic schema
|
||||
func (db dbItem) getDbVersion() int {
|
||||
stmtOut, err := db.conn.Prepare("SELECT version FROM goscrobble WHERE id = ? ")
|
||||
defer stmtOut.Close()
|
||||
|
||||
if err != nil {
|
||||
// We can assume this is a fresh database - Lets config it!
|
||||
return runMigrations(DBVersion)
|
||||
}
|
||||
|
||||
err = stmtOut.QueryRow(1).Scan(db.version)
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
|
||||
return *db.version
|
||||
}
|
||||
|
||||
func runMigrations(latestVersion int) int {
|
||||
driver, err := mysql.WithInstance(db.conn, &mysql.Config{})
|
||||
func runMigrations() {
|
||||
driver, err := mysql.WithInstance(db, &mysql.Config{})
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to run migrations! %v", err)
|
||||
}
|
||||
|
||||
m, _ := migrate.NewWithDatabaseInstance(
|
||||
"file:///migrations",
|
||||
m, err := migrate.NewWithDatabaseInstance(
|
||||
"file://migrations",
|
||||
"mysql",
|
||||
driver,
|
||||
)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Error fetching DB Migrations %v", err))
|
||||
}
|
||||
|
||||
m.Steps(2)
|
||||
err = m.Up()
|
||||
if err != nil {
|
||||
// Skip 'no change'. This is fine. Everything is fine.
|
||||
if err.Error() == "no change" {
|
||||
return
|
||||
}
|
||||
|
||||
return latestVersion
|
||||
panic(fmt.Errorf("Error running DB Migrations %v", err))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,51 +10,46 @@ import (
|
|||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
// spaHandler - Handles Single Page Applications (React)
|
||||
type spaHandler struct {
|
||||
staticPath string
|
||||
indexPath string
|
||||
}
|
||||
|
||||
// HandleRequests - Boot HTTP server
|
||||
// HandleRequests - Boot HTTP!
|
||||
func HandleRequests() {
|
||||
// Creates a new router
|
||||
// Create a new router
|
||||
httpRouter := mux.NewRouter().StrictSlash(true)
|
||||
|
||||
httpRouter.HandleFunc("/api/v1", serveEndpoint)
|
||||
httpRouter.HandleFunc("/api/v1/scrobble/jellyfin", serveEndpoint)
|
||||
// STATIC TOKEN AUTH
|
||||
httpRouter.HandleFunc("/api/v1/ingress/jellyfin", serveEndpoint)
|
||||
|
||||
// JWT AUTH?
|
||||
httpRouter.HandleFunc("/api/v1/profile/{id}", serveEndpoint)
|
||||
|
||||
// NO AUTH
|
||||
httpRouter.HandleFunc("/api/v1/login", serveEndpoint)
|
||||
httpRouter.HandleFunc("/api/v1/logout", serveEndpoint)
|
||||
httpRouter.HandleFunc("/api/v1/register", serveEndpoint)
|
||||
|
||||
// SERVE FRONTEND - NO AUTH
|
||||
spa := spaHandler{staticPath: "web/build", indexPath: "index.html"}
|
||||
httpRouter.PathPrefix("/").Handler(spa)
|
||||
|
||||
// fileServer := http.FileServer(http.Dir("web"))
|
||||
// fileMatcher := regexp.MustCompile(`\.[a-zA-Z]*$`)
|
||||
// http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
// if !fileMatcher.MatchString(r.URL.Path) {
|
||||
// http.ServeFile(w, r, "web/build/index.html")
|
||||
// } else {
|
||||
// fileServer.ServeHTTP(w, r)
|
||||
// }
|
||||
// })
|
||||
|
||||
// Serve HTTP Server
|
||||
// Serve it up!
|
||||
log.Fatal(http.ListenAndServe(":42069", httpRouter))
|
||||
}
|
||||
|
||||
// serveFrontend - Handle / queries
|
||||
func serveFrontend(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "Welcome!")
|
||||
}
|
||||
|
||||
func serveEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||
// Lets trick 'em for now
|
||||
fmt.Fprintf(w, "{}")
|
||||
}
|
||||
|
||||
func (h spaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// get the absolute path to prevent directory traversal
|
||||
// Get the absolute path to prevent directory traversal
|
||||
path, err := filepath.Abs(r.URL.Path)
|
||||
if err != nil {
|
||||
// if we failed to get the absolute path respond with a 400 bad request
|
||||
// and stop
|
||||
// If we failed to get the absolute path respond with a 400 bad request and return
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -1 +1,91 @@
|
|||
package goscrobble
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
const bCryptCost = 16
|
||||
|
||||
type User struct {
|
||||
UUID string `json:"uuid"`
|
||||
Username string `json:"username"`
|
||||
password []byte
|
||||
Email string `json:"email"`
|
||||
Verified bool `json:"verified"`
|
||||
Active bool `json:"active"`
|
||||
Admin bool `json:"admin"`
|
||||
}
|
||||
|
||||
// createUser - Called from API
|
||||
func createUser(username string, email string, password string) error {
|
||||
// Check if user already exists..
|
||||
if len(password) < 8 {
|
||||
return errors.New("Password must be at least 8 characters")
|
||||
}
|
||||
|
||||
// Check username is set
|
||||
if username == "" {
|
||||
return errors.New("A username is required")
|
||||
}
|
||||
|
||||
// Check if user or email exists!
|
||||
if userAlreadyExists(username, email) {
|
||||
return errors.New("Username or email already exists")
|
||||
}
|
||||
|
||||
// Lets hashit!
|
||||
hash, err := hashPassword(password)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return insertUser(username, email, hash)
|
||||
}
|
||||
|
||||
// insertUser - Does the dirtywork!
|
||||
func insertUser(username string, email string, password []byte) error {
|
||||
_, err := db.Exec("INSERT INTO users (uuid, username, email, password) VALUES (UUID_TO_BIN(UUID(), true),'?','?','?')", username, email, password)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// hashPassword - Returns bcrypt hash
|
||||
func hashPassword(password string) ([]byte, error) {
|
||||
return bcrypt.GenerateFromPassword([]byte(password), bCryptCost)
|
||||
}
|
||||
|
||||
// isValidPassword - Checks if password is valid
|
||||
func isValidPassword(password string, user User) bool {
|
||||
err := bcrypt.CompareHashAndPassword(user.password, []byte(password))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// userAlreadyExists - Returns bool indicating if a record exists for either username or email
|
||||
// Using two look ups to make use of DB indexes.
|
||||
func userAlreadyExists(username string, email string) bool {
|
||||
var usernameCount, emailCount int
|
||||
err := db.QueryRow("SELECT COUNT(*) FROM users WHERE username = '?'", username).Scan(&usernameCount)
|
||||
// Only run email check if there's an email...
|
||||
if email != "" {
|
||||
err = db.QueryRow("SELECT COUNT(*) FROM users WHERE email = '?'", email).Scan(&emailCount)
|
||||
} else {
|
||||
emailCount = 0
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("Error querying for duplicate users: %v", err)
|
||||
return true
|
||||
}
|
||||
|
||||
count := usernameCount + emailCount
|
||||
|
||||
// If there is more than one.. Return true. User exists.
|
||||
return count != 0
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue