adding project to github

This commit is contained in:
beorc-gar
2020-09-19 13:20:25 -04:00
commit 9d0ea6071a
30 changed files with 1082 additions and 0 deletions

184
api/account/member.go Normal file
View File

@@ -0,0 +1,184 @@
package account
import (
"errors"
"regexp"
"../database"
"github.com/twinj/uuid"
)
type Account struct {
Member Member `json:"member"`
User User `json:"user"`
Sso []Sso `json:"sso"`
}
type Member struct {
Id string `json:"id" db:"id"`
Name string `json:"name" db:"name"`
}
type User struct {
Id string `json:"id" db:"id"`
Username string `json:"username" db:"username"`
Password string `json:"password" db:"password"`
}
type Sso struct {
Id string `json:"id" db:"id"`
Token string `json:"token" db:"token"`
SsoTypeId string `json:"ssoTypeId" db:"ssoTypeId"`
}
type SsoType struct {
Id string `json:"id" db:"id"`
Source string `json:"source" db:"source"`
}
func Login(account *Account) error {
selectStatement := "SELECT BIN_TO_UUID(`member`.`id`) AS memberId " +
", `member`.`name` " +
", COALESCE(BIN_TO_UUID(`user`.`id`), '') AS userId " +
", COALESCE(`user`.`username`, '') AS username " +
", COALESCE(`user`.`password`, '') AS password " +
", COALESCE(BIN_TO_UUID(`sso`.`id`), '') AS ssoId " +
", COALESCE(`sso`.`token`, '') AS token " +
", COALESCE(BIN_TO_UUID(`sso`.`ssoTypeId`), '') AS ssoTypeId " +
"FROM `member` " +
"LEFT JOIN `user` ON `member`.`id` = `user`.`memberId` " +
"LEFT JOIN `sso` ON `member`.`id` = `sso`.`memberId` "
whereClause := ""
vars := make([]interface{}, 2)
errorMessage := ""
if &account.User != nil && &account.User.Username != nil && &account.User.Password != nil && account.User.Password != "" {
whereClause = "WHERE LOWER(`user`.`username`) = LOWER(?) " +
"AND `user`.`password` = ?"
vars[0] = account.User.Username
vars[1] = account.User.Password
errorMessage = "invalid username or password"
account.Sso = make([]Sso, 1)
account.Sso[0] = Sso{}
} else if &account.Sso != nil && len(account.Sso) == 1 && account.Sso[0].Token != "" && &account.Sso[0].SsoTypeId != nil && account.Sso[0].SsoTypeId != "" {
whereClause = "WHERE `sso`.`token` = ? " +
"AND `sso`.`ssoTypeId` = ?"
vars[0] = account.Sso[0].Token
vars[1] = account.Sso[0].SsoTypeId
errorMessage = "invalid sso credentials"
} else {
return errors.New("account must have either a user or an sso login")
}
result, err := database.DB.Query(selectStatement+whereClause, vars...)
if err != nil {
return err
} else if !result.Next() {
return errors.New(errorMessage)
}
err = result.Scan(&account.Member.Id, &account.Member.Name, &account.User.Id, &account.User.Username, &account.User.Password, &account.Sso[0].Id, &account.Sso[0].Token, &account.Sso[0].SsoTypeId)
return err
}
func Create(account *Account) error {
if account == nil || &account.Member == nil || account.Member.Name == "" {
return errors.New("account needs to have a name")
} else if &account.User != nil && &account.User.Username != nil && &account.User.Password != nil && account.User.Password != "" {
matched, err := regexp.MatchString("(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|\"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*\")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\\])", account.User.Username)
if err == nil && matched {
return createViaUser(account)
} else {
return errors.New("username must be an email address")
}
} else if &account.Sso != nil && len(account.Sso) == 1 && account.Sso[0].Token != "" && &account.Sso[0].SsoTypeId != nil && account.Sso[0].SsoTypeId != "" {
return createViaSso(account)
} else {
return errors.New("account must have either a user or an sso login")
}
}
func createViaUser(account *Account) error {
account.Member.Id = uuid.NewV4().String()
account.User.Id = uuid.NewV4().String()
exists, err := database.Exists("user", "username", account.User.Username)
if err != nil {
return err
} else if exists {
return errors.New("username unavailable")
}
err = database.Insert("member", account.Member, nil, nil)
if err != nil {
return err
}
err = database.Insert("user", account.User, []string{"memberId"}, []interface{}{account.Member.Id})
return err
}
func createViaSso(account *Account) error {
account.Member.Id = uuid.NewV4().String()
account.Sso[0].Id = uuid.NewV4().String()
result, err := database.DB.Query("SELECT BIN_TO_UUID(`id`) AS id, `source` FROM `ssoType` WHERE `id` = UUID_TO_BIN(?);", account.Sso[0].SsoTypeId)
if err != nil {
return err
} else if !result.Next() {
return errors.New("no sso type with this id")
}
result, err = database.DB.Query("SELECT COUNT(`id`) AS c FROM `sso` WHERE `token` = ? AND `ssoTypeId` = UUID_TO_BIN(?);", account.Sso[0].Token, account.Sso[0].SsoTypeId)
if err != nil {
return err
} else if !result.Next() {
return errors.New("query produced no results")
}
var c int
err = result.Scan(&c)
if err != nil {
return err
} else if c != 0 {
return errors.New("this sso already exists")
}
err = database.Insert("member", account.Member, nil, nil)
if err != nil {
return err
}
err = database.Insert("sso", account.Sso[0], []string{"memberId"}, []interface{}{account.Member.Id})
return err
}
func SsoTypes() ([]SsoType, error) {
result, err := database.DB.Query("SELECT BIN_TO_UUID(`id`) AS id, `source` FROM `ssoType`")
if err != nil {
return nil, err
}
ssoTypes := make([]SsoType,)
for result.Next() {
}
var c int
err = result.Scan(&c)
if err != nil {
return nil, err
} else if c != 0 {
return errors.New("this sso already exists")
}
}

