diff --git a/shows.go b/shows.go
index 0458630..349edb9 100644
--- a/shows.go
+++ b/shows.go
@@ -2,38 +2,63 @@ package shows
import (
"fmt"
- "time"
+
+ "gitlab.quimbo.fr/odwrtw/canape-sql/sqly"
+ "gitlab.quimbo.fr/odwrtw/canape-sql/users"
"github.com/jmoiron/sqlx"
"github.com/odwrtw/polochon/lib"
)
-const showsCreate = `
-CREATE TABLE shows (
- id SERIAL PRIMARY KEY,
- imdbid text NOT NULL UNIQUE,
- title text NOT NULL,
- updated timestamp DEFAULT current_timestamp,
- created timestamp DEFAULT current_timestamp
-);
-`
+var Schema = sqly.Schema{
+ Require: []sqly.Schema{
+ users.Schema,
+ },
+ Tables: []sqly.SchemaTable{
+ sqly.SchemaTable{
+ Name: "shows",
+ Sql: `
+ CREATE TABLE shows (
+ id SERIAL PRIMARY KEY,
+ imdbid text NOT NULL UNIQUE,
+ title text NOT NULL,
+ updated timestamp DEFAULT current_timestamp,
+ created timestamp DEFAULT current_timestamp
+ );
+ `},
+ sqly.SchemaTable{
+ Name: "episodes",
+ Sql: `
+ CREATE TABLE episodes (
+ id SERIAL PRIMARY KEY,
+ shows_id integer REFERENCES shows (id) ON DELETE CASCADE,
+ title text NOT NULL,
+ season integer NOT NULL,
+ episode integer NOT NULL,
+ updated timestamp DEFAULT current_timestamp,
+ created timestamp DEFAULT current_timestamp
+ );
+ `},
+ },
+ Drop: `
+ DROP TABLE episodes;
+ DROP TABLE shows;
+ `,
+}
const (
addShowQuery = `INSERT INTO shows (imdbid, title) VALUES (:imdbid, :title) RETURNING id;`
getShowQuery = `SELECT * FROM shows WHERE imdbid=$1;`
deleteShowQuery = `DELETE FROM shows WHERE id=$1;`
+
+ addEpisodeQuery = `INSERT INTO episodes (shows_id, title, season, episode) VALUES ($1,$2,$3,$4);`
+ getEpisodesQuery = `SELECT title, season, episode FROM episodes WHERE shows_id=$1;`
)
-// BaseTable have to be embeded in all your struct which reflect a table
-type BaseTable struct {
- Updated time.Time
- Created time.Time
-}
-
type Show struct {
- ID int
+ sqly.BaseTable
polochon.Show
- BaseTable
+ Episodes []*Episode
}
func Get(db *sqlx.DB, imdbID string) (*Show, error) {
@@ -55,6 +80,23 @@ func (s *Show) Add(db *sqlx.DB) error {
r.Scan(&id)
}
s.ID = id
+
+ // When add a show to database use polochon episode details
+ // so s.Show.Episodes
+ for _, pEp := range s.Show.Episodes {
+ err = s.addEpisode(db, pEp)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (s *Show) addEpisode(db *sqlx.DB, pEpisode *polochon.ShowEpisode) error {
+ _, err := db.Exec(addEpisodeQuery, s.ID, pEpisode.Title, pEpisode.Season, pEpisode.Episode)
+ if err != nil {
+ return err
+ }
return nil
}
@@ -69,3 +111,19 @@ func (s *Show) Delete(db *sqlx.DB) error {
}
return nil
}
+
+func (s *Show) GetEpisodes(db *sqlx.DB) error {
+ // When retrive episode's info from database populate the s.Episodes member
+ // and not s.Show.Episodes
+ err := db.Select(&s.Episodes, getEpisodesQuery, s.ID)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+type Episode struct {
+ sqly.BaseTable
+ polochon.ShowEpisode
+}
diff --git a/shows_test.go b/shows_test.go
index f6b59b9..fe6816d 100644
--- a/shows_test.go
+++ b/shows_test.go
@@ -1,22 +1,19 @@
package shows
import (
+ "database/sql"
"fmt"
"os"
"strings"
"testing"
- "gitlab.quimbo.fr/odwrtw/canape-sql/sqltest"
+ "gitlab.quimbo.fr/odwrtw/canape-sql/sqly"
"github.com/jmoiron/sqlx"
_ "github.com/lib/pq"
"github.com/odwrtw/polochon/lib"
)
-const drop = `
-DROP TABLE shows;
-`
-
const showNFO1 = `
Marvel's Jessica Jones
@@ -32,6 +29,43 @@ const showNFO1 = `
`
+const showNFO1E1 = `
+
+ AKA Ladies Night
+ Marvel's Jessica Jones
+ 1
+ 1
+ 5311261
+ 2015-11-20
+ 2015-11-20
+ Jessica Jones is hired to find a pretty NYU student who's vanished, but it turns out to be more than a simple missing persons case.
+ 60
+ http://thetvdb.com/banners/episodes/284190/5311261.jpg
+ 7.5
+ tt2357547
+ 284190
+ tt4162058
+
+`
+const showNFO1E2 = `
+
+ AKA Crush Syndrome
+ Marvel's Jessica Jones
+ 1
+ 2
+ 5311262
+ 2015-11-20
+ 2015-11-20
+ Jessica vows to prove Hope's innocence, even though it means tracking down a terrifying figure from her own past.
+ 60
+ http://thetvdb.com/banners/episodes/284190/5311262.jpg
+ 7.7
+ tt2357547
+ 284190
+ tt4162062
+
+`
+
var db *sqlx.DB
func init() {
@@ -44,20 +78,34 @@ func init() {
fmt.Printf("Unavailable PG tests:\n %v\n", err)
os.Exit(1)
}
+
+ err = sqly.InitDB(db)
+ if err != nil {
+ fmt.Printf("Unavailable PG tests:\n %v\n", err)
+ os.Exit(1)
+ }
}
func TestAddRemoveShow(t *testing.T) {
- sqltest.RunWithSchema(db, showsCreate, drop, t, func(db *sqlx.DB, t *testing.T) {
+ sqly.RunWithSchema(db, Schema, t, func(db *sqlx.DB, t *testing.T) {
nfo := strings.NewReader(showNFO1)
s := &polochon.Show{}
+ polochon.ReadNFO(nfo, s)
+
+ nfo = strings.NewReader(showNFO1E1)
+ ep1 := &polochon.ShowEpisode{}
+ polochon.ReadNFO(nfo, ep1)
+
+ nfo = strings.NewReader(showNFO1E2)
+ ep2 := &polochon.ShowEpisode{}
+ polochon.ReadNFO(nfo, ep2)
+
+ s.Episodes = append(s.Episodes, ep1)
+ s.Episodes = append(s.Episodes, ep2)
- err := polochon.ReadNFO(nfo, s)
- if err != nil {
- t.Fatal(err)
- }
show := Show{Show: *s}
- err = show.Add(db)
+ err := show.Add(db)
if err != nil {
t.Fatal(err)
}
@@ -67,6 +115,14 @@ func TestAddRemoveShow(t *testing.T) {
t.Fatal(err)
}
+ err = show1.GetEpisodes(db)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(show1.Episodes) != 2 {
+ t.Fatalf("Unexpected number of episodes: %d", len(show1.Episodes))
+ }
+
err = show1.Delete(db)
if err != nil {
t.Fatal(err)
@@ -76,7 +132,7 @@ func TestAddRemoveShow(t *testing.T) {
if err == nil {
t.Fatal("We expect an error here, the show didn't exist anymore")
}
- if err.Error() != "sql: no rows in result set" {
+ if err != sql.ErrNoRows {
t.Fatalf("Unexpected error: %q", err)
}
if show2 != nil {
diff --git a/sqly/sqly.go b/sqly/sqly.go
new file mode 100644
index 0000000..35eb820
--- /dev/null
+++ b/sqly/sqly.go
@@ -0,0 +1,115 @@
+package sqly
+
+import (
+ "fmt"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/jmoiron/sqlx"
+)
+
+const initDBQuery = `
+CREATE OR REPLACE FUNCTION update_modified_column()
+RETURNS TRIGGER AS $$
+BEGIN NEW.updated = now(); RETURN NEW; END; $$ language 'plpgsql';
+`
+
+// InitDB add some function to the database and template
+func InitDB(db *sqlx.DB) error {
+ err := MultiExec(db, initDBQuery)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// BaseTable have to be embeded in all your struct which reflect a table
+type BaseTable struct {
+ ID int
+ Updated time.Time
+ Created time.Time
+}
+
+type SchemaTable struct {
+ Name string
+ Sql string
+}
+
+type Schema struct {
+ // Create contains all tables, the key is the name and the
+ // value the sql
+ Tables []SchemaTable
+
+ // Drop contains the drop sql
+ Drop string
+
+ // Require add schema before create and delete after
+ Require []Schema
+}
+
+func (s Schema) Create(db *sqlx.DB) error {
+ for _, sch := range s.Require {
+ err := sch.Create(db)
+ if err != nil {
+ return err
+ }
+ }
+
+ for _, table := range s.Tables {
+ _, err := db.Exec(table.Sql)
+ if err != nil {
+ return fmt.Errorf("%s\n%s", err, table.Sql)
+ }
+ trigger := fmt.Sprintf("CREATE TRIGGER update_%s BEFORE UPDATE ON %s FOR EACH ROW EXECUTE PROCEDURE update_modified_column();", table.Name, table.Name)
+ _, err = db.Exec(trigger)
+ if err != nil {
+ return fmt.Errorf("%s\n%s", err, trigger)
+ }
+ }
+ return nil
+}
+
+func (s Schema) Delete(db *sqlx.DB) error {
+ for _, sch := range s.Require {
+ err := sch.Delete(db)
+ if err != nil {
+ return err
+ }
+ }
+ err := MultiExec(db, s.Drop)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func MultiExec(e sqlx.Execer, query string) error {
+ stmts := strings.Split(query, ";\n")
+ if len(strings.Trim(stmts[len(stmts)-1], " \n\t\r")) == 0 {
+ stmts = stmts[:len(stmts)-1]
+ }
+ for _, s := range stmts {
+ _, err := e.Exec(s)
+ if err != nil {
+ return fmt.Errorf("%s\n%s", err, s)
+ }
+ }
+ return nil
+}
+
+func RunWithSchema(db *sqlx.DB, schema Schema, t *testing.T, test func(db *sqlx.DB, t *testing.T)) {
+ defer func() {
+ err := schema.Delete(db)
+ if err != nil {
+ t.Fatalf("%s", err.Error())
+ }
+ }()
+
+ err := schema.Create(db)
+ if err != nil {
+ t.Fatalf("%s", err.Error())
+ }
+
+ test(db, t)
+}
diff --git a/users/users.go b/users/users.go
index 5b15b0e..4673480 100644
--- a/users/users.go
+++ b/users/users.go
@@ -2,35 +2,41 @@ package users
import (
"fmt"
- "time"
"github.com/jmoiron/sqlx"
"gitlab.quimbo.fr/odwrtw/canape-sql/random"
+ "gitlab.quimbo.fr/odwrtw/canape-sql/sqly"
)
-const usersCreate = `
-CREATE TABLE users (
- id SERIAL PRIMARY KEY,
- name text NOT NULL UNIQUE,
- updated timestamp DEFAULT current_timestamp,
- created timestamp DEFAULT current_timestamp
-);
+var Schema = sqly.Schema{
+ Tables: []sqly.SchemaTable{
+ sqly.SchemaTable{
+ Name: "users",
+ Sql: `
+ CREATE TABLE users (
+ id SERIAL PRIMARY KEY,
+ name text NOT NULL UNIQUE,
+ updated timestamp DEFAULT current_timestamp,
+ created timestamp DEFAULT current_timestamp
+ );`},
+ sqly.SchemaTable{
+ Name: "tokens",
+ Sql: `
+ CREATE TABLE tokens (
+ id SERIAL,
+ value text NOT NULL UNIQUE,
+ users_id integer REFERENCES users (id) ON DELETE CASCADE,
+ updated timestamp DEFAULT current_timestamp,
+ created timestamp DEFAULT current_timestamp
+ );
+ `},
+ },
+ Drop: `
+ DROP TABLE tokens;
+ DROP TABLE users;
+ `,
+}
-CREATE TABLE tokens (
- id SERIAL,
- value text NOT NULL UNIQUE,
- users_id integer REFERENCES users (id) ON DELETE CASCADE,
- updated timestamp DEFAULT current_timestamp,
- created timestamp DEFAULT current_timestamp
-);
-
-CREATE OR REPLACE FUNCTION update_modified_column()
-RETURNS TRIGGER AS $$
-BEGIN NEW.updated = now(); RETURN NEW; END; $$ language 'plpgsql';
-
-CREATE TRIGGER update_users BEFORE UPDATE ON users FOR EACH ROW EXECUTE PROCEDURE update_modified_column();
-CREATE TRIGGER update_tokens BEFORE UPDATE ON tokens FOR EACH ROW EXECUTE PROCEDURE update_modified_column();
-`
const (
addUserQuery = `INSERT INTO users (name) VALUES ($1) RETURNING id;`
getUserQuery = `SELECT * FROM users WHERE name=$1;`
@@ -43,23 +49,15 @@ const (
deleteTokenQuery = `DELETE FROM tokens WHERE users_id=$1 AND value=$2;`
)
-// BaseTable have to be embeded in all your struct which reflect a table
-type BaseTable struct {
- Updated time.Time
- Created time.Time
-}
-
// User represents an user
type User struct {
- BaseTable
- ID int
+ sqly.BaseTable
Name string
}
// Token represents a token
type Token struct {
- BaseTable
- ID int
+ sqly.BaseTable
Value string
}
diff --git a/users/users_test.go b/users/users_test.go
index 18fd064..49b20c4 100644
--- a/users/users_test.go
+++ b/users/users_test.go
@@ -5,19 +5,12 @@ import (
"os"
"testing"
- "gitlab.quimbo.fr/odwrtw/canape-sql/sqltest"
+ "gitlab.quimbo.fr/odwrtw/canape-sql/sqly"
"github.com/jmoiron/sqlx"
"github.com/lib/pq"
)
-const drop = `
-DROP TABLE tokens;
-DROP TABLE users;
-
-DROP FUNCTION update_modified_column();
-`
-
var db *sqlx.DB
func init() {
@@ -30,10 +23,16 @@ func init() {
fmt.Printf("Unavailable PG tests:\n %v\n", err)
os.Exit(1)
}
+
+ err = sqly.InitDB(db)
+ if err != nil {
+ fmt.Printf("Unavailable PG tests:\n %v\n", err)
+ os.Exit(1)
+ }
}
func TestUser(t *testing.T) {
- sqltest.RunWithSchema(db, usersCreate, drop, t, func(db *sqlx.DB, t *testing.T) {
+ sqly.RunWithSchema(db, Schema, t, func(db *sqlx.DB, t *testing.T) {
// Add a new user
u := &User{Name: "plop"}
@@ -101,7 +100,7 @@ func TestUser(t *testing.T) {
}
func TestTokenAddDelete(t *testing.T) {
- sqltest.RunWithSchema(db, usersCreate, drop, t, func(db *sqlx.DB, t *testing.T) {
+ sqly.RunWithSchema(db, Schema, t, func(db *sqlx.DB, t *testing.T) {
// Add a new user
u := &User{Name: "plop"}
err := u.Add(db)
@@ -161,7 +160,7 @@ func TestTokenAddDelete(t *testing.T) {
}
func TestTokenCheck(t *testing.T) {
- sqltest.RunWithSchema(db, usersCreate, drop, t, func(db *sqlx.DB, t *testing.T) {
+ sqly.RunWithSchema(db, Schema, t, func(db *sqlx.DB, t *testing.T) {
u := &User{Name: "plop"}
u.Add(db)
token, err := u.NewToken(db)
@@ -187,7 +186,7 @@ func TestTokenCheck(t *testing.T) {
}
func TestAutoUpdateCols(t *testing.T) {
- sqltest.RunWithSchema(db, usersCreate, drop, t, func(db *sqlx.DB, t *testing.T) {
+ sqly.RunWithSchema(db, Schema, t, func(db *sqlx.DB, t *testing.T) {
u := &User{Name: "plop"}
u.Add(db)
u.Name = "toto"