diff --git a/core/api/org.go b/core/api/org.go index bea5a4b..74c3c08 100644 --- a/core/api/org.go +++ b/core/api/org.go @@ -22,6 +22,7 @@ import ( * @apiSuccess {String} name Name of the Org. * @apiSuccess {String} currency Three letter currency code. * @apiSuccess {Number} precision How many digits the currency goes out to. + * @apiSuccess {String} timezone Timezone to use for accounting. * * @apiSuccessExample Success-Response: * HTTP/1.1 200 OK @@ -32,6 +33,7 @@ import ( * "name": "MyOrg", * "currency": "USD", * "precision": 2, + * "timezone": "America/New_York" * } * * @apiUse NotAuthorizedError @@ -66,6 +68,7 @@ func GetOrg(w rest.ResponseWriter, r *rest.Request) { * @apiSuccess {String} name Name of the Org. * @apiSuccess {String} currency Three letter currency code. * @apiSuccess {Number} precision How many digits the currency goes out to. + @apiSuccess {String} timezone Timezone to use for accounting. * * @apiSuccessExample Success-Response: * HTTP/1.1 200 OK @@ -77,6 +80,7 @@ func GetOrg(w rest.ResponseWriter, r *rest.Request) { * "name": "MyOrg", * "currency": "USD", * "precision": 2, + * "timezone": "America/New_York" * } * ] * @@ -109,6 +113,7 @@ func GetOrgs(w rest.ResponseWriter, r *rest.Request) { * @apiParam {String} name Name of the Org. * @apiParam {String} currency Three letter currency code. * @apiParam {Number} precision How many digits the currency goes out to. + * @apiParam {String} timezone Timezone to use for accounting. * * @apiSuccess {String} id Id of the Org. * @apiSuccess {Date} inserted Date Org was created @@ -116,6 +121,7 @@ func GetOrgs(w rest.ResponseWriter, r *rest.Request) { * @apiSuccess {String} name Name of the Org. * @apiSuccess {String} currency Three letter currency code. * @apiSuccess {Number} precision How many digits the currency goes out to. + * @apiSuccess {String} timezone Timezone to use for accounting. * * @apiSuccessExample Success-Response: * HTTP/1.1 200 OK @@ -126,6 +132,7 @@ func GetOrgs(w rest.ResponseWriter, r *rest.Request) { * "name": "MyOrg", * "currency": "USD", * "precision": 2, + * "timezone": "America/New_York" * } * * @apiUse NotAuthorizedError @@ -167,6 +174,7 @@ func PostOrg(w rest.ResponseWriter, r *rest.Request) { * @apiSuccess {String} name Name of the Org. * @apiSuccess {String} currency Three letter currency code. * @apiSuccess {Number} precision How many digits the currency goes out to. + * @apiSuccess {String} timezone Timezone to use for accounting. * * @apiSuccessExample Success-Response: * HTTP/1.1 200 OK @@ -177,6 +185,7 @@ func PostOrg(w rest.ResponseWriter, r *rest.Request) { * "name": "MyOrg", * "currency": "USD", * "precision": 2, + * "timezone": "America/New_York" * } * * @apiUse NotAuthorizedError diff --git a/core/model/db/org.go b/core/model/db/org.go index 1706007..ae439f7 100644 --- a/core/model/db/org.go +++ b/core/model/db/org.go @@ -21,7 +21,7 @@ type OrgInterface interface { DeleteInvite(string) error } -const orgFields = "LOWER(HEX(o.id)),o.inserted,o.updated,o.name,o.currency,o.`precision`" +const orgFields = "LOWER(HEX(o.id)),o.inserted,o.updated,o.name,o.currency,o.`precision`,o.timezone" const inviteFields = "i.id,LOWER(HEX(i.orgId)),i.inserted,i.updated,i.email,i.accepted" func (db *DB) CreateOrg(org *types.Org, userId string, accounts []*types.Account) (err error) { @@ -46,7 +46,7 @@ func (db *DB) CreateOrg(org *types.Org, userId string, accounts []*types.Account org.Updated = org.Inserted // create org - query1 := "INSERT INTO org(id,inserted,updated,name,currency,`precision`) VALUES(UNHEX(?),?,?,?,?,?)" + query1 := "INSERT INTO org(id,inserted,updated,name,currency,`precision`,timezone) VALUES(UNHEX(?),?,?,?,?,?,?)" res, err := tx.Exec( query1, @@ -56,6 +56,7 @@ func (db *DB) CreateOrg(org *types.Org, userId string, accounts []*types.Account org.Name, org.Currency, org.Precision, + org.Timezone, ) if err != nil { @@ -126,11 +127,12 @@ func (db *DB) CreateOrg(org *types.Org, userId string, accounts []*types.Account func (db *DB) UpdateOrg(org *types.Org) error { org.Updated = time.Now() - query := "UPDATE org SET updated = ?, name = ? WHERE id = UNHEX(?)" + query := "UPDATE org SET updated = ?, name = ?, timezone = ? WHERE id = UNHEX(?)" _, err := db.Exec( query, util.TimeToMs(org.Updated), org.Name, + org.Timezone, org.Id, ) @@ -143,7 +145,7 @@ func (db *DB) GetOrg(orgId string, userId string) (*types.Org, error) { var updated int64 err := db.QueryRow("SELECT "+orgFields+" FROM org o JOIN userorg ON userorg.orgId = o.id WHERE o.id = UNHEX(?) AND userorg.userId = UNHEX(?)", orgId, userId). - Scan(&o.Id, &inserted, &updated, &o.Name, &o.Currency, &o.Precision) + Scan(&o.Id, &inserted, &updated, &o.Name, &o.Currency, &o.Precision, &o.Timezone) switch { case err == sql.ErrNoRows: @@ -173,7 +175,7 @@ func (db *DB) GetOrgs(userId string) ([]*types.Org, error) { var inserted int64 var updated int64 - err = rows.Scan(&o.Id, &inserted, &updated, &o.Name, &o.Currency, &o.Precision) + err = rows.Scan(&o.Id, &inserted, &updated, &o.Name, &o.Currency, &o.Precision, &o.Timezone) if err != nil { return nil, err } diff --git a/core/model/types/org.go b/core/model/types/org.go index 124bab5..34452cb 100644 --- a/core/model/types/org.go +++ b/core/model/types/org.go @@ -11,4 +11,5 @@ type Org struct { Name string `json:"name"` Currency string `json:"currency"` Precision int `json:"precision"` + Timezone string `json:"timezone"` } diff --git a/migrations/migrate2.go b/migrations/migrate2.go new file mode 100644 index 0000000..87f28d9 --- /dev/null +++ b/migrations/migrate2.go @@ -0,0 +1,105 @@ +package main + +import ( + "encoding/json" + "github.com/openaccounting/oa-server/core/model/db" + "github.com/openaccounting/oa-server/core/model/types" + "log" + "os" +) + +func main() { + if len(os.Args) != 2 { + log.Fatal("Usage: migrate2.go ") + } + + command := os.Args[1] + + if command != "upgrade" && command != "downgrade" { + log.Fatal("Usage: migrate2.go ") + } + + //filename is the path to the json config file + var config types.Config + file, err := os.Open("./config.json") + + if err != nil { + log.Fatal(err) + } + + decoder := json.NewDecoder(file) + err = decoder.Decode(&config) + + if err != nil { + log.Fatal(err) + } + + connectionString := config.User + ":" + config.Password + "@/" + config.Database + db, err := db.NewDB(connectionString) + + if command == "upgrade" { + err = upgrade(db) + } else { + err = downgrade(db) + } + + if err != nil { + log.Fatal(err) + } + + log.Println("done") +} + +func upgrade(db *db.DB) (err error) { + tx, err := db.Begin() + + if err != nil { + return + } + + defer func() { + if p := recover(); p != nil { + tx.Rollback() + panic(p) // re-throw panic after Rollback + } else if err != nil { + tx.Rollback() + } else { + err = tx.Commit() + } + }() + + query1 := "ALTER TABLE org ADD COLUMN timezone VARCHAR(100) NOT NULL AFTER `precision`" + + if _, err = tx.Exec(query1); err != nil { + return + } + + return +} + +func downgrade(db *db.DB) (err error) { + tx, err := db.Begin() + + if err != nil { + return + } + + defer func() { + if p := recover(); p != nil { + tx.Rollback() + panic(p) // re-throw panic after Rollback + } else if err != nil { + tx.Rollback() + } else { + err = tx.Commit() + } + }() + + query1 := "ALTER TABLE org DROP COLUMN timezone" + + if _, err = tx.Exec(query1); err != nil { + return + } + + return +} diff --git a/schema.sql b/schema.sql index 998beea..36aa3a4 100644 --- a/schema.sql +++ b/schema.sql @@ -4,7 +4,7 @@ CREATE DATABASE openaccounting CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; use openaccounting; -CREATE TABLE org (id BINARY(16) NOT NULL, inserted BIGINT UNSIGNED NOT NULL, updated BIGINT UNSIGNED NOT NULL, name VARCHAR(100) NOT NULL, currency VARCHAR(10) NOT NULL, `precision` INT NOT NULL, PRIMARY KEY(id)) ENGINE=InnoDB; +CREATE TABLE org (id BINARY(16) NOT NULL, inserted BIGINT UNSIGNED NOT NULL, updated BIGINT UNSIGNED NOT NULL, name VARCHAR(100) NOT NULL, currency VARCHAR(10) NOT NULL, `precision` INT NOT NULL, timezone VARCHAR(100) NOT NULL, PRIMARY KEY(id)) ENGINE=InnoDB; CREATE TABLE user (id BINARY(16) NOT NULL, inserted BIGINT UNSIGNED NOT NULL, updated BIGINT UNSIGNED NOT NULL, firstName VARCHAR(50) NOT NULL, lastName VARCHAR(50) NOT NULL, email VARCHAR(100) NOT NULL, passwordHash VARCHAR(100) NOT NULL, agreeToTerms BOOLEAN NOT NULL, passwordReset VARCHAR(32) NOT NULL, emailVerified BOOLEAN NOT NULL, emailVerifyCode VARCHAR(32) NOT NULL, signupSource VARCHAR(100) NOT NULL, UNIQUE(email), PRIMARY KEY(id)) ENGINE=InnoDB;