113
api/database/connection.go Normal file
View File

@@ -0,0 +1,113 @@
package database
import (
"database/sql"
"errors"
"reflect"
"strings"
)
var DB *sql.DB
func Insert(table string, object interface{}, additionalColumns []string, additionalValues []interface{}) error {
return upsert(table, object, false, additionalColumns, additionalValues)
}
func Update(table string, object interface{}, additionalColumns []string, additionalValues []interface{}) error {
return upsert(table, object, true, additionalColumns, additionalValues)
}
func upsert(table string, object interface{}, isUpdate bool, additionalColumns []string, additionalValues []interface{}) error {
statement := "INSERT INTO `" + table + "` (" // col1, col2
values := ") VALUES (" // ?, ?
v := reflect.ValueOf(object)
if isUpdate {
statement = "UPDATE `" + table + "` SET"
values = ""
}
for i := 0; i < v.Type().NumField(); i++ {
if v.Field(i).CanInterface() {
additionalColumns = append(additionalColumns, getColumn(v.Type().Field(i)))
additionalValues = append(additionalValues, v.Field(i).Interface())
}
}
for i := 0; i < len(additionalColumns); i++ {
if i > 0 {
if !isUpdate {
statement += ", "
}
values += ", "
}
column := additionalColumns[i]
value := "?"
if column == "id" || strings.HasSuffix(column, "Id") {
value = "UUID_TO_BIN(?)"
}
if isUpdate {
values += "`" + column + "` = " + value
if column == "id" {
additionalValues = append(additionalValues, additionalValues[i])
}
} else {
statement += "`" + column + "`"
values += value
}
}
if isUpdate {
values += " WHERE `id` = UUID_TO_BIN(?);"
} else {
values += ");"
}
rows, err := DB.Exec(statement+values, additionalValues...)
if err != nil {
return err
}
n, err := rows.RowsAffected()
if err != nil {
return err
} else if n != 1 {
operation := "insert to"
if isUpdate {
operation = "update"
}
return errors.New("failed to " + operation + " " + table)
}
return nil
}
func getColumn(field reflect.StructField) string {
column, ok := field.Tag.Lookup("db")
if !ok {
column = field.Name
}
return column
}
func Exists(table string, column string, value interface{}) (bool, error) {
result, err := DB.Query("SELECT COUNT(`id`) AS c FROM `"+table+"` WHERE `"+column+"` = ?;", value)
if err != nil {
return false, err
} else if !result.Next() {
return false, errors.New("query produced no results")
}
var c int
err = result.Scan(&c)
if err != nil {
return false, err
}
return c > 0, nil
}

65
api/endpoint/account.go Normal file
View File

@@ -0,0 +1,65 @@
package endpoint
import (
"encoding/json"
"net/http"
"../account"
"../util"
)
//TODO do not return passwords and sso tokens
func CreateAccount(w http.ResponseWriter, req *http.Request) {
var acc account.Account
err := json.NewDecoder(req.Body).Decode(&acc)
if err != nil {
util.ErrorResponse(err, w)
return
}
err = account.Create(&acc)
if err != nil {
util.ErrorResponse(err, w)
return
}
jsonData, err := json.Marshal(acc)
if err != nil {
util.ErrorResponse(err, w)
return
}
w.Write(jsonData)
}
func Login(w http.ResponseWriter, req *http.Request) {
var acc account.Account
err := json.NewDecoder(req.Body).Decode(&acc)
if err != nil {
util.ErrorResponse(err, w)
return
}
err = account.Login(&acc)
if err != nil {
util.ErrorResponse(err, w)
return
}
jsonData, err := json.Marshal(acc)
if err != nil {
util.ErrorResponse(err, w)
return
}
w.Write(jsonData)
}

56
api/league/league.go Normal file
View File

