move new avatar into bedroom
parent
e8a17a2477
commit
043790c015
|
@ -150,19 +150,6 @@ func (s *gameWorldServer) Register(ctx context.Context, auth *proto.AuthInfo) (s
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var avatar *db.Object
|
|
||||||
avatar, err = s.db.CreateAvatar(account)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("created %#v for %s", avatar, account.Name)
|
|
||||||
|
|
||||||
// TODO create avatar object
|
|
||||||
// TODO create bedroom object
|
|
||||||
// TODO put avatar in bedroom
|
|
||||||
// TODO send room info, avatar info to client (need to figure this out and update proto)
|
|
||||||
|
|
||||||
var sessionID string
|
var sessionID string
|
||||||
sessionID, err = s.db.StartSession(*account)
|
sessionID, err = s.db.StartSession(*account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -170,6 +157,23 @@ func (s *gameWorldServer) Register(ctx context.Context, auth *proto.AuthInfo) (s
|
||||||
}
|
}
|
||||||
log.Printf("started session for %s", account.Name)
|
log.Printf("started session for %s", account.Name)
|
||||||
|
|
||||||
|
av, err := s.db.AvatarBySessionID(sessionID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
bedroom, err := s.db.BedroomBySessionID(sessionID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.db.MoveInto(*av, *bedroom)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO send room info, avatar info to client (need to figure this out and update proto)
|
||||||
|
|
||||||
si = &proto.SessionInfo{SessionID: sessionID}
|
si = &proto.SessionInfo{SessionID: sessionID}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
235
server/db/db.go
235
server/db/db.go
|
@ -3,7 +3,6 @@ package db
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
@ -16,20 +15,33 @@ import (
|
||||||
// go:embed schema.sql
|
// go:embed schema.sql
|
||||||
var schema string
|
var schema string
|
||||||
|
|
||||||
|
// TODO I have a suspicion that I'm going to want to move to an ORM like model where the object struct has a DB member and various methods on it. For now I'm just going to keep adding shit to the DB interface because if it doesn't get too long in the end then it's fine.
|
||||||
|
|
||||||
|
type DB interface {
|
||||||
|
// Accounts
|
||||||
|
CreateAccount(string, string) (*Account, error)
|
||||||
|
ValidateCredentials(string, string) (*Account, error)
|
||||||
|
GetAccount(string) (*Account, error)
|
||||||
|
StartSession(Account) (string, error)
|
||||||
|
EndSession(string) error
|
||||||
|
|
||||||
|
// Presence
|
||||||
|
AvatarBySessionID(string) (*Object, error)
|
||||||
|
BedroomBySessionID(string) (*Object, error)
|
||||||
|
MoveInto(toMove Object, container Object) error
|
||||||
|
}
|
||||||
|
|
||||||
type Account struct {
|
type Account struct {
|
||||||
ID int
|
ID int
|
||||||
Name string
|
Name string
|
||||||
Pwhash string
|
Pwhash string
|
||||||
}
|
}
|
||||||
|
|
||||||
type DB interface {
|
type Object struct {
|
||||||
// EnsureSchema() TODO look into tern
|
ID int
|
||||||
CreateAccount(string, string) (*Account, error)
|
Avatar bool
|
||||||
CreateAvatar(*Account) (*Object, error)
|
Bedroom bool
|
||||||
ValidateCredentials(string, string) (*Account, error)
|
Data map[string]string
|
||||||
GetAccount(string) (*Account, error)
|
|
||||||
StartSession(Account) (string, error)
|
|
||||||
EndSession(string) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type pgDB struct {
|
type pgDB struct {
|
||||||
|
@ -48,28 +60,67 @@ func NewDB(connURL string) (DB, error) {
|
||||||
return pgdb, nil
|
return pgdb, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *pgDB) CreateAccount(name, password string) (*Account, error) {
|
func (db *pgDB) CreateAccount(name, password string) (account *Account, err error) {
|
||||||
conn, err := db.pool.Acquire(context.Background())
|
ctx := context.Background()
|
||||||
|
tx, err := db.pool.Begin(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = conn.Exec(context.Background(),
|
defer tx.Rollback(ctx)
|
||||||
"INSERT INTO accounts (name, pwhash) VALUES ( $1, $2 )", name, password)
|
|
||||||
if err != nil {
|
account = &Account{
|
||||||
return nil, err
|
Name: name,
|
||||||
}
|
Pwhash: password,
|
||||||
|
|
||||||
row := conn.QueryRow(context.Background(), "SELECT id,name,pwhash FROM accounts WHERE name = $1", name)
|
|
||||||
a := &Account{}
|
|
||||||
err = row.Scan(&a.ID, &a.Name, &a.Pwhash)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stmt := "INSERT INTO accounts (name, pwhash) VALUES ( $1, $2 ) RETURNING id"
|
||||||
|
err = tx.QueryRow(ctx, stmt, name, password).Scan(&account.ID)
|
||||||
// TODO handle and cleanup unqiue violations
|
// TODO handle and cleanup unqiue violations
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
return a, err
|
var pid int
|
||||||
|
stmt = "INSERT INTO permissions DEFAULT VALUES RETURNING id"
|
||||||
|
err = tx.QueryRow(ctx, stmt).Scan(&pid)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data := map[string]string{}
|
||||||
|
data["name"] = account.Name
|
||||||
|
data["description"] = fmt.Sprintf("a gaseous form. it smells faintly of %s.", randSmell())
|
||||||
|
av := &Object{
|
||||||
|
Avatar: true,
|
||||||
|
Data: data,
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt = "INSERT INTO objects ( avatar, data, perms, owner ) VALUES ( $1, $2, $3, $4 ) RETURNING id"
|
||||||
|
err = tx.QueryRow(ctx, stmt, av.Avatar, av.Data, pid, account.ID).Scan(&av.ID)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt = "INSERT INTO permissions DEFAULT VALUES RETURNING id"
|
||||||
|
err = tx.QueryRow(ctx, stmt).Scan(&pid)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bedroom := &Object{
|
||||||
|
Bedroom: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt = "INSERT INTO objects ( bedroom, data, perms, owner ) VALUES ( $1, $2, $3, $4 )"
|
||||||
|
_, err = tx.Exec(ctx, stmt, bedroom.Bedroom, bedroom.Data, pid, account.ID)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tx.Commit(ctx)
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *pgDB) ValidateCredentials(name, password string) (*Account, error) {
|
func (db *pgDB) ValidateCredentials(name, password string) (*Account, error) {
|
||||||
|
@ -87,87 +138,101 @@ func (db *pgDB) ValidateCredentials(name, password string) (*Account, error) {
|
||||||
return a, nil
|
return a, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *pgDB) GetAccount(name string) (*Account, error) {
|
func (db *pgDB) GetAccount(name string) (a *Account, err error) {
|
||||||
conn, err := db.pool.Acquire(context.Background())
|
a = &Account{}
|
||||||
if err != nil {
|
stmt := "SELECT id, name, pwhash FROM accounts WHERE name = $1"
|
||||||
return nil, err
|
err = db.pool.QueryRow(context.Background(), stmt, name).Scan(&a.ID, &a.Name, &a.Pwhash)
|
||||||
}
|
return
|
||||||
|
|
||||||
row := conn.QueryRow(context.Background(), "SELECT id, name, pwhash FROM accounts WHERE name = $1", name)
|
|
||||||
|
|
||||||
a := &Account{}
|
|
||||||
|
|
||||||
err = row.Scan(&a.ID, &a.Name, &a.Pwhash)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return a, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *pgDB) StartSession(a Account) (sessionID string, err error) {
|
func (db *pgDB) StartSession(a Account) (sid string, err error) {
|
||||||
conn, err := db.pool.Acquire(context.Background())
|
sid = uuid.New().String()
|
||||||
|
_, err = db.pool.Exec(context.Background(),
|
||||||
|
"INSERT INTO sessions (id, account) VALUES ( $1, $2 )", sid, a.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionID = uuid.New().String()
|
// Clean up any ghosts to prevent avatar duplication
|
||||||
|
// TODO subquery
|
||||||
_, err = conn.Exec(context.Background(), "INSERT INTO sessions (id, account) VALUES ( $1, $2 )", sessionID, a.ID)
|
stmt := "DELETE FROM contains WHERE contained = (SELECT id FROM objects WHERE objects.avatar = true and objects.owner = $1)"
|
||||||
|
_, err = db.pool.Exec(context.Background(), stmt, a.ID)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("failed to clean up ghosts for %d: %s", a.ID, err.Error())
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *pgDB) EndSession(sid string) error {
|
func (db *pgDB) EndSession(sid string) (err error) {
|
||||||
if sid == "" {
|
if sid == "" {
|
||||||
log.Println("db.EndSession called with empty session id")
|
log.Println("db.EndSession called with empty session id")
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, err := db.pool.Acquire(context.Background())
|
var o *Object
|
||||||
|
if o, err = db.AvatarBySessionID(sid); err == nil {
|
||||||
|
if _, err = db.pool.Exec(context.Background(),
|
||||||
|
"DELETE FROM contains WHERE contained = $1", o.ID); err != nil {
|
||||||
|
log.Printf("failed to remove avatar from room: %s", err.Error())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Printf("failed to find avatar for session %s: %s", sid, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = db.pool.Exec(context.Background(), "DELETE FROM sessions WHERE id = $1", sid)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *pgDB) AvatarBySessionID(sid string) (avatar *Object, err error) {
|
||||||
|
avatar = &Object{}
|
||||||
|
|
||||||
|
// TODO subquery
|
||||||
|
stmt := `
|
||||||
|
SELECT (id,avatar,bedroom,data)
|
||||||
|
FROM objects WHERE avatar = true AND owner = (
|
||||||
|
SELECT a.id FROM sessions s JOIN accounts a ON s.account = a.id WHERE s.id = $1)`
|
||||||
|
err = db.pool.QueryRow(context.Background(), stmt, sid).Scan(
|
||||||
|
&avatar.ID, &avatar.Avatar, &avatar.Bedroom, &avatar.Data)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *pgDB) BedroomBySessionID(sid string) (bedroom *Object, err error) {
|
||||||
|
bedroom = &Object{}
|
||||||
|
|
||||||
|
// TODO subquery
|
||||||
|
stmt := `
|
||||||
|
SELECT (id,avatar,bedroom,data)
|
||||||
|
FROM objects WHERE bedroom = true AND owner = (
|
||||||
|
SELECT a.id FROM sessions s JOIN accounts a ON s.account = a.id WHERE s.id = $1)`
|
||||||
|
err = db.pool.QueryRow(context.Background(), stmt, sid).Scan(
|
||||||
|
&bedroom.ID, &bedroom.Avatar, &bedroom.Bedroom, &bedroom.Data)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *pgDB) MoveInto(toMove Object, container Object) error {
|
||||||
|
ctx := context.Background()
|
||||||
|
tx, err := db.pool.Begin(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer tx.Rollback(ctx)
|
||||||
|
|
||||||
|
stmt := "DELETE FROM contains WHERE contained = $1"
|
||||||
|
_, err = tx.Exec(ctx, stmt, toMove.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = conn.Exec(context.Background(), "DELETE FROM sessions WHERE id = $1", sid)
|
stmt = "INSERT INTO contains (contained, container) VALUES ($1, $1)"
|
||||||
|
_, err = tx.Exec(ctx, stmt, toMove.ID, container.ID)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
type Object struct {
|
|
||||||
ID int
|
|
||||||
Avatar bool
|
|
||||||
Bedroom bool
|
|
||||||
data string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *pgDB) CreateAvatar(account *Account) (avatar *Object, err error) {
|
|
||||||
// TODO start a transaction
|
|
||||||
data := map[string]string{}
|
|
||||||
data["name"] = account.Name
|
|
||||||
data["description"] = fmt.Sprintf("a gaseous form. it smells faintly of %s.", randSmell())
|
|
||||||
d, _ := json.Marshal(data)
|
|
||||||
avatar = &Object{
|
|
||||||
Avatar: true,
|
|
||||||
data: string(d),
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO I need to understand how to make use of INSERT...RETURNING
|
|
||||||
|
|
||||||
// TODO how do I determine what perm id to use? I might want to revisit the
|
|
||||||
// schema for that so perm knows about an object and not the other way
|
|
||||||
// around. I could also just store this data on the objects table. I will
|
|
||||||
// ponder if there is any reasonable argument for a separate permissions
|
|
||||||
// table.
|
|
||||||
|
|
||||||
_, err = db.pool.Exec(context.Background(), "INSERT ")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO fetch and return avatar
|
return tx.Commit(ctx)
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func randSmell() string {
|
func randSmell() string {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
CREATE TABLE accounts (
|
CREATE TABLE accounts (
|
||||||
id serial PRIMARY KEY,
|
id serial PRIMARY KEY,
|
||||||
name varchar(100) NOT NULL,
|
name varchar(100) NOT NULL UNIQUE,
|
||||||
pwhash varchar(100) NOT NULL,
|
pwhash varchar(100) NOT NULL,
|
||||||
god boolean NOT NULL DEFAULT FALSE
|
god boolean NOT NULL DEFAULT FALSE
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in New Issue