diff --git a/backend/entities/query.go b/backend/entities/query.go new file mode 100644 index 0000000..bca3ca2 --- /dev/null +++ b/backend/entities/query.go @@ -0,0 +1,9 @@ +package entities + +type Query struct { + ID string `json:"id,omitempty"` + Content string `json:"content,omitempty"` + OwnerID string `json:"owner_id,omitempty"` + Active bool `json:"active,omitempty"` + Description string `json:"description,omitempty"` +} diff --git a/backend/repos/query.go b/backend/repos/query.go new file mode 100644 index 0000000..2723663 --- /dev/null +++ b/backend/repos/query.go @@ -0,0 +1,142 @@ +package repos + +import ( + "errors" + "fmt" + "strings" + + "github.com/google/uuid" + "github.com/honeynet/ochi/backend/entities" + + "github.com/jmoiron/sqlx" + "github.com/jmoiron/sqlx/reflectx" + _ "github.com/mattn/go-sqlite3" +) + +type QueryRepo struct { + findByOwnerQuery *sqlx.Stmt + deleteQuery *sqlx.Stmt + createQuery *sqlx.NamedStmt + updateQuery *sqlx.Stmt + getByIdQuery *sqlx.Stmt + db *sqlx.DB +} + +func NewQueryRepo(db *sqlx.DB) (*QueryRepo, error) { + r := &QueryRepo{} + db.Mapper = reflectx.NewMapperFunc("json", strings.ToLower) + _, err := db.Exec(`CREATE TABLE IF NOT EXISTS queries ( + id TEXT PRIMARY KEY NOT NULL + , content TEXT NOT NULL + , owner_id TEXT NOT NULL + , active INTEGER NOT NULL + , description TEXT NOT NULL + , CONSTRAINT id_unique UNIQUE (id) + )`) + if err != nil { + return nil, err + } + r.findByOwnerQuery, err = db.Preparex("SELECT * FROM queries WHERE owner_id=?") + if err != nil { + return nil, err + } + r.createQuery, err = db.PrepareNamed("INSERT INTO queries (id, owner_id, content, active, description) VALUES (:id, :owner_id, :content, :active, :description)") + if err != nil { + return nil, err + } + r.updateQuery, err = db.Preparex("UPDATE queries SET content=?, active=?, description=? WHERE id=?") + if err != nil { + return nil, err + } + r.getByIdQuery, err = db.Preparex("SELECT * FROM queries WHERE id=?") + if err != nil { + return nil, err + } + r.deleteQuery, err = db.Preparex("DELETE FROM queries WHERE id=?") + if err != nil { + return nil, err + } + return r, nil +} + +// Get a user +func (r *QueryRepo) FindByOwnerId(ownerId string) ([]entities.Query, error) { + qs := []entities.Query{} + err := r.findByOwnerQuery.Select(&qs, ownerId) + return qs, err +} + +// Create a query +func (r *QueryRepo) Create(owner_id, content, description string, active bool) (entities.Query, error) { + id, err := uuid.NewRandom() + if err != nil { + return entities.Query{}, err + } + q := entities.Query{ID: id.String(), Content: content, OwnerID: owner_id, Active: active, Description: description} + _, err = r.createQuery.Exec(&q) + if err != nil { + return entities.Query{}, err + } + return q, nil +} + +// Update a query +func (r *QueryRepo) Update(id, content, description string, active bool) error { + res, err := r.updateQuery.Exec(content, active, description, id) + if err != nil { + return err + } + if cnt, err := res.RowsAffected(); err != nil { + return err + } else if cnt == 0 { + return errors.New(fmt.Sprintf("%s not found", id)) + } + return nil +} + +// GetByID +func (r *QueryRepo) GetByID(id string) (entities.Query, error) { + q := entities.Query{} + err := r.getByIdQuery.Get(&q, id) + return q, err +} + +// Delete a query +func (r *QueryRepo) Delete(id string) error { + res, err := r.deleteQuery.Exec(id) + if err != nil { + return err + } + if cnt, err := res.RowsAffected(); err != nil { + return err + } else if cnt == 0 { + return errors.New(fmt.Sprintf("%s not found", id)) + } + return nil +} + +// // Find a user +// func (r *QueryRepo) Find(email string) (entities.User, error) { +// u := entities.User{Email: email} +// err := r.readEmail.Get(&u, email) +// if err == sql.ErrNoRows { +// var id uuid.UUID +// id, err = uuid.NewRandom() +// if err != nil { +// return u, err +// } +// u.ID = id.String() +// _, err = r.create.Exec(&u) +// if err != nil { +// return u, err +// } +// } +// return u, err +// } + +// Close the db +func (r *QueryRepo) Close() { + if r.db != nil { + r.db.Close() + } +} diff --git a/backend/repos/query_test.go b/backend/repos/query_test.go new file mode 100644 index 0000000..15cffbc --- /dev/null +++ b/backend/repos/query_test.go @@ -0,0 +1,150 @@ +package repos + +import ( + "fmt" + "testing" + + "github.com/jmoiron/sqlx" + "github.com/stretchr/testify/require" +) + +func TestQuery(t *testing.T) { + r := initRepo(t) + u1, err := r.FindByOwnerId("test@test") + require.NoError(t, err) + require.Empty(t, u1) + + q, err := r.Create("123", "hello there", "this is desription", true) + require.NoError(t, err) + require.NotEmpty(t, q.ID) + + u1, err = r.FindByOwnerId("123") + require.NoError(t, err) + require.Equal(t, len(u1), 1) + + require.Equal(t, u1[0], q) + + err = r.Delete("Not found") + require.Error(t, err) + + err = r.Delete(q.ID) + require.NoError(t, err) + + u1, err = r.FindByOwnerId("123") + require.NoError(t, err) + require.Empty(t, u1) +} + +func TestGetById_Missing(t *testing.T) { + r := initRepo(t) + + _, err := r.GetByID("non-existing") + require.ErrorContains(t, err, "sql: no rows in result set") +} + +func TestGetById_Existing(t *testing.T) { + r := initRepo(t) + q, err := r.Create("001", "content", "description", true) + require.NoError(t, err) + require.Equal(t, "001", q.OwnerID) + require.Equal(t, "content", q.Content) + require.Equal(t, "description", q.Description) + + q1, err := r.GetByID(q.ID) + require.NoError(t, err) + require.Equal(t, q, q1) +} + +func TestFindByOwnerId_Missing(t *testing.T) { + r := initRepo(t) + + qs, err := r.FindByOwnerId("non-existing") + require.NoError(t, err) + require.Empty(t, qs) +} + +func TestFindByOwnerId_Existing(t *testing.T) { + r := initRepo(t) + + q, err := r.Create("001", "content", "description", true) + require.NoError(t, err) + require.Equal(t, "001", q.OwnerID) + require.Equal(t, "content", q.Content) + require.Equal(t, "description", q.Description) + + q1, err := r.Create("001", "content1", "description1", true) + require.NoError(t, err) + require.Equal(t, "001", q1.OwnerID) + require.Equal(t, "content1", q1.Content) + require.Equal(t, "description1", q1.Description) + + q2, err := r.Create("001", "content2", "description2", true) + require.NoError(t, err) + require.Equal(t, "001", q2.OwnerID) + require.Equal(t, "content2", q2.Content) + require.Equal(t, "description2", q2.Description) + + qs, err := r.FindByOwnerId("001") + require.NoError(t, err) + require.Contains(t, qs, q) + require.Contains(t, qs, q1) + require.Contains(t, qs, q2) +} + +func TestDeleteQuery_Missing(t *testing.T) { + r := initRepo(t) + + err := r.Delete("non-existing") + require.ErrorContains(t, err, "non-existing not found") +} + +func TestDeleteQuery_Existing(t *testing.T) { + r := initRepo(t) + + q, err := r.Create("001", "content", "description", true) + require.NoError(t, err) + + err = r.Delete(q.ID) + require.NoError(t, err) + + err = r.Delete(q.ID) + require.Error(t, err) + + _, err = r.GetByID(q.ID) + require.Error(t, err) +} + +func TestUpdateQuery_Missing(t *testing.T) { + r := initRepo(t) + + err := r.Update("non-existing", "content", "description", true) + require.ErrorContains(t, err, "non-existing not found") +} + +func TestUpdateQuery_Existing(t *testing.T) { + r := initRepo(t) + + q, err := r.Create("001", "content", "description", true) + require.NoError(t, err) + + err = r.Update(q.ID, "content-new", "description-new", false) + require.NoError(t, err) + + q1, err := r.GetByID(q.ID) + require.Equal(t, "content-new", q1.Content) + require.Equal(t, "description-new", q1.Description) + require.Equal(t, false, q1.Active) +} + +func initRepo(t *testing.T) *QueryRepo { + tmp := t.TempDir() + dbPath := fmt.Sprintf("%s/test.db", tmp) + db, err := sqlx.Connect("sqlite3", dbPath) + require.NoError(t, err) + + // defer os.Remove("./querytest.db") + r, err := NewQueryRepo(db) + require.NoError(t, err) + require.NotNil(t, r) + return r +}