@@ -0,0 +1,56 @@
package league
import (
"errors"
"../database"
"github.com/twinj/uuid"
)
type League struct {
Id string `json:"id"`
Name string `json:"name"`
Public bool `json:"public"`
}
func createLeague(league *League) error {
if league == nil || &league.Name == nil || league.Name == "" {
return errors.New("league needs to have a name")
}
//todo use db.exists
result, err := database.DB.Query("SELECT COUNT(`id`) AS c FROM `league` WHERE `name` = ?;", league.Name)
if err != nil {
return err
} else if !result.Next() {
return errors.New("query produced no results")
}
var c int
err = result.Scan(&c)
if err != nil {
return err
} else if c != 0 {
return errors.New("league name unavailable")
}
league.Id = uuid.NewV4().String()
if &league.Public == nil {
league.Public = true
}
//todo use db.insert
rows, err := database.DB.Exec("INSERT INTO `league` (`id`, `name`, `public`) VALUES (UUID_TO_BIN(?), ?, ?);", league.Id, league.Name, league.Public)
if err != nil {
return err
}
n, err := rows.RowsAffected()
if err != nil {
return err
} else if n != 1 {
return errors.New("failed to insert member")
}
}

28
api/main.go Normal file
View File

@@ -0,0 +1,28 @@
package main
import (
"database/sql"
"net/http"
"./database"
"./endpoint"
_ "github.com/go-sql-driver/mysql"
"github.com/gorilla/mux"
)
func main() {
//todo get connection string based on zone
db, err := sql.Open("mysql", "root:Se4Q2Lp-3587@tcp(localhost)/baseball")
if err != nil {
//todo handle it (fatal, can't continue.)
}
database.DB = db
defer database.DB.Close()
router := mux.NewRouter()
router.HandleFunc("/account/create", endpoint.CreateAccount).Methods("POST")
router.HandleFunc("/account/login", endpoint.Login).Methods("POST")
http.ListenAndServe(":8080", router)
}

12
api/util/log.go Normal file
View File

@@ -0,0 +1,12 @@
package util
import (
"net/http"
"strings"
)
func ErrorResponse(err error, w http.ResponseWriter) {
if err != nil {
w.Write([]byte("{\"error\":\"" + strings.Replace(err.Error(), "\"", "\\\"", -1) + "\"}"))
}
}

13
data/README.md Normal file
View File

@@ -0,0 +1,13 @@
# How to use these files
Each `sql` script in this folder needs to be executed using the appropriate database.
Due to foreign key constraints, a proper order must be followed.
`_MASTER_.sql` has the proper order, and can be executed like so:
```sql
use baseball;
source /path/to/_MASTER_.sql;
```
Which will then execute each script in order.
Then, verify that each statement was successful.

19
data/_MASTER_.sql Normal file
View File

@@ -0,0 +1,19 @@
source C:\Users\Bronson\Desktop\baseballApp\data\league.sql
source C:\Users\Bronson\Desktop\baseballApp\data\member.sql
source C:\Users\Bronson\Desktop\baseballApp\data\role.sql
source C:\Users\Bronson\Desktop\baseballApp\data\leagueMember.sql
source C:\Users\Bronson\Desktop\baseballApp\data\team.sql
source C:\Users\Bronson\Desktop\baseballApp\data\position.sql
source C:\Users\Bronson\Desktop\baseballApp\data\player.sql
source C:\Users\Bronson\Desktop\baseballApp\data\teamPlayer.sql
source C:\Users\Bronson\Desktop\baseballApp\data\trade.sql
source C:\Users\Bronson\Desktop\baseballApp\data\injury.sql
source C:\Users\Bronson\Desktop\baseballApp\data\invitation.sql
source C:\Users\Bronson\Desktop\baseballApp\data\statistic.sql
source C:\Users\Bronson\Desktop\baseballApp\data\draft.sql
source C:\Users\Bronson\Desktop\baseballApp\data\draftPick.sql
source C:\Users\Bronson\Desktop\baseballApp\data\notificationTemplate.sql
source C:\Users\Bronson\Desktop\baseballApp\data\notification.sql
source C:\Users\Bronson\Desktop\baseballApp\data\user.sql
source C:\Users\Bronson\Desktop\baseballApp\data\ssoType.sql
source C:\Users\Bronson\Desktop\baseballApp\data\sso.sql

29
data/draft.sql Normal file
View File

@@ -0,0 +1,29 @@
CREATE TABLE IF NOT EXISTS `draft` (
`id` BINARY(16) PRIMARY KEY,
`leagueId` BINARY(16) NOT NULL,
`turn` BINARY(16) NOT NULL, -- this is the leagueMemberId of whose turn it is. not foreign key so if the member is removed, we can go: this person doesn't exist, so it's the next member after.
`created` DATETIME,
`modified` DATETIME,
FOREIGN KEY(`leagueId`) REFERENCES `league`(`id`) ON DELETE CASCADE
);
DROP TRIGGER IF EXISTS `insertDraftTrigger`;
DELIMITER $$
CREATE TRIGGER `insertDraftTrigger` BEFORE INSERT ON `draft`
FOR EACH ROW
BEGIN
SET NEW.`created` = IFNULL(NEW.`created`, NOW());
END$$
DELIMITER ;
DROP TRIGGER IF EXISTS `updateDraftTrigger`;
DELIMITER $$
CREATE TRIGGER `updateDraftTrigger` BEFORE UPDATE ON `draft`
FOR EACH ROW
BEGIN
SET NEW.`created` = OLD.`created`;
SET NEW.`modified` = NOW();
END$$
DELIMITER ;

34
data/draftPick.sql Normal file
View File

@@ -0,0 +1,34 @@
CREATE TABLE IF NOT EXISTS `draftPick` (
`id` BINARY(16) PRIMARY KEY,
`draftId` BINARY(16) NOT NULL,
`leagueMemberId` BINARY(16) NOT NULL,
`order` INT(11) NOT NULL,
`created` DATETIME,
`modified` DATETIME,
UNIQUE (`draftId`, `leagueMemberId`),
UNIQUE (`leagueMemberId`, `order`),
FOREIGN KEY(`draftId`) REFERENCES `draft`(`id`) ON DELETE CASCADE,
FOREIGN KEY(`leagueMemberId`) REFERENCES `leagueMember`(`id`) ON DELETE CASCADE
);
DROP TRIGGER IF EXISTS `insertDraftPickTrigger`;
DELIMITER $$
CREATE TRIGGER `insertDraftPickTrigger` BEFORE INSERT ON `draftPick`
FOR EACH ROW
BEGIN
SET NEW.`created` = IFNULL(NEW.`created`, NOW());
END$$
DELIMITER ;
DROP TRIGGER IF EXISTS `updateDraftPickTrigger`;
DELIMITER $$
CREATE TRIGGER `updateDraftPickTrigger` BEFORE UPDATE ON `draftPick`
FOR EACH ROW
BEGIN
SET NEW.`created` = OLD.`created`;
SET NEW.`modified` = NOW();
END$$
DELIMITER ;

30
data/injury.sql Normal file
View File

@@ -0,0 +1,30 @@
CREATE TABLE IF NOT EXISTS `injury` (
`id` BINARY(16) PRIMARY KEY,
`playerId` BINARY(16) NOT NULL,
`reason` VARCHAR(1024), -- this and below are from fantasybaseballnerd.com
`notes` VARCHAR(2048),
`created` DATETIME,
`modified` DATETIME,
FOREIGN KEY(`playerId`) REFERENCES `player`(`id`) ON DELETE CASCADE
);
DROP TRIGGER IF EXISTS `insertInjuryTrigger`;
DELIMITER $$
CREATE TRIGGER `insertInjuryTrigger` BEFORE INSERT ON `injury`
FOR EACH ROW
BEGIN
SET NEW.`created` = IFNULL(NEW.`created`, NOW());
END$$
DELIMITER ;
DROP TRIGGER IF EXISTS `updateInjuryTrigger`;
DELIMITER $$
CREATE TRIGGER `updateInjuryTrigger` BEFORE UPDATE ON `injury`
FOR EACH ROW
BEGIN
SET NEW.`created` = OLD.`created`;
SET NEW.`modified` = NOW();
END$$
DELIMITER ;

33
data/invitation.sql Normal file
View File

@@ -0,0 +1,33 @@
CREATE TABLE IF NOT EXISTS `invitation` (
`id` BINARY(16) PRIMARY KEY,
`leagueId` BINARY(16) NOT NULL,
`memberId` BINARY(16) NOT NULL,
`isRequest` BOOLEAN NOT NULL, -- true if member asked to join league, false if league invited member.
`created` DATETIME,
`modified` DATETIME,
UNIQUE (`leagueId`, `memberId`),
FOREIGN KEY(`leagueId`) REFERENCES `league`(`id`) ON DELETE CASCADE,
FOREIGN KEY(`memberId`) REFERENCES `member`(`id`) ON DELETE CASCADE
);
DROP TRIGGER IF EXISTS `insertInvitationTrigger`;
DELIMITER $$
CREATE TRIGGER `insertInvitationTrigger` BEFORE INSERT ON `invitation`
FOR EACH ROW
BEGIN
SET NEW.`created` = IFNULL(NEW.`created`, NOW());
END$$
DELIMITER ;
DROP TRIGGER IF EXISTS `updateInvitationTrigger`;
DELIMITER $$
CREATE TRIGGER `updateInvitationTrigger` BEFORE UPDATE ON `invitation`
FOR EACH ROW
BEGIN
SET NEW.`created` = OLD.`created`;
SET NEW.`modified` = NOW();
END$$
DELIMITER ;

27
data/league.sql Normal file
View File

@@ -0,0 +1,27 @@
CREATE TABLE IF NOT EXISTS `league` (
`id` BINARY(16) PRIMARY KEY,
`name` VARCHAR(64) NOT NULL UNIQUE,
`public` BOOLEAN NOT NULL,
`created` DATETIME,
`modified` DATETIME
);
DROP TRIGGER IF EXISTS `insertLeagueTrigger`;
DELIMITER $$
CREATE TRIGGER `insertLeagueTrigger` BEFORE INSERT ON `league`
FOR EACH ROW
BEGIN
SET NEW.`created` = IFNULL(NEW.`created`, NOW());
END$$
DELIMITER ;
DROP TRIGGER IF EXISTS `updateLeagueTrigger`;
DELIMITER $$
CREATE TRIGGER `updateLeagueTrigger` BEFORE UPDATE ON `league`
FOR EACH ROW
BEGIN
SET NEW.`created` = OLD.`created`;
SET NEW.`modified` = NOW();
END$$
DELIMITER ;

35
data/leagueMember.sql Normal file
View File

@@ -0,0 +1,35 @@
CREATE TABLE IF NOT EXISTS `leagueMember` (
`id` BINARY(16) PRIMARY KEY,
`leagueId` BINARY(16) NOT NULL,
`memberId` BINARY(16) NOT NULL,
`nickName` VARCHAR(64),
`roleId` BINARY(16) NOT NULL,
`created` DATETIME,
`modified` DATETIME,
UNIQUE (`leagueId`, `memberId`),
FOREIGN KEY(`leagueId`) REFERENCES `league`(`id`) ON DELETE CASCADE,
FOREIGN KEY(`memberId`) REFERENCES `member`(`id`) ON DELETE CASCADE,
FOREIGN KEY(`roleId`) REFERENCES `role`(`id`)
);
DROP TRIGGER IF EXISTS `insertLeagueMemberTrigger`;
DELIMITER $$
CREATE TRIGGER `insertLeagueMemberTrigger` BEFORE INSERT ON `leagueMember`
FOR EACH ROW
BEGIN
SET NEW.`created` = IFNULL(NEW.`created`, NOW());
END$$
DELIMITER ;
DROP TRIGGER IF EXISTS `updateLeagueMemberTrigger`;
DELIMITER $$
CREATE TRIGGER `updateLeagueMemberTrigger` BEFORE UPDATE ON `leagueMember`
FOR EACH ROW
BEGIN
SET NEW.`created` = OLD.`created`;
SET NEW.`modified` = NOW();
END$$
DELIMITER ;

26
data/member.sql Normal file
View File

@@ -0,0 +1,26 @@
CREATE TABLE IF NOT EXISTS `member` (
`id` BINARY(16) PRIMARY KEY,
`name` VARCHAR(64) NOT NULL,
`created` DATETIME,
`modified` DATETIME
);
DROP TRIGGER IF EXISTS `insertMemberTrigger`;
DELIMITER $$
CREATE TRIGGER `insertMemberTrigger` BEFORE INSERT ON `member`
FOR EACH ROW
BEGIN
SET NEW.`created` = IFNULL(NEW.`created`, NOW());
END$$
DELIMITER ;
DROP TRIGGER IF EXISTS `updateMemberTrigger`;
DELIMITER $$
CREATE TRIGGER `updateMemberTrigger` BEFORE UPDATE ON `member`
FOR EACH ROW
BEGIN
SET NEW.`created` = OLD.`created`;
SET NEW.`modified` = NOW();
END$$
DELIMITER ;

34
data/notification.sql Normal file
View File

@@ -0,0 +1,34 @@
CREATE TABLE IF NOT EXISTS `notification` (
`id` BINARY(16) PRIMARY KEY,
`templateId` BINARY(16) NOT NULL,
`leagueMemberId` BINARY(16) NOT NULL,
`referenceId` BINARY(16), -- used to link to injury, invitation, etc.
`reference` VARCHAR(128), -- describe what the referenceId is referring to
`created` DATETIME,
`modified` DATETIME,
UNIQUE (`templateId`, `leagueMemberId`, `referenceId`),
FOREIGN KEY(`templateId`) REFERENCES `notificationTemplate`(`id`) ON DELETE CASCADE,
FOREIGN KEY(`leagueMemberId`) REFERENCES `leagueMember`(`id`) ON DELETE CASCADE
);
DROP TRIGGER IF EXISTS `insertNotificationTrigger`;
DELIMITER $$
CREATE TRIGGER `insertNotificationTrigger` BEFORE INSERT ON `notification`
FOR EACH ROW
BEGIN
SET NEW.`created` = IFNULL(NEW.`created`, NOW());
END$$
DELIMITER ;
DROP TRIGGER IF EXISTS `updateNotificationTrigger`;
DELIMITER $$
CREATE TRIGGER `updateNotificationTrigger` BEFORE UPDATE ON `notification`
FOR EACH ROW
BEGIN
SET NEW.`created` = OLD.`created`;
SET NEW.`modified` = NOW();
END$$
DELIMITER ;

View File

@@ -0,0 +1,14 @@
CREATE TABLE IF NOT EXISTS `notificationTemplate` (
`id` BINARY(16) PRIMARY KEY,
`code` VARCHAR(16) UNIQUE,
`title` VARCHAR(256),
`message` VARCHAR(2048)
);
INSERT IGNORE INTO `notificationTemplate` (`id`, `code`, `title`, `message`) VALUES
(UUID_TO_BIN(UUID()), 'YOUR-TURN', "It's your turn to pick!", "Why don't you go on ahead and make the next addition to your team?"), -- referenceId will be draftPick
(UUID_TO_BIN(UUID()), 'INJURED-PLAYER', "Your player was injured.", "Unfortunately, one of your players has went and gotten himself injured."), -- referenceId will be injury
(UUID_TO_BIN(UUID()), 'INACTIVE-PLAYER', "Your player is no longer available.", "Unfortunately, one of your players has retired, or was demoted, or something."), -- referenceId will be player
(UUID_TO_BIN(UUID()), 'INACTIVE-PLAYER', "Your player is no longer available.", "Unfortunately, one of your players has retired, or was demoted, or something."), -- referenceId will be player
(UUID_TO_BIN(UUID()), 'LEAGUE-REQUEST', "Someone wants to join your league.", "You probably shouldn't leave them hanging."), -- referenceId will be invitation
(UUID_TO_BIN(UUID()), 'LEAGUE-INVITE', "Someone wants you to join their league.", "You probably shouldn't leave them hanging."); -- referenceId will be invitation

39
data/player.sql Normal file
View File

@@ -0,0 +1,39 @@
CREATE TABLE IF NOT EXISTS `player` (
`id` BINARY(16) PRIMARY KEY,
`sourceId` INT(11) UNIQUE, -- player's id on fantasybaseballnerd.com
`name` VARCHAR(64) NOT NULL, -- this and below is data from fantasybaseballnerd.com
`positionId` BINARY(16) NOT NULL,
`team` VARCHAR(3),
`bats` VARCHAR(1),
`throws` VARCHAR(1),
`height` VARCHAR(5),
`weight` INT(3),
`jersey` INT(2),
`birthDate` DATE,
`active` BOOLEAN, -- when the player isn't returned by api anymore, set to true
`created` DATETIME,
`modified` DATETIME,
FOREIGN KEY(`positionId`) REFERENCES `position`(`id`)
);
DROP TRIGGER IF EXISTS `insertPlayerTrigger`;
DELIMITER $$
CREATE TRIGGER `insertPlayerTrigger` BEFORE INSERT ON `player`
FOR EACH ROW
BEGIN
SET NEW.`created` = IFNULL(NEW.`created`, NOW());
END$$
DELIMITER ;
DROP TRIGGER IF EXISTS `updatePlayerTrigger`;
DELIMITER $$
CREATE TRIGGER `updatePlayerTrigger` BEFORE UPDATE ON `player`
FOR EACH ROW
BEGIN
SET NEW.`created` = OLD.`created`;
SET NEW.`modified` = NOW();
END$$
DELIMITER ;

21
data/position.sql Normal file
View File

@@ -0,0 +1,21 @@
CREATE TABLE IF NOT EXISTS `position` (
`id` BINARY(16) PRIMARY KEY,
`code` VARCHAR(2) UNIQUE,
`name` VARCHAR(64) NOT NULL UNIQUE
);
INSERT IGNORE INTO `position` (`id`, `code`, `name`) VALUES
(UUID_TO_BIN(UUID()), 'DH', 'designated hitter'),
(UUID_TO_BIN(UUID()), 'C', 'catcher'),
(UUID_TO_BIN(UUID()), 'P', 'pitcher'),
(UUID_TO_BIN(UUID()), 'RP', 'relief pitcher'),
(UUID_TO_BIN(UUID()), 'SP', 'starting pitcher'),
(UUID_TO_BIN(UUID()), 'IF', 'in field'),
(UUID_TO_BIN(UUID()), '1B', 'first base'),
(UUID_TO_BIN(UUID()), '2B', 'second base'),
(UUID_TO_BIN(UUID()), 'SS', 'short stop'),
(UUID_TO_BIN(UUID()), '3B', 'third base'),
(UUID_TO_BIN(UUID()), 'OF', 'out field'),
(UUID_TO_BIN(UUID()), 'LF', 'left field'),
(UUID_TO_BIN(UUID()), 'RF', 'right field'),
(UUID_TO_BIN(UUID()), 'CF', 'center field');

11
data/role.sql Normal file
View File

@@ -0,0 +1,11 @@
CREATE TABLE IF NOT EXISTS `role` (
`id` BINARY(16) PRIMARY KEY,
`name` VARCHAR(64) NOT NULL UNIQUE
);
INSERT IGNORE INTO `role` (`id`, `name`) VALUES
(UUID_TO_BIN(UUID()), 'owner'),
(UUID_TO_BIN(UUID()), 'admin'),
(UUID_TO_BIN(UUID()), 'advanced'), -- someone who can manage their own stuff but no one else's.
(UUID_TO_BIN(UUID()), 'member'),
(UUID_TO_BIN(UUID()), 'spectator');

33
data/sso.sql Normal file
View File

@@ -0,0 +1,33 @@
CREATE TABLE IF NOT EXISTS `sso` (
`id` BINARY(16) PRIMARY KEY,
`memberId` BINARY(16) NOT NULL,
`token` VARCHAR(64) NOT NULL,
`ssoTypeId` BINARY(16) NOT NULL,
`created` DATETIME,
`modified` DATETIME,
UNIQUE (`token`, `ssoTypeId`),
FOREIGN KEY(`memberId`) REFERENCES `member`(`id`) ON DELETE CASCADE,
FOREIGN KEY(`ssoTypeId`) REFERENCES `ssoType`(`id`) ON DELETE CASCADE
);
DROP TRIGGER IF EXISTS `insertSsoTrigger`;
DELIMITER $$
CREATE TRIGGER `insertSsoTrigger` BEFORE INSERT ON `sso`
FOR EACH ROW
BEGIN
SET NEW.`created` = IFNULL(NEW.`created`, NOW());
END$$
DELIMITER ;
DROP TRIGGER IF EXISTS `updateSsoTrigger`;
DELIMITER $$
CREATE TRIGGER `updateSsoTrigger` BEFORE UPDATE ON `sso`
FOR EACH ROW
BEGIN
SET NEW.`created` = OLD.`created`;
SET NEW.`modified` = NOW();
END$$
DELIMITER ;

8
data/ssoType.sql Normal file
View File

@@ -0,0 +1,8 @@
CREATE TABLE IF NOT EXISTS `ssoType` (
`id` BINARY(16) PRIMARY KEY,
`source` VARCHAR(32) UNIQUE
);
INSERT IGNORE INTO `ssoType` (`id`, `source`) VALUES
(UUID_TO_BIN(UUID()), 'Google'),
(UUID_TO_BIN(UUID()), 'Facebook');

40
data/statistic.sql Normal file
View File

@@ -0,0 +1,40 @@
CREATE TABLE IF NOT EXISTS `statistic` (
`id` BINARY(16) PRIMARY KEY,
`playerId` BINARY(16) NOT NULL,
`rank` INT(11), -- this and below are from fantasybaseballnerd.com
`averageRank` REAL(3,3),
`atBats` INT(11),
`runs` INT(11),
`homeRuns` INT(11),
`runsBattedIn` INT(11),
`stolenBases` INT(11),
`battingAverage` REAL(3,3),
`onBasePlusSlugging` REAL(3,3),
`singles` INT(11),
`doubles` INT(11),
`triples` INT(11),
`created` DATETIME,
`modified` DATETIME,
FOREIGN KEY(`playerId`) REFERENCES `player`(`id`) ON DELETE CASCADE
);
DROP TRIGGER IF EXISTS `insertStatisticTrigger`;
DELIMITER $$
CREATE TRIGGER `insertStatisticTrigger` BEFORE INSERT ON `statistic`
FOR EACH ROW
BEGIN
SET NEW.`created` = IFNULL(NEW.`created`, NOW());
END$$
DELIMITER ;
DROP TRIGGER IF EXISTS `updateStatisticTrigger`;
DELIMITER $$
CREATE TRIGGER `updateStatisticTrigger` BEFORE UPDATE ON `statistic`
FOR EACH ROW
BEGIN
SET NEW.`created` = OLD.`created`;
SET NEW.`modified` = NOW();
END$$
DELIMITER ;

29
data/team.sql Normal file
View File

@@ -0,0 +1,29 @@
CREATE TABLE IF NOT EXISTS `team` (
`id` BINARY(16) PRIMARY KEY,
`name` VARCHAR(64) NOT NULL,
`leagueMemberId` BINARY(16) NOT NULL,
`created` DATETIME,
`modified` DATETIME,
FOREIGN KEY(`leagueMemberId`) REFERENCES `leagueMember`(`id`) ON DELETE CASCADE
);
DROP TRIGGER IF EXISTS `insertTeamTrigger`;
DELIMITER $$
CREATE TRIGGER `insertTeamTrigger` BEFORE INSERT ON `team`
FOR EACH ROW
BEGIN
SET NEW.`created` = IFNULL(NEW.`created`, NOW());
END$$
DELIMITER ;
DROP TRIGGER IF EXISTS `updateTeamTrigger`;
DELIMITER $$
CREATE TRIGGER `updateTeamTrigger` BEFORE UPDATE ON `team`
FOR EACH ROW
BEGIN
SET NEW.`created` = OLD.`created`;
SET NEW.`modified` = NOW();
END$$
DELIMITER ;

31
data/teamPlayer.sql Normal file
View File

@@ -0,0 +1,31 @@
CREATE TABLE IF NOT EXISTS `teamPlayer` (
`id` BINARY(16) PRIMARY KEY,
`teamId` BINARY(16) NOT NULL,
`playerId` BINARY(16) NOT NULL,
`created` DATETIME,
`modified` DATETIME,
UNIQUE (`teamId`, `playerId`),
FOREIGN KEY(`teamId`) REFERENCES `team`(`id`) ON DELETE CASCADE,
FOREIGN KEY(`playerId`) REFERENCES `player`(`id`) ON DELETE CASCADE
);
DROP TRIGGER IF EXISTS `insertTeamPlayerTrigger`;
DELIMITER $$
CREATE TRIGGER `insertTeamPlayerTrigger` BEFORE INSERT ON `teamPlayer`
FOR EACH ROW
BEGIN
SET NEW.`created` = IFNULL(NEW.`created`, NOW());
END$$
DELIMITER ;
DROP TRIGGER IF EXISTS `updateTeamPlayerTrigger`;
DELIMITER $$
CREATE TRIGGER `updateTeamPlayerTrigger` BEFORE UPDATE ON `teamPlayer`
FOR EACH ROW
BEGIN
SET NEW.`created` = OLD.`created`;
SET NEW.`modified` = NOW();
END$$
DELIMITER ;

32
data/trade.sql Normal file
View File

@@ -0,0 +1,32 @@
CREATE TABLE IF NOT EXISTS `trade` (
`id` BINARY(16) PRIMARY KEY,
`teamId` BINARY(16) NOT NULL,
`oldPlayerId` BINARY(16) NOT NULL,
`newPlayerId` BINARY(16) NOT NULL,
`created` DATETIME,
`modified` DATETIME,
FOREIGN KEY(`teamId`) REFERENCES `team`(`id`) ON DELETE CASCADE,
FOREIGN KEY(`oldPlayerId`) REFERENCES `player`(`id`) ON DELETE CASCADE,
FOREIGN KEY(`newPlayerId`) REFERENCES `player`(`id`) ON DELETE CASCADE
);
DROP TRIGGER IF EXISTS `insertTradeTrigger`;
DELIMITER $$
CREATE TRIGGER `insertTradeTrigger` BEFORE INSERT ON `trade`
FOR EACH ROW
BEGIN
SET NEW.`created` = IFNULL(NEW.`created`, NOW());
END$$
DELIMITER ;
DROP TRIGGER IF EXISTS `updateTradeTrigger`;
DELIMITER $$
CREATE TRIGGER `updateTradeTrigger` BEFORE UPDATE ON `trade`
FOR EACH ROW
BEGIN
SET NEW.`created` = OLD.`created`;
SET NEW.`modified` = NOW();
END$$
DELIMITER ;

31
data/user.sql Normal file
View File

@@ -0,0 +1,31 @@
CREATE TABLE IF NOT EXISTS `user` (
`id` BINARY(16) PRIMARY KEY,
`username` VARCHAR(254) NOT NULL UNIQUE,
`password` BINARY(60),
`memberId` BINARY(16) NOT NULL UNIQUE,
`verified` TINYINT(1), -- 0 when they first create the account, 1 once they verify the email address
`created` DATETIME,
`modified` DATETIME,
FOREIGN KEY(`memberId`) REFERENCES `member`(`id`) ON DELETE CASCADE
);
DROP TRIGGER IF EXISTS `insertUserTrigger`;
DELIMITER $$
CREATE TRIGGER `insertUserTrigger` BEFORE INSERT ON `user`
FOR EACH ROW
BEGIN
SET NEW.`created` = IFNULL(NEW.`created`, NOW());
END$$
DELIMITER ;
DROP TRIGGER IF EXISTS `updateUserTrigger`;
DELIMITER $$
CREATE TRIGGER `updateUserTrigger` BEFORE UPDATE ON `user`
FOR EACH ROW
BEGIN
SET NEW.`created` = OLD.`created`;
SET NEW.`modified` = NOW();
END$$
DELIMITER ;

20
docs/endpoints.txt Normal file
View File

@@ -0,0 +1,20 @@
account.create
league.create
league.invite
league.getMembers (incl role)
member.requestToJoin
member.getLeagues
leagueMember.getTeam
draft.pick
draft.getTurn
team.getPlayers (incl position, stats, injury)
notification.create
start with creating an account and logging in.

23
docs/notes.txt Normal file
View File

@@ -0,0 +1,23 @@
Will have to deal with when a player is no longer returned by fantasybaseballnerd, but is in use in peoples teams.
Some examples of when this would happen: a player retires, a player is demoted out of mlb.
Will have to deal with when a league owner deletes their account or league
what to do if any of the things are deleted that have a reference id in notification table
todo items (not a priority):
player photos
trading
auto score keeping
divisions (split league into different groups for places)
notifications that will exist:
your turn to draft
your player was injured
someone wants to join your league
someone invited you to join their league
your player is inactive
automated processes:
refreshing players and their stats, setting players inactive: notification
populating injuries: notification

12
docs/testing_setup.txt Normal file
View File

@@ -0,0 +1,12 @@
How to Setup the Testing Environment on Localhost
run MySQL Installer and configure server
go to last option and apply configuration. This will start the server.
Run SQLyog and connect to the localhost.
if object browser isnt showing up, hit Ctrl + B
open mysql command line client and run `use baseball; source path\to\_MASTER_.sql`
go get github.com/go-sql-driver/mysql
go get github.com/gorilla/mux
go get github.com/twinj/uuid