diff --git a/cmd/sheltertech-go/bookmarks_integration_test.go b/cmd/sheltertech-go/bookmarks_integration_test.go new file mode 100644 index 00000000..69258cc9 --- /dev/null +++ b/cmd/sheltertech-go/bookmarks_integration_test.go @@ -0,0 +1,81 @@ +//go:build integration +// +build integration + +// go test -tags=integration + +package main + +import ( + "encoding/json" + "io/ioutil" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/sheltertechsf/sheltertech-go/internal/common" +) + +func init() { + setIntegrationTestEnv() + go main() +} + +const bookmarkUrl = "http://localhost:3001/api/bookmarks" + +func TestGetBookmarksBadRequest(t *testing.T) { + res, err := http.Get(bookmarkUrl + "?user_id=a") + require.NoError(t, err) + defer res.Body.Close() + + body, err := ioutil.ReadAll(res.Body) + require.NoError(t, err) + + serviceResponse := new(common.Error) + err = json.Unmarshal(body, serviceResponse) + require.NoError(t, err) + + assert.Equal(t, serviceResponse.StatusCode, http.StatusBadRequest, "Response contains a 400") + assert.NotEmpty(t, serviceResponse.Error, "Response has some error message") +} + +func TestGetBookmarkByIDError(t *testing.T) { + res, err := http.Get(bookmarkUrl + "/0") + require.NoError(t, err) + defer res.Body.Close() + + body, err := ioutil.ReadAll(res.Body) + require.NoError(t, err) + + serviceResponse := new(common.Error) + err = json.Unmarshal(body, serviceResponse) + require.NoError(t, err) + + assert.Equal(t, serviceResponse.StatusCode, http.StatusInternalServerError, "Response contains a 400") + assert.Equal(t, serviceResponse.Error, common.InternalServerErrorMessage) +} + +func TestUnauthorized(t *testing.T) { + t.Skip("skipping until this is supported in dev") + + res, err := http.Get(bookmarkUrl) + require.NoError(t, err) + defer res.Body.Close() + + require.Equal(t, http.StatusUnauthorized, res.StatusCode) +} + +func TestAuthorized(t *testing.T) { + t.Skip("skipping until this is supported in dev") + + req, err := http.NewRequest(http.MethodGet, bookmarkUrl, nil) + require.NoError(t, err) + req.Header.Set("Authorization", "Bearer k") + + res, err := http.DefaultClient.Do(req) + require.NoError(t, err) + defer res.Body.Close() + + require.Equal(t, http.StatusOK, res.StatusCode) +} diff --git a/cmd/sheltertech-go/categories_integration_test.go b/cmd/sheltertech-go/categories_integration_test.go new file mode 100644 index 00000000..c8f6e8ea --- /dev/null +++ b/cmd/sheltertech-go/categories_integration_test.go @@ -0,0 +1,92 @@ +//go:build integration +// +build integration + +// go test -tags=integration + +package main + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/sheltertechsf/sheltertech-go/internal/categories" +) + +func init() { + setIntegrationTestEnv() + go main() +} + +const categoryUrl = "http://localhost:3001/api/categories" + +func TestGetCategoriesFeatured(t *testing.T) { + url := categoryUrl + "/featured" + + req, err := http.NewRequest("GET", url, nil) + require.NoError(t, err) + + res, err := http.DefaultClient.Do(req) + require.NoError(t, err) + defer res.Body.Close() + body, _ := ioutil.ReadAll(res.Body) + + categoriesResponse := new(categories.Categories) + + err = json.Unmarshal(body, categoriesResponse) + require.NoError(t, err) + + assert.True(t, len(categoriesResponse.Categories) > 0) +} + +func TestGetCategories(t *testing.T) { + req, err := http.NewRequest("GET", categoryUrl, nil) + require.NoError(t, err) + + res, err := http.DefaultClient.Do(req) + require.NoError(t, err) + defer res.Body.Close() + body, _ := ioutil.ReadAll(res.Body) + + categoriesResponse := &categories.Categories{} + + err = json.Unmarshal(body, categoriesResponse) + require.NoError(t, err) + + assert.Equal(t, 121, (*categoriesResponse).Categories[0].Id, "12-step ID 121") + assert.Equal(t, "12-Step", (*categoriesResponse).Categories[0].Name, "12-step is the first category ordered by Name") +} + +func TestGetCategoryByID(t *testing.T) { + res, err := http.Get(categoryUrl) + require.NoError(t, err) + defer res.Body.Close() + + body, err := ioutil.ReadAll(res.Body) + require.NoError(t, err) + + categoriesResponse := new(categories.Categories) + err = json.Unmarshal(body, categoriesResponse) + require.NoError(t, err) + require.NotEmpty(t, categoriesResponse.Categories, "Nothing found. Check database connection and baseUrl") + + categoryId := categoriesResponse.Categories[0].Id + + res, err = http.Get(categoryUrl + "/" + fmt.Sprintf("%d", categoryId)) + require.NoError(t, err) + defer res.Body.Close() + + body, err = ioutil.ReadAll(res.Body) + require.NoError(t, err) + + categoryResponse := new(categories.Category) + err = json.Unmarshal(body, categoryResponse) + require.NoError(t, err) + + assert.Equal(t, categoryResponse.Id, categoryId, "Category Id is a match") +} diff --git a/cmd/sheltertech-go/integration_test.go b/cmd/sheltertech-go/integration_test.go deleted file mode 100644 index 9198dcb4..00000000 --- a/cmd/sheltertech-go/integration_test.go +++ /dev/null @@ -1,247 +0,0 @@ -//go:build integration -// +build integration - -// go test -tags=integration - -package main - -import ( - "bytes" - "encoding/json" - "fmt" - "github.com/sheltertechsf/sheltertech-go/internal/categories" - "github.com/sheltertechsf/sheltertech-go/internal/changerequest" - "github.com/sheltertechsf/sheltertech-go/internal/common" - "github.com/sheltertechsf/sheltertech-go/internal/services" - "github.com/spf13/viper" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "io/ioutil" - "net/http" - "testing" - "time" -) - -const categoryUrl = "http://localhost:3001/api/categories" -const serviceUrl = "http://localhost:3001/api/services" -const bookmarkUrl = "http://localhost:3001/api/bookmarks" - -func TestGetCategoriesFeatured(t *testing.T) { - startServer() - - url := categoryUrl + "/featured" - - req, err := http.NewRequest("GET", url, nil) - require.NoError(t, err) - - res, err := http.DefaultClient.Do(req) - require.NoError(t, err) - defer res.Body.Close() - body, _ := ioutil.ReadAll(res.Body) - - categoriesResponse := new(categories.Categories) - - err = json.Unmarshal(body, categoriesResponse) - require.NoError(t, err) - - assert.True(t, len(categoriesResponse.Categories) > 0) -} - -func TestGetCategories(t *testing.T) { - startServer() - - req, err := http.NewRequest("GET", categoryUrl, nil) - require.NoError(t, err) - - res, err := http.DefaultClient.Do(req) - require.NoError(t, err) - defer res.Body.Close() - body, _ := ioutil.ReadAll(res.Body) - - categoriesResponse := &categories.Categories{} - - err = json.Unmarshal(body, categoriesResponse) - require.NoError(t, err) - - assert.Equal(t, 121, (*categoriesResponse).Categories[0].Id, "12-step ID 121") - assert.Equal(t, "12-Step", (*categoriesResponse).Categories[0].Name, "12-step is the first category ordered by Name") -} - -func TestGetCategoryByID(t *testing.T) { - startServer() - - res, err := http.Get(categoryUrl) - require.NoError(t, err) - defer res.Body.Close() - - body, err := ioutil.ReadAll(res.Body) - require.NoError(t, err) - - categoriesResponse := new(categories.Categories) - err = json.Unmarshal(body, categoriesResponse) - require.NoError(t, err) - require.NotEmpty(t, categoriesResponse.Categories, "Nothing found. Check database connection and baseUrl") - - categoryId := categoriesResponse.Categories[0].Id - - res, err = http.Get(categoryUrl + "/" + fmt.Sprintf("%d", categoryId)) - require.NoError(t, err) - defer res.Body.Close() - - body, err = ioutil.ReadAll(res.Body) - require.NoError(t, err) - - categoryResponse := new(categories.Category) - err = json.Unmarshal(body, categoryResponse) - require.NoError(t, err) - - assert.Equal(t, categoryResponse.Id, categoryId, "Category Id is a match") -} - -func TestGetServiceByID(t *testing.T) { - startServer() - serviceId := 1 - - res, err := http.Get(serviceUrl + "/" + fmt.Sprintf("%d", serviceId)) - require.NoError(t, err) - defer res.Body.Close() - - body, err := ioutil.ReadAll(res.Body) - require.NoError(t, err) - - serviceResponse := new(services.ServiceResponse) - err = json.Unmarshal(body, serviceResponse) - require.NoError(t, err) - - assert.Equal(t, serviceResponse.Service.Id, serviceId, "Service Id is a match") -} - -func TestUnauthorized(t *testing.T) { - t.Skip("skipping until this is supported in dev") - startServer() - - res, err := http.Get(bookmarkUrl) - require.NoError(t, err) - defer res.Body.Close() - - require.Equal(t, http.StatusUnauthorized, res.StatusCode) -} - -func TestAuthorized(t *testing.T) { - t.Skip("skipping until this is supported in dev") - startServer() - - req, err := http.NewRequest(http.MethodGet, bookmarkUrl, nil) - require.NoError(t, err) - req.Header.Set("Authorization", "Bearer k") - - res, err := http.DefaultClient.Do(req) - require.NoError(t, err) - defer res.Body.Close() - - require.Equal(t, http.StatusOK, res.StatusCode) -} - -func TestPostServicesChangeRequest(t *testing.T) { - startServer() - - url := "http://localhost:3001/api/services/1/change_request" - - changeRequest := changerequest.ChangeRequest{ - Type: "ServiceChangeRequest", - ObjectID: 1, - Status: 1, - Action: 1, - } - body, err := json.Marshal(changeRequest) - require.NoError(t, err) - bytes := bytes.NewBuffer(body) - - req, err := http.NewRequest("POST", url, bytes) - require.NoError(t, err) - - res, err := http.DefaultClient.Do(req) - require.NoError(t, err) - - assert.Equal(t, http.StatusCreated, res.StatusCode) -} - -func TestGetBookmarksBadRequest(t *testing.T) { - startServer() - - res, err := http.Get(bookmarkUrl + "?user_id=a") - require.NoError(t, err) - defer res.Body.Close() - - body, err := ioutil.ReadAll(res.Body) - require.NoError(t, err) - - serviceResponse := new(common.Error) - err = json.Unmarshal(body, serviceResponse) - require.NoError(t, err) - - assert.Equal(t, serviceResponse.StatusCode, http.StatusBadRequest, "Response contains a 400") - assert.NotEmpty(t, serviceResponse.Error, "Response has some error message") -} - -func TestGetBookmarkByIDError(t *testing.T) { - startServer() - - res, err := http.Get(bookmarkUrl + "/0") - require.NoError(t, err) - defer res.Body.Close() - - body, err := ioutil.ReadAll(res.Body) - require.NoError(t, err) - - serviceResponse := new(common.Error) - err = json.Unmarshal(body, serviceResponse) - require.NoError(t, err) - - assert.Equal(t, serviceResponse.StatusCode, http.StatusInternalServerError, "Response contains a 400") - assert.Equal(t, serviceResponse.Error, common.InternalServerErrorMessage) -} - -func TestSwaggerDocs(t *testing.T) { - viper.SetDefault("SERVE_DOCS", "true") - startServer() - - url := "http://localhost:3002/swagger/index.html" - - req, err := http.NewRequest("GET", url, nil) - require.NoError(t, err) - - res, err := http.DefaultClient.Do(req) - require.NoError(t, err) - - assert.Equal(t, http.StatusOK, res.StatusCode) - viper.SetDefault("SERVE_DOCS", "false") -} - -func TestPrometheusMetrics(t *testing.T) { - startServer() - - url := "http://localhost:3001/metrics" - - req, err := http.NewRequest("GET", url, nil) - require.NoError(t, err) - - res, err := http.DefaultClient.Do(req) - require.NoError(t, err) - defer res.Body.Close() - body, _ := ioutil.ReadAll(res.Body) - - assert.True(t, len(body) > 0) - assert.Equal(t, 200, res.StatusCode) -} - -func startServer() { - viper.SetDefault("DB_USER", "postgres") - viper.SetDefault("DB_HOST", "localhost") - viper.SetDefault("DB_PORT", "5432") - viper.SetDefault("DB_NAME", "askdarcel_development") - viper.SetDefault("DB_PASS", "") - viper.SetDefault("AUTH0_DOMAIN", "login.sfserviceguide.org") - go main() - time.Sleep(time.Second) -} diff --git a/cmd/sheltertech-go/main.go b/cmd/sheltertech-go/main.go index bc1d4e02..62995444 100644 --- a/cmd/sheltertech-go/main.go +++ b/cmd/sheltertech-go/main.go @@ -151,3 +151,12 @@ func main() { http.ListenAndServe(":3001", r) } + +func setIntegrationTestEnv() { + viper.SetDefault("DB_USER", "postgres") + viper.SetDefault("DB_HOST", "localhost") + viper.SetDefault("DB_PORT", "5432") + viper.SetDefault("DB_NAME", "askdarcel_development") + viper.SetDefault("DB_PASS", "") + viper.SetDefault("AUTH0_DOMAIN", "login.sfserviceguide.org") +} diff --git a/cmd/sheltertech-go/metrics_integration_test.go b/cmd/sheltertech-go/metrics_integration_test.go new file mode 100644 index 00000000..e1019a25 --- /dev/null +++ b/cmd/sheltertech-go/metrics_integration_test.go @@ -0,0 +1,35 @@ +//go:build integration +// +build integration + +// go test -tags=integration + +package main + +import ( + "io/ioutil" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func init() { + setIntegrationTestEnv() + go main() +} + +func TestPrometheusMetrics(t *testing.T) { + url := "http://localhost:3001/metrics" + + req, err := http.NewRequest("GET", url, nil) + require.NoError(t, err) + + res, err := http.DefaultClient.Do(req) + require.NoError(t, err) + defer res.Body.Close() + body, _ := ioutil.ReadAll(res.Body) + + assert.True(t, len(body) > 0) + assert.Equal(t, 200, res.StatusCode) +} diff --git a/cmd/sheltertech-go/services_integration_test.go b/cmd/sheltertech-go/services_integration_test.go new file mode 100644 index 00000000..9a4ed166 --- /dev/null +++ b/cmd/sheltertech-go/services_integration_test.go @@ -0,0 +1,67 @@ +//go:build integration +// +build integration + +// go test -tags=integration + +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/sheltertechsf/sheltertech-go/internal/changerequest" + "github.com/sheltertechsf/sheltertech-go/internal/services" +) + +func init() { + setIntegrationTestEnv() + go main() +} + +const serviceUrl = "http://localhost:3001/api/services" + +func TestGetServiceByID(t *testing.T) { + serviceId := 1 + + res, err := http.Get(serviceUrl + "/" + fmt.Sprintf("%d", serviceId)) + require.NoError(t, err) + defer res.Body.Close() + + body, err := ioutil.ReadAll(res.Body) + require.NoError(t, err) + + serviceResponse := new(services.ServiceResponse) + err = json.Unmarshal(body, serviceResponse) + require.NoError(t, err) + + assert.Equal(t, serviceResponse.Service.Id, serviceId, "Service Id is a match") +} + +func TestPostServicesChangeRequest(t *testing.T) { + url := "http://localhost:3001/api/services/1/change_request" + + changeRequest := changerequest.ChangeRequest{ + Type: "ServiceChangeRequest", + ObjectID: 1, + Status: 1, + Action: 1, + } + body, err := json.Marshal(changeRequest) + require.NoError(t, err) + bytes := bytes.NewBuffer(body) + + req, err := http.NewRequest("POST", url, bytes) + require.NoError(t, err) + + res, err := http.DefaultClient.Do(req) + require.NoError(t, err) + + assert.Equal(t, http.StatusCreated, res.StatusCode) +} diff --git a/cmd/sheltertech-go/swagger_integration_test.go b/cmd/sheltertech-go/swagger_integration_test.go new file mode 100644 index 00000000..66403609 --- /dev/null +++ b/cmd/sheltertech-go/swagger_integration_test.go @@ -0,0 +1,34 @@ +//go:build integration +// +build integration + +// go test -tags=integration + +package main + +import ( + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "net/http" + "testing" + "time" +) + +func init() { + setIntegrationTestEnv() + viper.SetDefault("SERVE_DOCS", "true") + time.Sleep(time.Second) + go main() +} + +func TestSwaggerDocs(t *testing.T) { + url := "http://localhost:3002/swagger/index.html" + + req, err := http.NewRequest("GET", url, nil) + require.NoError(t, err) + + res, err := http.DefaultClient.Do(req) + require.NoError(t, err) + + assert.Equal(t, http.StatusOK, res.StatusCode) +} diff --git a/internal/db/addresses.go b/internal/db/addresses.go new file mode 100644 index 00000000..51c7bdac --- /dev/null +++ b/internal/db/addresses.go @@ -0,0 +1,80 @@ +package db + +import ( + "database/sql" + "fmt" + "log" + "time" +) + +type Address struct { + Id int + Attention sql.NullString + Address1 string + Address2 sql.NullString + Address3 sql.NullString + Address4 sql.NullString + City string + StateProvince string + PostalCode string + ResourceId sql.NullInt32 + Latitude sql.NullFloat64 + Longitude sql.NullFloat64 + Online sql.NullBool + Region sql.NullString + Name sql.NullString + Description sql.NullString + Transportation sql.NullString + CreatedAt time.Time + UpdatedAt time.Time +} + +const addressesByServiceIDSql = ` +SELECT a.id, a.attention, a.address_1, a.address_2, a.address_3, a.address_4, a.city, a.state_province, a.postal_code, a.resource_id, a.latitude, a.longitude, a.online, a.region, a.name ,a.description , a.transportation +FROM public.addresses a +LEFT JOIN public.addresses_services ads on a.id = ads.address_id +WHERE ads.service_id = $1 +ORDER BY a.id +` + +const addressesByResourceIDSql = ` +SELECT a.id, a.attention, a.address_1, a.address_2, a.address_3, a.address_4, a.city, a.state_province, a.postal_code, a.resource_id, a.latitude, a.longitude, a.online, a.region, a.name ,a.description , a.transportation +FROM public.addresses a +WHERE a.resource_id = $1 +ORDER BY a.id +` + +func (m *Manager) GetAddressesByServiceID(serviceId int) []*Address { + var rows *sql.Rows + var err error + rows, err = m.DB.Query(addressesByServiceIDSql, serviceId) + if err != nil { + log.Printf("%v\n", err) + } + return scanAddresses(rows) +} + +func (m *Manager) GetAddressesByResourceID(resourceId int) []*Address { + var rows *sql.Rows + var err error + rows, err = m.DB.Query(addressesByResourceIDSql, resourceId) + if err != nil { + log.Printf("%v\n", err) + } + return scanAddresses(rows) +} + +func scanAddresses(rows *sql.Rows) []*Address { + var addresses []*Address + for rows.Next() { + var address Address + err := rows.Scan(&address.Id, &address.Attention, &address.Address1, &address.Address2, &address.Address3, &address.Address4, &address.City, &address.StateProvince, &address.PostalCode, &address.ResourceId, &address.Latitude, &address.Longitude, &address.Online, &address.Region, &address.Name, &address.Description, &address.Transportation) + switch err { + case sql.ErrNoRows: + fmt.Println("No rows were returned!") + return nil + } + addresses = append(addresses, &address) + } + return addresses +} diff --git a/internal/db/bookmarks.go b/internal/db/bookmarks.go new file mode 100644 index 00000000..8b11fbf3 --- /dev/null +++ b/internal/db/bookmarks.go @@ -0,0 +1,158 @@ +package db + +import ( + "database/sql" + "errors" + "fmt" + "log" +) + +type Bookmark struct { + Id int + Order int + FolderID *int + ServiceID *int + ResourceID *int + UserID *int + CreatedAt sql.NullTime + UpdatedAt sql.NullTime +} + +const findBookmarksSql = ` +SELECT id, "order", user_id, folder_id, service_id, resource_id from public.bookmarks` + +const findBookmarksByUserIDSql = ` +SELECT id, "order", user_id, folder_id, service_id, resource_id from public.bookmarks +WHERE user_id=$1` + +const findBookmarksByIDSql = ` +SELECT id, "order", user_id, folder_id, service_id, resource_id from public.bookmarks +WHERE id=$1` + +const submitBookmark = ` +INSERT INTO public.bookmarks ("order", user_id, folder_id, resource_id, service_id, created_at, updated_at) +VALUES ($1, $2, $3, $4, $5, now(), now())` + +const updateBookmark = ` +UPDATE public.bookmarks SET "order" = $2, user_id = $3, folder_id= $4, resource_id = $5, service_id = $6 where id = $1` + +const deleteBookmarkByIDSql = ` +DELETE FROM public.bookmarks WHERE id = $1 +` + +// const bookmarksByFolderIDSQL = ` +// SELECT b.if, b.order, b.service_id +// FROM public.bookmarks b +// WHERE b.folder_id = $1 +// ` + +func (m *Manager) GetBookmarks() ([]*Bookmark, error) { + var rows *sql.Rows + var err error + rows, err = m.DB.Query(findBookmarksSql) + if err != nil { + log.Printf("%v\n", err) + } + return scanBookmarks(rows), err +} + +func (m *Manager) GetBookmarkByID(bookmarkId int) (*Bookmark, error) { + row := m.DB.QueryRow(findBookmarksByIDSql, bookmarkId) + return scanBookmark(row) +} + +func (m *Manager) GetBookmarksByUserID(userId int) ([]*Bookmark, error) { + var rows *sql.Rows + var err error + rows, err = m.DB.Query(findBookmarksByUserIDSql, userId) + if err != nil { + log.Printf("%v\n", err) + return nil, err + } + return scanBookmarks(rows), err +} + +func (m *Manager) SubmitBookmark(bookmark *Bookmark) error { + tx, err := m.DB.Begin() + if err != nil { + return err + } + res, err := tx.Exec(submitBookmark, bookmark.Order, bookmark.UserID, bookmark.FolderID, bookmark.ResourceID, bookmark.ServiceID) + if err != nil { + return err + } + + rowCount, err := res.RowsAffected() + if err != nil { + return err + } + if rowCount != 1 { + defer tx.Rollback() + return errors.New(fmt.Sprintf("unexpected rows modified, expected one, saw %v", rowCount)) + } + + return tx.Commit() +} + +func (m *Manager) UpdateBookmark(bookmark *Bookmark) error { + tx, err := m.DB.Begin() + if err != nil { + return err + } + res, err := tx.Exec(updateBookmark, bookmark.Id, bookmark.Order, bookmark.UserID, bookmark.FolderID, bookmark.ResourceID, bookmark.ServiceID) + if err != nil { + return err + } + + rowCount, err := res.RowsAffected() + if err != nil { + return err + } + if rowCount != 1 { + defer tx.Rollback() + return errors.New(fmt.Sprintf("unexpected rows modified, expected one, saw %v", rowCount)) + } + return tx.Commit() +} + +func (m *Manager) DeleteBookmarkByID(bookmarkId int) error { + tx, err := m.DB.Begin() + if err != nil { + return err + } + + res, err := tx.Exec(deleteBookmarkByIDSql, bookmarkId) + if err != nil { + return err + } + rowCount, err := res.RowsAffected() + if err != nil { + return err + } + if rowCount != 1 { + defer tx.Rollback() + return errors.New(fmt.Sprintf("unexpected rows modified, expected one, saw %v", rowCount)) + } + return tx.Commit() +} + +func scanBookmarks(rows *sql.Rows) []*Bookmark { + var bookmarks []*Bookmark + for rows.Next() { + var bookmark Bookmark + err := rows.Scan(&bookmark.Id, &bookmark.Order, &bookmark.UserID, &bookmark.FolderID, &bookmark.ServiceID, &bookmark.ResourceID) + switch err { + case sql.ErrNoRows: + fmt.Println("No rows were returned!") + return nil + } + bookmarks = append(bookmarks, &bookmark) + } + return bookmarks +} + +func scanBookmark(row *sql.Row) (*Bookmark, error) { + var bookmark Bookmark + err := row.Scan(&bookmark.Id, &bookmark.Order, &bookmark.UserID, &bookmark.FolderID, &bookmark.ServiceID, &bookmark.ResourceID) + return &bookmark, err +} diff --git a/internal/db/categories.go b/internal/db/categories.go new file mode 100644 index 00000000..7e5fa60f --- /dev/null +++ b/internal/db/categories.go @@ -0,0 +1,254 @@ +package db + +import ( + "database/sql" + "fmt" + "github.com/lib/pq" + "log" +) + +type Category struct { + Id int + Name string + TopLevel bool + Vocabulary string + Featured bool +} + +type CategoryCount struct { + CategoryName string + Count int +} + +const categoryServiceCounts = ` +SELECT c.name, count(s.id) as services +FROM categories c +JOIN categories_services cs ON cs.category_id = c.id +JOIN services s ON s.id = cs.service_id +WHERE s.status = 1 +GROUP BY c.name +ORDER BY c.name asc +` + +const categoryResourceCounts = ` +SELECT c.name, count(r.id) as resources +FROM categories c +JOIN categories_resources cr ON cr.category_id = c.id +JOIN resources r ON r.id = cr.resource_id +WHERE r.status = 1 +GROUP BY c.name +ORDER BY c.name asc +` + +const categoriesByFeaturedSql = ` +SELECT id, name, top_level, featured +FROM public.categories +WHERE featured = true +` + +const subCategoriesByIDSql = ` +SELECT id, name, top_level, featured +FROM public.categories +WHERE id in (SELECT child_id from public.category_relationships WHERE parent_id = $1) +` + +const categoriesSql = ` +SELECT id, name, top_level, featured +FROM public.categories +ORDER BY name +` + +const categoriesByTopLevelSql = ` +SELECT id, name, top_level, featured +FROM public.categories +WHERE top_level = $1 +ORDER BY name DESC +` + +const categoriesByIDSql = ` +SELECT id, name, top_level, featured +FROM public.categories +WHERE id = $1 +` + +const categoriesByIDsSql = ` +SELECT c.id, c.name, c.top_level, c.featured +FROM public.categories c +WHERE c.id = ANY ($1) +` + +const categoriesByNamesSql = ` +SELECT c.id, c.name, c.top_level, c.featured +FROM public.categories c +WHERE c.name = ANY ($1) +` + +const categoriesByServiceIDSql = ` +SELECT c.id, c.name, c.top_level, c.featured +FROM public.categories c +LEFT JOIN public.categories_services cs on c.id = cs.category_id +WHERE cs.service_id = $1 +ORDER BY c.id +` + +const categoriesByResourceIDSql = ` +SELECT c.id, c.name, c.top_level, c.featured +FROM public.categories c +LEFT JOIN public.categories_resources cs on c.id = cs.category_id +WHERE cs.resource_id = $1 +ORDER BY c.id +` + +func (m *Manager) GetCategories(topLevel *bool) []*Category { + var rows *sql.Rows + var err error + if topLevel != nil { + rows, err = m.DB.Query(categoriesByTopLevelSql, topLevel) + } else { + rows, err = m.DB.Query(categoriesSql) + } + if err != nil { + log.Printf("%v\n", err) + } + return scanCategories(rows) +} + +func (m *Manager) GetCategoryServiceCounts() []*CategoryCount { + var rows *sql.Rows + var err error + rows, err = m.DB.Query(categoryServiceCounts) + if err != nil { + log.Printf("%v\n", err) + } + return scanCategoryCounts(rows) +} + +func (m *Manager) GetCategoryResourceCounts() []*CategoryCount { + var rows *sql.Rows + var err error + rows, err = m.DB.Query(categoryResourceCounts) + if err != nil { + log.Printf("%v\n", err) + } + return scanCategoryCounts(rows) +} + +func (m *Manager) GetCategoriesByFeatured() []*Category { + var rows *sql.Rows + var err error + rows, err = m.DB.Query(categoriesByFeaturedSql) + if err != nil { + log.Printf("%v\n", err) + } + return scanCategories(rows) +} + +func (m *Manager) GetSubCategoriesByID(categoryId int) []*Category { + var rows *sql.Rows + var err error + rows, err = m.DB.Query(subCategoriesByIDSql, categoryId) + if err != nil { + log.Printf("%v\n", err) + } + return scanCategories(rows) +} + +func (m *Manager) GetCategoryByID(categoryId int) *Category { + row := m.DB.QueryRow(categoriesByIDSql, categoryId) + return scanCategory(row) +} + +func (m *Manager) GetCategoriesByIDs(ids []int) []*Category { + var rows *sql.Rows + var err error + stmt, err := m.DB.Prepare(categoriesByIDsSql) + if err != nil { + log.Printf("Prepare failed: %v\n", err) + return nil + } + rows, err = stmt.Query(pq.Array(ids)) + if err != nil { + log.Printf("%v\n", err) + } + return scanCategories(rows) +} + +func (m *Manager) GetCategoriesByNames(names []string) []*Category { + var rows *sql.Rows + var err error + stmt, err := m.DB.Prepare(categoriesByNamesSql) + if err != nil { + log.Printf("Prepare failed: %v\n", err) + return nil + } + rows, err = stmt.Query(pq.Array(names)) + if err != nil { + log.Printf("%v\n", err) + } + return scanCategories(rows) +} + +func (m *Manager) GetCategoriesByServiceID(serviceId int) []*Category { + var rows *sql.Rows + var err error + rows, err = m.DB.Query(categoriesByServiceIDSql, serviceId) + if err != nil { + log.Printf("%v\n", err) + } + return scanCategories(rows) +} + +func (m *Manager) GetCategoriesByResourceID(resourceId int) []*Category { + var rows *sql.Rows + var err error + rows, err = m.DB.Query(categoriesByResourceIDSql, resourceId) + if err != nil { + log.Printf("%v\n", err) + } + return scanCategories(rows) +} + +func scanCategories(rows *sql.Rows) []*Category { + var categories []*Category + for rows.Next() { + var category Category + err := rows.Scan(&category.Id, &category.Name, &category.TopLevel, &category.Featured) + switch err { + case sql.ErrNoRows: + fmt.Println("No rows were returned!") + return nil + } + categories = append(categories, &category) + } + return categories +} + +func scanCategory(row *sql.Row) *Category { + var category Category + err := row.Scan(&category.Id, &category.Name, &category.TopLevel, &category.Featured) + if err != nil { + switch err { + case sql.ErrNoRows: + fmt.Println("No rows were returned!") + return nil + default: + panic(err) + } + } + return &category +} + +func scanCategoryCounts(rows *sql.Rows) []*CategoryCount { + var counts []*CategoryCount + for rows.Next() { + var count CategoryCount + err := rows.Scan(&count.CategoryName, &count.Count) + switch err { + case sql.ErrNoRows: + fmt.Println("No rows were returned!") + return nil + } + counts = append(counts, &count) + } + return counts +} diff --git a/internal/db/changerequests.go b/internal/db/changerequests.go new file mode 100644 index 00000000..13d65bfe --- /dev/null +++ b/internal/db/changerequests.go @@ -0,0 +1,42 @@ +package db + +import ( + "database/sql" + "errors" + "fmt" +) + +type ChangeRequest struct { + Id int + Type string + ObjectId int + Status int + Action int + ResourceId int + CreatedAt sql.NullTime + UpdatedAt sql.NullTime +} + +const submitChangeRequest = ` +INSERT INTO public.change_requests (type, object_id, status, action, resource_id, created_at, updated_at) +VALUES ($1, $2, $3, $4, $5, now(), now())` + +func (m *Manager) SubmitChangeRequest(changeRequest *ChangeRequest) error { + tx, err := m.DB.Begin() + if err != nil { + return err + } + res, err := tx.Exec(submitChangeRequest, changeRequest.Type, changeRequest.ObjectId, changeRequest.Status, changeRequest.Action, changeRequest.ResourceId) + if err != nil { + return err + } + rowCount, err := res.RowsAffected() + if err != nil { + return err + } + if rowCount != 1 { + defer tx.Rollback() + return errors.New(fmt.Sprintf("unexpected rows modified, expected one, saw %v", rowCount)) + } + return tx.Commit() +} diff --git a/internal/db/documents.go b/internal/db/documents.go new file mode 100644 index 00000000..d9a054db --- /dev/null +++ b/internal/db/documents.go @@ -0,0 +1,49 @@ +package db + +import ( + "database/sql" + "fmt" + "log" + "time" +) + +type Document struct { + Id int + Name sql.NullString + Url sql.NullString + Description sql.NullString + CreatedAt time.Time + UpdatedAt time.Time +} + +const documentsByServiceIDSql = ` +SELECT d.id, d.name, d.url, d.description +FROM public.documents d +LEFT JOIN public.documents_services ds on d.id = ds.document_id +WHERE ds.service_id = $1 +` + +func (m *Manager) GetDocumentsByServiceID(serviceId int) []*Document { + var rows *sql.Rows + var err error + rows, err = m.DB.Query(documentsByServiceIDSql, serviceId) + if err != nil { + log.Printf("%v\n", err) + } + return scanDocuments(rows) +} + +func scanDocuments(rows *sql.Rows) []*Document { + var documents []*Document + for rows.Next() { + var document Document + err := rows.Scan(&document.Id, &document.Name, &document.Url, &document.Description) + switch err { + case sql.ErrNoRows: + fmt.Println("No rows were returned!") + return nil + } + documents = append(documents, &document) + } + return documents +} diff --git a/internal/db/eligibilities.go b/internal/db/eligibilities.go new file mode 100644 index 00000000..8abf512b --- /dev/null +++ b/internal/db/eligibilities.go @@ -0,0 +1,91 @@ +package db + +import ( + "database/sql" + "fmt" + "github.com/lib/pq" + "log" + "time" +) + +type Eligibility struct { + Id int + Name sql.NullString + FeatureRank sql.NullInt32 + CreatedAt time.Time + UpdatedAt time.Time +} + +const eligibilitiesByIDsSql = ` +SELECT e.id, e.name, e.feature_rank +FROM public.eligibilities e +WHERE e.id = ANY ($1) +` + +const eligibilitiesByNamesSql = ` +SELECT e.id, e.name, e.feature_rank +FROM public.eligibilities e +WHERE e.name = ANY ($1) +` + +const eligibilitiesByServiceIDSql = ` +SELECT e.id, e.name, e.feature_rank +FROM public.eligibilities e +LEFT JOIN public.eligibilities_services es on e.id = es.eligibility_id +WHERE es.service_id = $1 +` + +func (m *Manager) GetEligibilitiesByIDs(ids []int) []*Eligibility { + var rows *sql.Rows + var err error + stmt, err := m.DB.Prepare(eligibilitiesByIDsSql) + if err != nil { + log.Printf("Prepare failed: %v\n", err) + return nil + } + rows, err = stmt.Query(pq.Array(ids)) + if err != nil { + log.Printf("%v\n", err) + } + return scanEligibilities(rows) +} + +func (m *Manager) GetEligibilitiesByNames(names []string) []*Eligibility { + var rows *sql.Rows + var err error + stmt, err := m.DB.Prepare(eligibilitiesByNamesSql) + if err != nil { + log.Printf("Prepare failed: %v\n", err) + return nil + } + rows, err = stmt.Query(pq.Array(names)) + if err != nil { + log.Printf("%v\n", err) + } + return scanEligibilities(rows) +} + +func (m *Manager) GetEligibilitiesByServiceID(serviceId int) []*Eligibility { + var rows *sql.Rows + var err error + rows, err = m.DB.Query(eligibilitiesByServiceIDSql, serviceId) + if err != nil { + log.Printf("%v\n", err) + } + return scanEligibilities(rows) +} + +func scanEligibilities(rows *sql.Rows) []*Eligibility { + var eligibilities []*Eligibility + for rows.Next() { + var eligibility Eligibility + err := rows.Scan(&eligibility.Id, &eligibility.Name, &eligibility.FeatureRank) + switch err { + case sql.ErrNoRows: + fmt.Println("No rows were returned!") + return nil + } + eligibilities = append(eligibilities, &eligibility) + } + return eligibilities +} diff --git a/internal/db/folders.go b/internal/db/folders.go new file mode 100644 index 00000000..a65c4134 --- /dev/null +++ b/internal/db/folders.go @@ -0,0 +1,144 @@ +package db + +import ( + "database/sql" + "errors" + "fmt" + "log" +) + +type Folder struct { + Id int + Name string + Order int + UserId int +} + +const folderByIDSql = ` +SELECT id, name, "order", user_id +FROM public.folders +WHERE id = $1 +` + +const foldersByUserIDSql = ` +SELECT f.id, f.name, f.order, f.user_id +FROM public.folders f +WHERE f.user_id = $1 +` + +const createFolder = ` +INSERT INTO public.folders (name, "order", user_id, created_at, updated_at) +VALUES ($1, $2, $3, now(), now()) +RETURNING id +` + +const updateFolder = ` +UPDATE public.folders f +SET name = $2, "order" = $3 +WHERE f.id = $1 +` + +const deleteFolder = ` +DELETE FROM public.folders f +WHERE f.id = $1 +` + +func (m *Manager) GetFolderById(folderId int) *Folder { + row := m.DB.QueryRow(folderByIDSql, folderId) + return scanFolder(row) +} + +func (m *Manager) GetFolders(userId int) []*Folder { + var rows *sql.Rows + var err error + rows, err = m.DB.Query(foldersByUserIDSql, userId) + if err != nil { + log.Printf("%v\n", err) + } + return scanFolders(rows) +} + +func (m *Manager) CreateFolder(folder *Folder) (int, error) { + tx, err := m.DB.Begin() + if err != nil { + return -1, err + } + row := tx.QueryRow(createFolder, folder.Name, folder.Order, folder.UserId) + var id int + err = row.Scan(&id) + if err != nil { + return -1, err + } + return id, tx.Commit() +} + +func (m *Manager) UpdateFolder(folder *Folder) error { + tx, err := m.DB.Begin() + if err != nil { + return err + } + res, err := tx.Exec(updateFolder, folder.Id, folder.Name, folder.Order) + if err != nil { + return err + } + rowCount, err := res.RowsAffected() + if err != nil { + return err + } + if rowCount != 1 { + defer tx.Rollback() + return errors.New(fmt.Sprintf("unexpected rows modified, expected one, saw %v", rowCount)) + } + return tx.Commit() +} + +func (m *Manager) DeleteFolderById(folderId int) error { + tx, err := m.DB.Begin() + if err != nil { + return err + } + + res, err := tx.Exec(deleteFolder, folderId) + if err != nil { + return err + } + rowCount, err := res.RowsAffected() + if err != nil { + return err + } + if rowCount != 1 { + defer tx.Rollback() + return errors.New(fmt.Sprintf("unexpected rows modified, expected one, saw %v", rowCount)) + } + return tx.Commit() +} + +func scanFolders(rows *sql.Rows) []*Folder { + var folders []*Folder + for rows.Next() { + var folder Folder + err := rows.Scan(&folder.Id, &folder.Name, &folder.Order, &folder.UserId) + switch err { + case sql.ErrNoRows: + fmt.Println("No rows were returned!") + return nil + } + folders = append(folders, &folder) + } + return folders +} + +func scanFolder(row *sql.Row) *Folder { + var folder Folder + err := row.Scan(&folder.Id, &folder.Name, &folder.Order, &folder.UserId) + if err != nil { + switch err { + case sql.ErrNoRows: + fmt.Println("No rows were returned!") + return nil + default: + panic(err) + } + } + return &folder +} diff --git a/internal/db/instructions.go b/internal/db/instructions.go new file mode 100644 index 00000000..694a8aec --- /dev/null +++ b/internal/db/instructions.go @@ -0,0 +1,46 @@ +package db + +import ( + "database/sql" + "fmt" + "log" + "time" +) + +type Instruction struct { + Id int + Instruction sql.NullString + CreatedAt time.Time + UpdatedAt time.Time +} + +const instructionsByServiceIDSql = ` +SELECT i.id, i.instruction +FROM public.instructions i +WHERE i.service_id = $1 +` + +func (m *Manager) GetInstructionsByServiceID(serviceId int) []*Instruction { + var rows *sql.Rows + var err error + rows, err = m.DB.Query(instructionsByServiceIDSql, serviceId) + if err != nil { + log.Printf("%v\n", err) + } + return scanInstructions(rows) +} + +func scanInstructions(rows *sql.Rows) []*Instruction { + var instructions []*Instruction + for rows.Next() { + var instruction Instruction + err := rows.Scan(&instruction.Id, &instruction.Instruction) + switch err { + case sql.ErrNoRows: + fmt.Println("No rows were returned!") + return nil + } + instructions = append(instructions, &instruction) + } + return instructions +} diff --git a/internal/db/manager.go b/internal/db/manager.go index 7fef20f1..0e6dc47d 100644 --- a/internal/db/manager.go +++ b/internal/db/manager.go @@ -2,11 +2,7 @@ package db import ( "database/sql" - "errors" "fmt" - "log" - - "github.com/lib/pq" ) type Manager struct { @@ -38,825 +34,3 @@ func New(dbHost, dbPort, dbName, dbUser, dbPass string) *Manager { } return manager } - -func (m *Manager) GetCategories(topLevel *bool) []*Category { - var rows *sql.Rows - var err error - if topLevel != nil { - rows, err = m.DB.Query(categoriesByTopLevelSql, topLevel) - } else { - rows, err = m.DB.Query(categoriesSql) - } - if err != nil { - log.Printf("%v\n", err) - } - return scanCategories(rows) -} - -func (m *Manager) GetCategoryServiceCounts() []*CategoryCount { - var rows *sql.Rows - var err error - rows, err = m.DB.Query(categoryServiceCounts) - if err != nil { - log.Printf("%v\n", err) - } - return scanCategoryCounts(rows) -} - -func (m *Manager) GetCategoryResourceCounts() []*CategoryCount { - var rows *sql.Rows - var err error - rows, err = m.DB.Query(categoryResourceCounts) - if err != nil { - log.Printf("%v\n", err) - } - return scanCategoryCounts(rows) -} - -func (m *Manager) GetCategoriesByFeatured() []*Category { - var rows *sql.Rows - var err error - rows, err = m.DB.Query(categoriesByFeaturedSql) - if err != nil { - log.Printf("%v\n", err) - } - return scanCategories(rows) -} - -func (m *Manager) GetSubCategoriesByID(categoryId int) []*Category { - var rows *sql.Rows - var err error - rows, err = m.DB.Query(subCategoriesByIDSql, categoryId) - if err != nil { - log.Printf("%v\n", err) - } - return scanCategories(rows) -} - -func (m *Manager) GetCategoryByID(categoryId int) *Category { - row := m.DB.QueryRow(categoriesByIDSql, categoryId) - return scanCategory(row) -} - -func (m *Manager) GetCategoriesByIDs(ids []int) []*Category { - var rows *sql.Rows - var err error - stmt, err := m.DB.Prepare(categoriesByIDsSql) - if err != nil { - log.Printf("Prepare failed: %v\n", err) - return nil - } - rows, err = stmt.Query(pq.Array(ids)) - if err != nil { - log.Printf("%v\n", err) - } - return scanCategories(rows) -} - -func (m *Manager) GetCategoriesByNames(names []string) []*Category { - var rows *sql.Rows - var err error - stmt, err := m.DB.Prepare(categoriesByNamesSql) - if err != nil { - log.Printf("Prepare failed: %v\n", err) - return nil - } - rows, err = stmt.Query(pq.Array(names)) - if err != nil { - log.Printf("%v\n", err) - } - return scanCategories(rows) -} - -func (m *Manager) GetCategoriesByServiceID(serviceId int) []*Category { - var rows *sql.Rows - var err error - rows, err = m.DB.Query(categoriesByServiceIDSql, serviceId) - if err != nil { - log.Printf("%v\n", err) - } - return scanCategories(rows) -} - -func (m *Manager) GetCategoriesByResourceID(resourceId int) []*Category { - var rows *sql.Rows - var err error - rows, err = m.DB.Query(categoriesByResourceIDSql, resourceId) - if err != nil { - log.Printf("%v\n", err) - } - return scanCategories(rows) -} - -func (m *Manager) GetNotesByServiceID(serviceId int) []*Note { - var rows *sql.Rows - var err error - rows, err = m.DB.Query(notesByServiceIDSql, serviceId) - if err != nil { - log.Printf("%v\n", err) - } - return scanNotes(rows) -} - -func (m *Manager) GetNotesByResourceID(resourceId int) []*Note { - var rows *sql.Rows - var err error - rows, err = m.DB.Query(notesByResourceIDSql, resourceId) - if err != nil { - log.Printf("%v\n", err) - } - return scanNotes(rows) -} - -func scanNotes(rows *sql.Rows) []*Note { - var notes []*Note - for rows.Next() { - var note Note - err := rows.Scan(¬e.Id, ¬e.Note, ¬e.CreatedAt, ¬e.UpdatedAt) - switch err { - case sql.ErrNoRows: - fmt.Println("No rows were returned!") - return nil - } - notes = append(notes, ¬e) - } - return notes -} - -func (m *Manager) GetBookmarks() ([]*Bookmark, error) { - var rows *sql.Rows - var err error - rows, err = m.DB.Query(findBookmarksSql) - if err != nil { - log.Printf("%v\n", err) - } - return scanBookmarks(rows), err -} - -func (m *Manager) GetBookmarksByUserID(userId int) ([]*Bookmark, error) { - var rows *sql.Rows - var err error - rows, err = m.DB.Query(findBookmarksByUserIDSql, userId) - if err != nil { - log.Printf("%v\n", err) - return nil, err - } - return scanBookmarks(rows), err -} - -func (m *Manager) GetBookmarkByID(bookmarkId int) (*Bookmark, error) { - row := m.DB.QueryRow(findBookmarksByIDSql, bookmarkId) - return scanBookmark(row) -} - -func scanBookmarks(rows *sql.Rows) []*Bookmark { - var bookmarks []*Bookmark - for rows.Next() { - var bookmark Bookmark - err := rows.Scan(&bookmark.Id, &bookmark.Order, &bookmark.UserID, &bookmark.FolderID, &bookmark.ServiceID, &bookmark.ResourceID) - switch err { - case sql.ErrNoRows: - fmt.Println("No rows were returned!") - return nil - } - bookmarks = append(bookmarks, &bookmark) - } - return bookmarks -} - -func scanBookmark(row *sql.Row) (*Bookmark, error) { - var bookmark Bookmark - err := row.Scan(&bookmark.Id, &bookmark.Order, &bookmark.UserID, &bookmark.FolderID, &bookmark.ServiceID, &bookmark.ResourceID) - return &bookmark, err -} - -func (m *Manager) GetAddressesByServiceID(serviceId int) []*Address { - var rows *sql.Rows - var err error - rows, err = m.DB.Query(addressesByServiceIDSql, serviceId) - if err != nil { - log.Printf("%v\n", err) - } - return scanAddresses(rows) -} - -func (m *Manager) GetAddressesByResourceID(resourceId int) []*Address { - var rows *sql.Rows - var err error - rows, err = m.DB.Query(addressesByResourceIDSql, resourceId) - if err != nil { - log.Printf("%v\n", err) - } - return scanAddresses(rows) -} - -func scanAddresses(rows *sql.Rows) []*Address { - var addresses []*Address - for rows.Next() { - var address Address - err := rows.Scan(&address.Id, &address.Attention, &address.Address1, &address.Address2, &address.Address3, &address.Address4, &address.City, &address.StateProvince, &address.PostalCode, &address.ResourceId, &address.Latitude, &address.Longitude, &address.Online, &address.Region, &address.Name, &address.Description, &address.Transportation) - switch err { - case sql.ErrNoRows: - fmt.Println("No rows were returned!") - return nil - } - addresses = append(addresses, &address) - } - return addresses -} - -func (m *Manager) GetPhonesByResourceID(resourceId int) []*Phone { - var rows *sql.Rows - var err error - rows, err = m.DB.Query(phonesByResourceIDSql, resourceId) - if err != nil { - log.Printf("%v\n", err) - } - return scanPhones(rows) -} - -func scanPhones(rows *sql.Rows) []*Phone { - var phones []*Phone - for rows.Next() { - var phone Phone - err := rows.Scan(&phone.Id, &phone.Number, &phone.ServiceType) - switch err { - case sql.ErrNoRows: - fmt.Println("No rows were returned!") - return nil - } - phones = append(phones, &phone) - } - return phones -} - -func (m *Manager) GetEligibilitiesByIDs(ids []int) []*Eligibility { - var rows *sql.Rows - var err error - stmt, err := m.DB.Prepare(eligibilitiesByIDsSql) - if err != nil { - log.Printf("Prepare failed: %v\n", err) - return nil - } - rows, err = stmt.Query(pq.Array(ids)) - if err != nil { - log.Printf("%v\n", err) - } - return scanEligibilities(rows) -} - -func (m *Manager) GetEligibilitiesByNames(names []string) []*Eligibility { - var rows *sql.Rows - var err error - stmt, err := m.DB.Prepare(eligibilitiesByNamesSql) - if err != nil { - log.Printf("Prepare failed: %v\n", err) - return nil - } - rows, err = stmt.Query(pq.Array(names)) - if err != nil { - log.Printf("%v\n", err) - } - return scanEligibilities(rows) -} - -func (m *Manager) GetEligibilitiesByServiceID(serviceId int) []*Eligibility { - var rows *sql.Rows - var err error - rows, err = m.DB.Query(eligibilitiesByServiceIDSql, serviceId) - if err != nil { - log.Printf("%v\n", err) - } - return scanEligibilities(rows) -} - -func scanEligibilities(rows *sql.Rows) []*Eligibility { - var eligibilities []*Eligibility - for rows.Next() { - var eligibility Eligibility - err := rows.Scan(&eligibility.Id, &eligibility.Name, &eligibility.FeatureRank) - switch err { - case sql.ErrNoRows: - fmt.Println("No rows were returned!") - return nil - } - eligibilities = append(eligibilities, &eligibility) - } - return eligibilities -} -func (m *Manager) GetInstructionsByServiceID(serviceId int) []*Instruction { - var rows *sql.Rows - var err error - rows, err = m.DB.Query(instructionsByServiceIDSql, serviceId) - if err != nil { - log.Printf("%v\n", err) - } - return scanInstructions(rows) -} - -func scanInstructions(rows *sql.Rows) []*Instruction { - var instructions []*Instruction - for rows.Next() { - var instruction Instruction - err := rows.Scan(&instruction.Id, &instruction.Instruction) - switch err { - case sql.ErrNoRows: - fmt.Println("No rows were returned!") - return nil - } - instructions = append(instructions, &instruction) - } - return instructions -} -func (m *Manager) GetDocumentsByServiceID(serviceId int) []*Document { - var rows *sql.Rows - var err error - rows, err = m.DB.Query(documentsByServiceIDSql, serviceId) - if err != nil { - log.Printf("%v\n", err) - } - return scanDocuments(rows) -} - -func scanDocuments(rows *sql.Rows) []*Document { - var documents []*Document - for rows.Next() { - var document Document - err := rows.Scan(&document.Id, &document.Name, &document.Url, &document.Description) - switch err { - case sql.ErrNoRows: - fmt.Println("No rows were returned!") - return nil - } - documents = append(documents, &document) - } - return documents -} -func scanCategories(rows *sql.Rows) []*Category { - var categories []*Category - for rows.Next() { - var category Category - err := rows.Scan(&category.Id, &category.Name, &category.TopLevel, &category.Featured) - switch err { - case sql.ErrNoRows: - fmt.Println("No rows were returned!") - return nil - } - categories = append(categories, &category) - } - return categories -} - -func scanCategoryCounts(rows *sql.Rows) []*CategoryCount { - var counts []*CategoryCount - for rows.Next() { - var count CategoryCount - err := rows.Scan(&count.CategoryName, &count.Count) - switch err { - case sql.ErrNoRows: - fmt.Println("No rows were returned!") - return nil - } - counts = append(counts, &count) - } - return counts -} - -func scanCategory(row *sql.Row) *Category { - var category Category - err := row.Scan(&category.Id, &category.Name, &category.TopLevel, &category.Featured) - if err != nil { - switch err { - case sql.ErrNoRows: - fmt.Println("No rows were returned!") - return nil - default: - panic(err) - } - } - return &category -} - -func scanService(row *sql.Row) *Service { - var service Service - err := row.Scan(&service.Id, &service.CreatedAt, &service.UpdatedAt, &service.Name, &service.LongDescription, &service.Eligibility, &service.RequiredDocuments, &service.Fee, &service.ApplicationProcess, &service.ResourceId, &service.VerifiedAt, &service.Email, &service.Status, &service.Certified, &service.ProgramId, &service.InterpretationServices, &service.Url, &service.WaitTime, &service.ContactId, &service.FundingId, &service.AlternateName, &service.CertifiedAt, &service.Featured, &service.SourceAttribution, &service.InternalNote, &service.ShortDescription) - if err != nil { - switch err { - case sql.ErrNoRows: - fmt.Println("No rows were returned!") - return nil - default: - panic(err) - } - } - return &service -} - -func scanServices(rows *sql.Rows) []*Service { - var services []*Service - for rows.Next() { - var service Service - err := rows.Scan(&service.Id, &service.CreatedAt, &service.UpdatedAt, &service.Name, &service.LongDescription, &service.Eligibility, &service.RequiredDocuments, &service.Fee, &service.ApplicationProcess, &service.ResourceId, &service.VerifiedAt, &service.Email, &service.Status, &service.Certified, &service.ProgramId, &service.InterpretationServices, &service.Url, &service.WaitTime, &service.ContactId, &service.FundingId, &service.AlternateName, &service.CertifiedAt, &service.Featured, &service.SourceAttribution, &service.InternalNote, &service.ShortDescription) - switch err { - case sql.ErrNoRows: - fmt.Println("No rows were returned!") - return nil - } - services = append(services, &service) - } - return services -} - -func scanProgram(row *sql.Row) *Program { - var program Program - err := row.Scan(&program.Id, &program.Name, &program.AlternateName, &program.Description) - if err != nil { - switch err { - case sql.ErrNoRows: - fmt.Println("No rows were returned!") - return nil - default: - panic(err) - } - } - return &program -} - -func scanResource(row *sql.Row) *Resource { - var resource Resource - err := row.Scan(&resource.Id, &resource.Name, &resource.ShortDescription, &resource.LongDescription, &resource.Website, &resource.VerifiedAt, &resource.Email, &resource.Status, &resource.Certified, &resource.AlternateName, &resource.LegalStatus, &resource.ContactId, &resource.FundingId, &resource.CertifiedAt, &resource.Featured, &resource.SourceAttribution, &resource.InternalNote, &resource.UpdatedAt) - if err != nil { - switch err { - case sql.ErrNoRows: - fmt.Println("No rows were returned!") - return nil - default: - panic(err) - } - } - return &resource -} - -func scanSchedule(row *sql.Row) *Schedule { - var schedule Schedule - err := row.Scan(&schedule.Id, &schedule.HoursKnown) - if err != nil { - switch err { - case sql.ErrNoRows: - fmt.Println("No rows were returned!") - return nil - default: - panic(err) - } - } - return &schedule -} - -func scanUser(row *sql.Row) *User { - var user User - err := row.Scan(&user.Id, &user.Name, &user.Organization, &user.UserExternalId, &user.Email) - if err != nil { - switch err { - case sql.ErrNoRows: - fmt.Println("No rows were returned!") - return nil - default: - panic(err) - } - } - return &user -} - -func (m *Manager) SubmitChangeRequest(changeRequest *ChangeRequest) error { - tx, err := m.DB.Begin() - if err != nil { - return err - } - res, err := tx.Exec(submitChangeRequest, changeRequest.Type, changeRequest.ObjectId, changeRequest.Status, changeRequest.Action, changeRequest.ResourceId) - if err != nil { - return err - } - rowCount, err := res.RowsAffected() - if err != nil { - return err - } - if rowCount != 1 { - defer tx.Rollback() - return errors.New(fmt.Sprintf("unexpected rows modified, expected one, saw %v", rowCount)) - } - return tx.Commit() -} - -func (m *Manager) SubmitBookmark(bookmark *Bookmark) error { - - tx, err := m.DB.Begin() - if err != nil { - return err - } - res, err := tx.Exec(submitBookmark, bookmark.Order, bookmark.UserID, bookmark.FolderID, bookmark.ResourceID, bookmark.ServiceID) - if err != nil { - return err - } - - rowCount, err := res.RowsAffected() - if err != nil { - return err - } - if rowCount != 1 { - defer tx.Rollback() - return errors.New(fmt.Sprintf("unexpected rows modified, expected one, saw %v", rowCount)) - } - - return tx.Commit() -} - -func (m *Manager) UpdateBookmark(bookmark *Bookmark) error { - tx, err := m.DB.Begin() - if err != nil { - return err - } - res, err := tx.Exec(updateBookmark, bookmark.Id, bookmark.Order, bookmark.UserID, bookmark.FolderID, bookmark.ResourceID, bookmark.ServiceID) - if err != nil { - return err - } - - rowCount, err := res.RowsAffected() - if err != nil { - return err - } - if rowCount != 1 { - defer tx.Rollback() - return errors.New(fmt.Sprintf("unexpected rows modified, expected one, saw %v", rowCount)) - } - return tx.Commit() -} - -func (m *Manager) GetServiceById(serviceId int) *Service { - row := m.DB.QueryRow(serviceByIDSql, serviceId) - return scanService(row) -} - -func (m *Manager) GetApprovedServicesByResourceId(resourceId int) []*Service { - var rows *sql.Rows - var err error - rows, err = m.DB.Query(approvedServicesByResourceIDSql, resourceId) - if err != nil { - log.Printf("%v\n", err) - } - return scanServices(rows) -} - -func (m *Manager) GetProgramById(programId int) *Program { - row := m.DB.QueryRow(programByIDSql, programId) - return scanProgram(row) -} -func (m *Manager) GetResourceById(resourceId int) *Resource { - row := m.DB.QueryRow(resourceByIDSql, resourceId) - return scanResource(row) -} - -func (m *Manager) GetScheduleByServiceId(serviceId int) *Schedule { - row := m.DB.QueryRow(scheduleByServiceIDSql, serviceId) - schedule := scanSchedule(row) - schedule.ScheduleDays = m.GetScheduleDaysByScheduleID(schedule.Id) - return schedule -} - -func (m *Manager) GetScheduleByResourceId(resourceId int) *Schedule { - row := m.DB.QueryRow(scheduleByResourceIDSql, resourceId) - schedule := scanSchedule(row) - schedule.ScheduleDays = m.GetScheduleDaysByScheduleID(schedule.Id) - return schedule -} - -func (m *Manager) GetScheduleDaysByScheduleID(scheduleId int) []*ScheduleDay { - var rows *sql.Rows - var err error - rows, err = m.DB.Query(scheduleDaysByScheduleIDSql, scheduleId) - if err != nil { - log.Printf("%v\n", err) - } - return scanScheduleDays(rows) -} - -func (m *Manager) DeleteBookmarkByID(bookmarkId int) error { - tx, err := m.DB.Begin() - if err != nil { - return err - } - - res, err := tx.Exec(deleteBookmarkByIDSql, bookmarkId) - if err != nil { - return err - } - rowCount, err := res.RowsAffected() - if err != nil { - return err - } - if rowCount != 1 { - defer tx.Rollback() - return errors.New(fmt.Sprintf("unexpected rows modified, expected one, saw %v", rowCount)) - } - - return tx.Commit() - -} - -func scanScheduleDays(rows *sql.Rows) []*ScheduleDay { - var scheduleDays []*ScheduleDay - for rows.Next() { - var scheduleDay ScheduleDay - err := rows.Scan(&scheduleDay.Id, &scheduleDay.Day, &scheduleDay.OpensAt, &scheduleDay.ClosesAt, &scheduleDay.OpenTime, &scheduleDay.OpenDay, &scheduleDay.CloseTime, &scheduleDay.CloseDay) - switch err { - case sql.ErrNoRows: - fmt.Println("No rows were returned!") - return nil - } - scheduleDays = append(scheduleDays, &scheduleDay) - } - return scheduleDays -} - -func scanFolder(row *sql.Row) *Folder { - var folder Folder - err := row.Scan(&folder.Id, &folder.Name, &folder.Order, &folder.UserId) - if err != nil { - switch err { - case sql.ErrNoRows: - fmt.Println("No rows were returned!") - return nil - default: - panic(err) - } - } - return &folder -} -func (m *Manager) GetFolderById(folderId int) *Folder { - row := m.DB.QueryRow(folderByIDSql, folderId) - return scanFolder(row) -} - -func scanFolders(rows *sql.Rows) []*Folder { - var folders []*Folder - for rows.Next() { - var folder Folder - err := rows.Scan(&folder.Id, &folder.Name, &folder.Order, &folder.UserId) - switch err { - case sql.ErrNoRows: - fmt.Println("No rows were returned!") - return nil - } - folders = append(folders, &folder) - } - return folders -} - -func (m *Manager) GetFolders(userId int) []*Folder { - var rows *sql.Rows - var err error - rows, err = m.DB.Query(foldersByUserIDSql, userId) - if err != nil { - log.Printf("%v\n", err) - } - return scanFolders(rows) -} - -func (m *Manager) CreateFolder(folder *Folder) (int, error) { - tx, err := m.DB.Begin() - if err != nil { - return -1, err - } - row := tx.QueryRow(createFolder, folder.Name, folder.Order, folder.UserId) - var id int - err = row.Scan(&id) - if err != nil { - return -1, err - } - return id, tx.Commit() -} - -func (m *Manager) UpdateFolder(folder *Folder) error { - tx, err := m.DB.Begin() - if err != nil { - return err - } - res, err := tx.Exec(updateFolder, folder.Id, folder.Name, folder.Order) - if err != nil { - return err - } - rowCount, err := res.RowsAffected() - if err != nil { - return err - } - if rowCount != 1 { - defer tx.Rollback() - return errors.New(fmt.Sprintf("unexpected rows modified, expected one, saw %v", rowCount)) - } - return tx.Commit() -} - -func (m *Manager) DeleteFolderById(folderId int) error { - tx, err := m.DB.Begin() - if err != nil { - return err - } - - res, err := tx.Exec(deleteFolder, folderId) - if err != nil { - return err - } - rowCount, err := res.RowsAffected() - if err != nil { - return err - } - if rowCount != 1 { - defer tx.Rollback() - return errors.New(fmt.Sprintf("unexpected rows modified, expected one, saw %v", rowCount)) - } - return tx.Commit() -} - -func scanSavedSearch(row *sql.Row) *SavedSearch { - var savedSearch SavedSearch - err := row.Scan(&savedSearch.Id, &savedSearch.UserId, &savedSearch.Name, &savedSearch.Search) - if err != nil { - switch err { - case sql.ErrNoRows: - fmt.Println("No rows were returned!") - return nil - default: - panic(err) - } - } - return &savedSearch -} - -func (m *Manager) GetSavedSearchById(savedSearchId int) *SavedSearch { - row := m.DB.QueryRow(savedSearchByIDSql, savedSearchId) - return scanSavedSearch(row) -} - -func scanSavedSearches(rows *sql.Rows) []*SavedSearch { - var savedSearches []*SavedSearch - for rows.Next() { - var savedSearch SavedSearch - err := rows.Scan(&savedSearch.Id, &savedSearch.UserId, &savedSearch.Name, &savedSearch.Search) - switch err { - case sql.ErrNoRows: - fmt.Println("No rows were returned!") - return nil - } - savedSearches = append(savedSearches, &savedSearch) - } - return savedSearches -} - -func (m *Manager) GetSavedSearches(userId int) []*SavedSearch { - var rows *sql.Rows - var err error - rows, err = m.DB.Query(savedSearchesByUserIDSql, userId) - if err != nil { - log.Printf("%v\n", err) - } - return scanSavedSearches(rows) -} - -func (m *Manager) CreateSavedSearch(savedSearch *SavedSearch) (int, error) { - tx, err := m.DB.Begin() - if err != nil { - return -1, err - } - row := tx.QueryRow(createSavedSearchSql, savedSearch.UserId, savedSearch.Name, savedSearch.Search) - var id int - err = row.Scan(&id) - if err != nil { - return -1, err - } - return id, tx.Commit() -} - -func (m *Manager) DeleteSavedSearchById(id int) error { - tx, err := m.DB.Begin() - if err != nil { - return err - } - - res, err := tx.Exec(deleteSavedSearchSql, id) - if err != nil { - return err - } - rowCount, err := res.RowsAffected() - if err != nil { - return err - } - if rowCount != 1 { - defer tx.Rollback() - return errors.New(fmt.Sprintf("unexpected rows modified, expected one, saw %v", rowCount)) - } - return tx.Commit() -} - -func (m *Manager) GetUserByUserExternalID(userExternalId string) *User { - row := m.DB.QueryRow(userByUserExternalIDSql, userExternalId) - return scanUser(row) -} diff --git a/internal/db/notes.go b/internal/db/notes.go new file mode 100644 index 00000000..d186efd3 --- /dev/null +++ b/internal/db/notes.go @@ -0,0 +1,63 @@ +package db + +import ( + "database/sql" + "fmt" + "log" +) + +type Note struct { + Id int + Note sql.NullString + ResourceId sql.NullInt32 + ServiceId sql.NullInt32 + CreatedAt sql.NullTime + UpdatedAt sql.NullTime +} + +const notesByServiceIDSql = ` +SELECT n.id, n.note, n.created_at, n.updated_at +FROM public.notes n +WHERE n.service_id = $1 +` + +const notesByResourceIDSql = ` +SELECT n.id, n.note, n.created_at, n.updated_at +FROM public.notes n +WHERE n.resource_id = $1 +` + +func (m *Manager) GetNotesByServiceID(serviceId int) []*Note { + var rows *sql.Rows + var err error + rows, err = m.DB.Query(notesByServiceIDSql, serviceId) + if err != nil { + log.Printf("%v\n", err) + } + return scanNotes(rows) +} + +func (m *Manager) GetNotesByResourceID(resourceId int) []*Note { + var rows *sql.Rows + var err error + rows, err = m.DB.Query(notesByResourceIDSql, resourceId) + if err != nil { + log.Printf("%v\n", err) + } + return scanNotes(rows) +} + +func scanNotes(rows *sql.Rows) []*Note { + var notes []*Note + for rows.Next() { + var note Note + err := rows.Scan(¬e.Id, ¬e.Note, ¬e.CreatedAt, ¬e.UpdatedAt) + switch err { + case sql.ErrNoRows: + fmt.Println("No rows were returned!") + return nil + } + notes = append(notes, ¬e) + } + return notes +} diff --git a/internal/db/phones.go b/internal/db/phones.go new file mode 100644 index 00000000..5e526d46 --- /dev/null +++ b/internal/db/phones.go @@ -0,0 +1,44 @@ +package db + +import ( + "database/sql" + "fmt" + "log" +) + +type Phone struct { + Id int + Number string + ServiceType string +} + +const phonesByResourceIDSql = ` +SELECT p.id, p.number, p.service_type +FROM public.phones p +WHERE p.resource_id = $1 +` + +func (m *Manager) GetPhonesByResourceID(resourceId int) []*Phone { + var rows *sql.Rows + var err error + rows, err = m.DB.Query(phonesByResourceIDSql, resourceId) + if err != nil { + log.Printf("%v\n", err) + } + return scanPhones(rows) +} + +func scanPhones(rows *sql.Rows) []*Phone { + var phones []*Phone + for rows.Next() { + var phone Phone + err := rows.Scan(&phone.Id, &phone.Number, &phone.ServiceType) + switch err { + case sql.ErrNoRows: + fmt.Println("No rows were returned!") + return nil + } + phones = append(phones, &phone) + } + return phones +} diff --git a/internal/db/programs.go b/internal/db/programs.go new file mode 100644 index 00000000..94a1b2c0 --- /dev/null +++ b/internal/db/programs.go @@ -0,0 +1,41 @@ +package db + +import ( + "database/sql" + "fmt" +) + +type Program struct { + Id int + Name sql.NullString + AlternateName sql.NullString + Description sql.NullString + CreatedAt sql.NullTime + UpdatedAt sql.NullTime +} + +const programByIDSql = ` +SELECT id, name, alterante_name, description +FROM public.programs +WHERE id = $1 +` + +func (m *Manager) GetProgramById(programId int) *Program { + row := m.DB.QueryRow(programByIDSql, programId) + return scanProgram(row) +} + +func scanProgram(row *sql.Row) *Program { + var program Program + err := row.Scan(&program.Id, &program.Name, &program.AlternateName, &program.Description) + if err != nil { + switch err { + case sql.ErrNoRows: + fmt.Println("No rows were returned!") + return nil + default: + panic(err) + } + } + return &program +} diff --git a/internal/db/resources.go b/internal/db/resources.go new file mode 100644 index 00000000..893564d8 --- /dev/null +++ b/internal/db/resources.go @@ -0,0 +1,55 @@ +package db + +import ( + "database/sql" + "fmt" + "time" +) + +type Resource struct { + Id int + AlternateName sql.NullString + Certified sql.NullBool + Email sql.NullString + LegalStatus sql.NullString + LongDescription sql.NullString + Name string + ShortDescription sql.NullString + Status sql.NullString + VerifiedAt *time.Time + Website sql.NullString + CertifiedAt *time.Time + Featured sql.NullBool + SourceAttribution sql.NullString + InternalNote sql.NullString + CreatedAt time.Time + UpdatedAt time.Time + ContactId sql.NullInt32 + FundingId sql.NullInt32 +} + +const resourceByIDSql = ` +SELECT id, name, short_description, long_description, website, verified_at, email, status, certified, alternate_name, legal_status, contact_id, funding_id, certified_at, featured, source_attribution, internal_note, updated_at +FROM public.resources +WHERE id = $1 +` + +func (m *Manager) GetResourceById(resourceId int) *Resource { + row := m.DB.QueryRow(resourceByIDSql, resourceId) + return scanResource(row) +} + +func scanResource(row *sql.Row) *Resource { + var resource Resource + err := row.Scan(&resource.Id, &resource.Name, &resource.ShortDescription, &resource.LongDescription, &resource.Website, &resource.VerifiedAt, &resource.Email, &resource.Status, &resource.Certified, &resource.AlternateName, &resource.LegalStatus, &resource.ContactId, &resource.FundingId, &resource.CertifiedAt, &resource.Featured, &resource.SourceAttribution, &resource.InternalNote, &resource.UpdatedAt) + if err != nil { + switch err { + case sql.ErrNoRows: + fmt.Println("No rows were returned!") + return nil + default: + panic(err) + } + } + return &resource +} diff --git a/internal/db/savedsearch.go b/internal/db/savedsearch.go new file mode 100644 index 00000000..745f28ad --- /dev/null +++ b/internal/db/savedsearch.go @@ -0,0 +1,143 @@ +package db + +import ( + "database/sql" + "database/sql/driver" + "encoding/json" + "errors" + "fmt" + "log" +) + +type SavedSearchQuery struct { + Eligibilities []int `json:"eligibilities"` + Categories []int `json:"categories"` + Lat *float64 `json:"lat"` + Lng *float64 `json:"lng"` + Query string `json:"query"` +} + +// Methods needed for automatic serialization/deserialization to JSONB column. +// https://www.alexedwards.net/blog/using-postgresql-jsonb + +func (s SavedSearchQuery) Value() (driver.Value, error) { + return json.Marshal(s) +} + +func (s *SavedSearchQuery) Scan(value interface{}) error { + b, ok := value.([]byte) + if !ok { + return errors.New("type assertion to []byte failed") + } + return json.Unmarshal(b, &s) +} + +const savedSearchByIDSql = ` +SELECT id, user_id, name, search +FROM public.saved_searches +WHERE id = $1 +` + +const savedSearchesByUserIDSql = ` +SELECT ss.id, ss.user_id, ss.name, ss.search +FROM public.saved_searches ss +WHERE ss.user_id = $1 +` + +const createSavedSearchSql = ` +INSERT INTO public.saved_searches (user_id, name, search, created_at, updated_at) +VALUES ($1, $2, $3, now(), now()) +RETURNING id +` + +const deleteSavedSearchSql = ` +DELETE FROM public.saved_searches ss +WHERE ss.id = $1 +` + +type SavedSearch struct { + Id int + UserId int + Name string + Search SavedSearchQuery +} + +func (m *Manager) GetSavedSearchById(savedSearchId int) *SavedSearch { + row := m.DB.QueryRow(savedSearchByIDSql, savedSearchId) + return scanSavedSearch(row) +} + +func (m *Manager) GetSavedSearches(userId int) []*SavedSearch { + var rows *sql.Rows + var err error + rows, err = m.DB.Query(savedSearchesByUserIDSql, userId) + if err != nil { + log.Printf("%v\n", err) + } + return scanSavedSearches(rows) +} + +func (m *Manager) CreateSavedSearch(savedSearch *SavedSearch) (int, error) { + tx, err := m.DB.Begin() + if err != nil { + return -1, err + } + row := tx.QueryRow(createSavedSearchSql, savedSearch.UserId, savedSearch.Name, savedSearch.Search) + var id int + err = row.Scan(&id) + if err != nil { + return -1, err + } + return id, tx.Commit() +} + +func (m *Manager) DeleteSavedSearchById(id int) error { + tx, err := m.DB.Begin() + if err != nil { + return err + } + + res, err := tx.Exec(deleteSavedSearchSql, id) + if err != nil { + return err + } + rowCount, err := res.RowsAffected() + if err != nil { + return err + } + if rowCount != 1 { + defer tx.Rollback() + return errors.New(fmt.Sprintf("unexpected rows modified, expected one, saw %v", rowCount)) + } + return tx.Commit() +} + +func scanSavedSearches(rows *sql.Rows) []*SavedSearch { + var savedSearches []*SavedSearch + for rows.Next() { + var savedSearch SavedSearch + err := rows.Scan(&savedSearch.Id, &savedSearch.UserId, &savedSearch.Name, &savedSearch.Search) + switch err { + case sql.ErrNoRows: + fmt.Println("No rows were returned!") + return nil + } + savedSearches = append(savedSearches, &savedSearch) + } + return savedSearches +} + +func scanSavedSearch(row *sql.Row) *SavedSearch { + var savedSearch SavedSearch + err := row.Scan(&savedSearch.Id, &savedSearch.UserId, &savedSearch.Name, &savedSearch.Search) + if err != nil { + switch err { + case sql.ErrNoRows: + fmt.Println("No rows were returned!") + return nil + default: + panic(err) + } + } + return &savedSearch +} diff --git a/internal/db/schedules.go b/internal/db/schedules.go new file mode 100644 index 00000000..e0aeb96a --- /dev/null +++ b/internal/db/schedules.go @@ -0,0 +1,97 @@ +package db + +import ( + "database/sql" + "fmt" + "log" +) + +type Schedule struct { + Id int + HoursKnown bool + + ScheduleDays []*ScheduleDay +} + +type ScheduleDay struct { + Id int + Day string + OpensAt sql.NullInt32 + ClosesAt sql.NullInt32 + OpenTime sql.NullTime + OpenDay sql.NullString + CloseTime sql.NullTime + CloseDay sql.NullString +} + +const scheduleByServiceIDSql = ` +SELECT s.id, s.hours_known +FROM public.schedules s +WHERE s.service_id = $1 LIMIT 1 +` + +const scheduleByResourceIDSql = ` +SELECT s.id, s.hours_known +FROM public.schedules s +WHERE s.resource_id = $1 LIMIT 1 +` + +const scheduleDaysByScheduleIDSql = ` +SELECT sd.id, sd.day, sd.opens_at, sd.closes_at, sd.open_time, sd.open_day, sd.close_time, sd.close_day +FROM public.schedule_days sd +WHERE sd.schedule_id = $1 +` + +func (m *Manager) GetScheduleByServiceId(serviceId int) *Schedule { + row := m.DB.QueryRow(scheduleByServiceIDSql, serviceId) + schedule := scanSchedule(row) + schedule.ScheduleDays = m.GetScheduleDaysByScheduleID(schedule.Id) + return schedule +} + +func (m *Manager) GetScheduleByResourceId(resourceId int) *Schedule { + row := m.DB.QueryRow(scheduleByResourceIDSql, resourceId) + schedule := scanSchedule(row) + schedule.ScheduleDays = m.GetScheduleDaysByScheduleID(schedule.Id) + return schedule +} + +func (m *Manager) GetScheduleDaysByScheduleID(scheduleId int) []*ScheduleDay { + var rows *sql.Rows + var err error + rows, err = m.DB.Query(scheduleDaysByScheduleIDSql, scheduleId) + if err != nil { + log.Printf("%v\n", err) + } + return scanScheduleDays(rows) +} + +func scanScheduleDays(rows *sql.Rows) []*ScheduleDay { + var scheduleDays []*ScheduleDay + for rows.Next() { + var scheduleDay ScheduleDay + err := rows.Scan(&scheduleDay.Id, &scheduleDay.Day, &scheduleDay.OpensAt, &scheduleDay.ClosesAt, &scheduleDay.OpenTime, &scheduleDay.OpenDay, &scheduleDay.CloseTime, &scheduleDay.CloseDay) + switch err { + case sql.ErrNoRows: + fmt.Println("No rows were returned!") + return nil + } + scheduleDays = append(scheduleDays, &scheduleDay) + } + return scheduleDays +} + +func scanSchedule(row *sql.Row) *Schedule { + var schedule Schedule + err := row.Scan(&schedule.Id, &schedule.HoursKnown) + if err != nil { + switch err { + case sql.ErrNoRows: + fmt.Println("No rows were returned!") + return nil + default: + panic(err) + } + } + return &schedule +} diff --git a/internal/db/services.go b/internal/db/services.go new file mode 100644 index 00000000..eeb4e77b --- /dev/null +++ b/internal/db/services.go @@ -0,0 +1,94 @@ +package db + +import ( + "database/sql" + "fmt" + "log" + "time" +) + +type Service struct { + Id int + CreatedAt time.Time + UpdatedAt time.Time + Name sql.NullString + LongDescription sql.NullString + Eligibility sql.NullString + RequiredDocuments sql.NullString + Fee sql.NullString + ApplicationProcess sql.NullString + ResourceId sql.NullInt32 + VerifiedAt *time.Time + Email sql.NullString + Status sql.NullInt32 + Certified bool + ProgramId sql.NullInt32 + InterpretationServices sql.NullString + Url sql.NullString + WaitTime sql.NullString + ContactId sql.NullInt32 + FundingId sql.NullInt32 + AlternateName sql.NullString + CertifiedAt *time.Time + Featured sql.NullBool + SourceAttribution sql.NullInt32 + InternalNote sql.NullString + ShortDescription sql.NullString +} + +const serviceByIDSql = ` +SELECT id, created_at, updated_at, name, long_description, eligibility, required_documents, fee, application_process, resource_id, verified_at, email, status, certified, program_id, interpretation_services, url, wait_time, contact_id, funding_id, alternate_name, certified_at, featured, source_attribution, internal_note, short_description +FROM public.services +WHERE id = $1 +` + +const approvedServicesByResourceIDSql = ` +SELECT id, created_at, updated_at, name, long_description, eligibility, required_documents, fee, application_process, resource_id, verified_at, email, status, certified, program_id, interpretation_services, url, wait_time, contact_id, funding_id, alternate_name, certified_at, featured, source_attribution, internal_note, short_description +FROM public.services +WHERE resource_id = $1 and status = 1 +` + +func (m *Manager) GetServiceById(serviceId int) *Service { + row := m.DB.QueryRow(serviceByIDSql, serviceId) + return scanService(row) +} + +func (m *Manager) GetApprovedServicesByResourceId(resourceId int) []*Service { + var rows *sql.Rows + var err error + rows, err = m.DB.Query(approvedServicesByResourceIDSql, resourceId) + if err != nil { + log.Printf("%v\n", err) + } + return scanServices(rows) +} + +func scanServices(rows *sql.Rows) []*Service { + var services []*Service + for rows.Next() { + var service Service + err := rows.Scan(&service.Id, &service.CreatedAt, &service.UpdatedAt, &service.Name, &service.LongDescription, &service.Eligibility, &service.RequiredDocuments, &service.Fee, &service.ApplicationProcess, &service.ResourceId, &service.VerifiedAt, &service.Email, &service.Status, &service.Certified, &service.ProgramId, &service.InterpretationServices, &service.Url, &service.WaitTime, &service.ContactId, &service.FundingId, &service.AlternateName, &service.CertifiedAt, &service.Featured, &service.SourceAttribution, &service.InternalNote, &service.ShortDescription) + switch err { + case sql.ErrNoRows: + fmt.Println("No rows were returned!") + return nil + } + services = append(services, &service) + } + return services +} + +func scanService(row *sql.Row) *Service { + var service Service + err := row.Scan(&service.Id, &service.CreatedAt, &service.UpdatedAt, &service.Name, &service.LongDescription, &service.Eligibility, &service.RequiredDocuments, &service.Fee, &service.ApplicationProcess, &service.ResourceId, &service.VerifiedAt, &service.Email, &service.Status, &service.Certified, &service.ProgramId, &service.InterpretationServices, &service.Url, &service.WaitTime, &service.ContactId, &service.FundingId, &service.AlternateName, &service.CertifiedAt, &service.Featured, &service.SourceAttribution, &service.InternalNote, &service.ShortDescription) + if err != nil { + switch err { + case sql.ErrNoRows: + fmt.Println("No rows were returned!") + return nil + default: + panic(err) + } + } + return &service +} diff --git a/internal/db/sql.go b/internal/db/sql.go deleted file mode 100644 index 4ed6cd25..00000000 --- a/internal/db/sql.go +++ /dev/null @@ -1,274 +0,0 @@ -package db - -const categoriesSql = ` -SELECT id, name, top_level, featured -FROM public.categories -ORDER BY name -` - -const categoriesByTopLevelSql = ` -SELECT id, name, top_level, featured -FROM public.categories -WHERE top_level = $1 -ORDER BY name DESC -` - -const categoriesByIDSql = ` -SELECT id, name, top_level, featured -FROM public.categories -WHERE id = $1 -` - -const categoriesByIDsSql = ` -SELECT c.id, c.name, c.top_level, c.featured -FROM public.categories c -WHERE c.id = ANY ($1) -` - -const categoriesByNamesSql = ` -SELECT c.id, c.name, c.top_level, c.featured -FROM public.categories c -WHERE c.name = ANY ($1) -` - -const categoriesByServiceIDSql = ` -SELECT c.id, c.name, c.top_level, c.featured -FROM public.categories c -LEFT JOIN public.categories_services cs on c.id = cs.category_id -WHERE cs.service_id = $1 -ORDER BY c.id -` - -const categoriesByResourceIDSql = ` -SELECT c.id, c.name, c.top_level, c.featured -FROM public.categories c -LEFT JOIN public.categories_resources cs on c.id = cs.category_id -WHERE cs.resource_id = $1 -ORDER BY c.id -` - -const notesByServiceIDSql = ` -SELECT n.id, n.note, n.created_at, n.updated_at -FROM public.notes n -WHERE n.service_id = $1 -` - -const notesByResourceIDSql = ` -SELECT n.id, n.note, n.created_at, n.updated_at -FROM public.notes n -WHERE n.resource_id = $1 -` - -const addressesByServiceIDSql = ` -SELECT a.id, a.attention, a.address_1, a.address_2, a.address_3, a.address_4, a.city, a.state_province, a.postal_code, a.resource_id, a.latitude, a.longitude, a.online, a.region, a.name ,a.description , a.transportation -FROM public.addresses a -LEFT JOIN public.addresses_services ads on a.id = ads.address_id -WHERE ads.service_id = $1 -ORDER BY a.id -` - -const addressesByResourceIDSql = ` -SELECT a.id, a.attention, a.address_1, a.address_2, a.address_3, a.address_4, a.city, a.state_province, a.postal_code, a.resource_id, a.latitude, a.longitude, a.online, a.region, a.name ,a.description , a.transportation -FROM public.addresses a -WHERE a.resource_id = $1 -ORDER BY a.id -` - -const folderByIDSql = ` -SELECT id, name, "order", user_id -FROM public.folders -WHERE id = $1 -` - -const foldersByUserIDSql = ` -SELECT f.id, f.name, f.order, f.user_id -FROM public.folders f -WHERE f.user_id = $1 -` - -const createFolder = ` -INSERT INTO public.folders (name, "order", user_id, created_at, updated_at) -VALUES ($1, $2, $3, now(), now()) -RETURNING id -` - -const updateFolder = ` -UPDATE public.folders f -SET name = $2, "order" = $3 -WHERE f.id = $1 -` - -const deleteFolder = ` -DELETE FROM public.folders f -WHERE f.id = $1 -` - -// const bookmarksByFolderIDSQL = ` -// SELECT b.if, b.order, b.service_id -// FROM public.bookmarks b -// WHERE b.folder_id = $1 -// ` - -const savedSearchByIDSql = ` -SELECT id, user_id, name, search -FROM public.saved_searches -WHERE id = $1 -` - -const savedSearchesByUserIDSql = ` -SELECT ss.id, ss.user_id, ss.name, ss.search -FROM public.saved_searches ss -WHERE ss.user_id = $1 -` - -const createSavedSearchSql = ` -INSERT INTO public.saved_searches (user_id, name, search, created_at, updated_at) -VALUES ($1, $2, $3, now(), now()) -RETURNING id -` - -const deleteSavedSearchSql = ` -DELETE FROM public.saved_searches ss -WHERE ss.id = $1 -` - -const phonesByResourceIDSql = ` -SELECT p.id, p.number, p.service_type -FROM public.phones p -WHERE p.resource_id = $1 -` - -const eligibilitiesByIDsSql = ` -SELECT e.id, e.name, e.feature_rank -FROM public.eligibilities e -WHERE e.id = ANY ($1) -` - -const eligibilitiesByNamesSql = ` -SELECT e.id, e.name, e.feature_rank -FROM public.eligibilities e -WHERE e.name = ANY ($1) -` - -const eligibilitiesByServiceIDSql = ` -SELECT e.id, e.name, e.feature_rank -FROM public.eligibilities e -LEFT JOIN public.eligibilities_services es on e.id = es.eligibility_id -WHERE es.service_id = $1 -` - -const instructionsByServiceIDSql = ` -SELECT i.id, i.instruction -FROM public.instructions i -WHERE i.service_id = $1 -` -const documentsByServiceIDSql = ` -SELECT d.id, d.name, d.url, d.description -FROM public.documents d -LEFT JOIN public.documents_services ds on d.id = ds.document_id -WHERE ds.service_id = $1 -` -const categoryServiceCounts = ` -SELECT c.name, count(s.id) as services -FROM categories c -JOIN categories_services cs ON cs.category_id = c.id -JOIN services s ON s.id = cs.service_id -WHERE s.status = 1 -GROUP BY c.name -ORDER BY c.name asc -` - -const categoryResourceCounts = ` -SELECT c.name, count(r.id) as resources -FROM categories c -JOIN categories_resources cr ON cr.category_id = c.id -JOIN resources r ON r.id = cr.resource_id -WHERE r.status = 1 -GROUP BY c.name -ORDER BY c.name asc -` - -const categoriesByFeaturedSql = ` -SELECT id, name, top_level, featured -FROM public.categories -WHERE featured = true -` - -const subCategoriesByIDSql = ` -SELECT id, name, top_level, featured -FROM public.categories -WHERE id in (SELECT child_id from public.category_relationships WHERE parent_id = $1) -` - -const submitChangeRequest = ` -INSERT INTO public.change_requests (type, object_id, status, action, resource_id, created_at, updated_at) -VALUES ($1, $2, $3, $4, $5, now(), now())` - -const findBookmarksSql = ` -SELECT id, "order", user_id, folder_id, service_id, resource_id from public.bookmarks` - -const findBookmarksByUserIDSql = ` -SELECT id, "order", user_id, folder_id, service_id, resource_id from public.bookmarks -WHERE user_id=$1` - -const findBookmarksByIDSql = ` -SELECT id, "order", user_id, folder_id, service_id, resource_id from public.bookmarks -WHERE id=$1` - -const submitBookmark = ` -INSERT INTO public.bookmarks ("order", user_id, folder_id, resource_id, service_id, created_at, updated_at) -VALUES ($1, $2, $3, $4, $5, now(), now())` - -const updateBookmark = ` -UPDATE public.bookmarks SET "order" = $2, user_id = $3, folder_id= $4, resource_id = $5, service_id = $6 where id = $1` - -const deleteBookmarkByIDSql = ` -DELETE FROM public.bookmarks WHERE id = $1 -` - -const serviceByIDSql = ` -SELECT id, created_at, updated_at, name, long_description, eligibility, required_documents, fee, application_process, resource_id, verified_at, email, status, certified, program_id, interpretation_services, url, wait_time, contact_id, funding_id, alternate_name, certified_at, featured, source_attribution, internal_note, short_description -FROM public.services -WHERE id = $1 -` - -const approvedServicesByResourceIDSql = ` -SELECT id, created_at, updated_at, name, long_description, eligibility, required_documents, fee, application_process, resource_id, verified_at, email, status, certified, program_id, interpretation_services, url, wait_time, contact_id, funding_id, alternate_name, certified_at, featured, source_attribution, internal_note, short_description -FROM public.services -WHERE resource_id = $1 and status = 1 -` - -const programByIDSql = ` -SELECT id, name, alterante_name, description -FROM public.programs -WHERE id = $1 -` - -const resourceByIDSql = ` -SELECT id, name, short_description, long_description, website, verified_at, email, status, certified, alternate_name, legal_status, contact_id, funding_id, certified_at, featured, source_attribution, internal_note, updated_at -FROM public.resources -WHERE id = $1 -` -const scheduleByServiceIDSql = ` -SELECT s.id, s.hours_known -FROM public.schedules s -WHERE s.service_id = $1 LIMIT 1 -` - -const scheduleByResourceIDSql = ` -SELECT s.id, s.hours_known -FROM public.schedules s -WHERE s.resource_id = $1 LIMIT 1 -` - -const scheduleDaysByScheduleIDSql = ` -SELECT sd.id, sd.day, sd.opens_at, sd.closes_at, sd.open_time, sd.open_day, sd.close_time, sd.close_day -FROM public.schedule_days sd -WHERE sd.schedule_id = $1 -` - -const userByUserExternalIDSql = ` -SELECT u.id, u.name, u.organization, u.user_external_id, u.email -FROM public.users u -WHERE u.user_external_id = $1 -` diff --git a/internal/db/types.go b/internal/db/types.go deleted file mode 100644 index 75419f28..00000000 --- a/internal/db/types.go +++ /dev/null @@ -1,222 +0,0 @@ -package db - -import ( - "database/sql" - "database/sql/driver" - "encoding/json" - "errors" - "time" -) - -type Category struct { - Id int - Name string - TopLevel bool - Vocabulary string - Featured bool -} - -type ChangeRequest struct { - Id int - Type string - ObjectId int - Status int - Action int - ResourceId int - CreatedAt sql.NullTime - UpdatedAt sql.NullTime -} - -type Bookmark struct { - Id int - Order int - FolderID *int - ServiceID *int - ResourceID *int - UserID *int - CreatedAt sql.NullTime - UpdatedAt sql.NullTime -} - -type Service struct { - Id int - CreatedAt time.Time - UpdatedAt time.Time - Name sql.NullString - LongDescription sql.NullString - Eligibility sql.NullString - RequiredDocuments sql.NullString - Fee sql.NullString - ApplicationProcess sql.NullString - ResourceId sql.NullInt32 - VerifiedAt *time.Time - Email sql.NullString - Status sql.NullInt32 - Certified bool - ProgramId sql.NullInt32 - InterpretationServices sql.NullString - Url sql.NullString - WaitTime sql.NullString - ContactId sql.NullInt32 - FundingId sql.NullInt32 - AlternateName sql.NullString - CertifiedAt *time.Time - Featured sql.NullBool - SourceAttribution sql.NullInt32 - InternalNote sql.NullString - ShortDescription sql.NullString -} - -type Note struct { - Id int - Note sql.NullString - ResourceId sql.NullInt32 - ServiceId sql.NullInt32 - CreatedAt sql.NullTime - UpdatedAt sql.NullTime -} - -type Address struct { - Id int - Attention sql.NullString - Address1 string - Address2 sql.NullString - Address3 sql.NullString - Address4 sql.NullString - City string - StateProvince string - PostalCode string - ResourceId sql.NullInt32 - Latitude sql.NullFloat64 - Longitude sql.NullFloat64 - Online sql.NullBool - Region sql.NullString - Name sql.NullString - Description sql.NullString - Transportation sql.NullString - CreatedAt time.Time - UpdatedAt time.Time -} -type Eligibility struct { - Id int - Name sql.NullString - FeatureRank sql.NullInt32 - CreatedAt time.Time - UpdatedAt time.Time -} -type Instruction struct { - Id int - Instruction sql.NullString - CreatedAt time.Time - UpdatedAt time.Time -} -type Document struct { - Id int - Name sql.NullString - Url sql.NullString - Description sql.NullString - CreatedAt time.Time - UpdatedAt time.Time -} -type Resource struct { - Id int - AlternateName sql.NullString - Certified sql.NullBool - Email sql.NullString - LegalStatus sql.NullString - LongDescription sql.NullString - Name string - ShortDescription sql.NullString - Status sql.NullString - VerifiedAt *time.Time - Website sql.NullString - CertifiedAt *time.Time - Featured sql.NullBool - SourceAttribution sql.NullString - InternalNote sql.NullString - CreatedAt time.Time - UpdatedAt time.Time - ContactId sql.NullInt32 - FundingId sql.NullInt32 -} -type Program struct { - Id int - Name sql.NullString - AlternateName sql.NullString - Description sql.NullString - CreatedAt sql.NullTime - UpdatedAt sql.NullTime -} - -type Phone struct { - Id int - Number string - ServiceType string -} - -type Schedule struct { - Id int - HoursKnown bool - - ScheduleDays []*ScheduleDay -} - -type ScheduleDay struct { - Id int - Day string - OpensAt sql.NullInt32 - ClosesAt sql.NullInt32 - OpenTime sql.NullTime - OpenDay sql.NullString - CloseTime sql.NullTime - CloseDay sql.NullString -} -type CategoryCount struct { - CategoryName string - Count int -} - -type Folder struct { - Id int - Name string - Order int - UserId int -} - -type SavedSearchQuery struct { - Eligibilities []int `json:"eligibilities"` - Categories []int `json:"categories"` - Lat *float64 `json:"lat"` - Lng *float64 `json:"lng"` - Query string `json:"query"` -} - -// Methods needed for automatic serialization/deserialization to JSONB column. -// https://www.alexedwards.net/blog/using-postgresql-jsonb - -func (s SavedSearchQuery) Value() (driver.Value, error) { - return json.Marshal(s) -} - -func (s *SavedSearchQuery) Scan(value interface{}) error { - b, ok := value.([]byte) - if !ok { - return errors.New("type assertion to []byte failed") - } - return json.Unmarshal(b, &s) -} - -type SavedSearch struct { - Id int - UserId int - Name string - Search SavedSearchQuery -} - -type User struct { - Id int - Name string - Organization string - UserExternalId string - Email string -} diff --git a/internal/db/users.go b/internal/db/users.go new file mode 100644 index 00000000..83ca439c --- /dev/null +++ b/internal/db/users.go @@ -0,0 +1,39 @@ +package db + +import ( + "database/sql" + "fmt" +) + +type User struct { + Id int + Name string + Organization string + UserExternalId string + Email string +} + +const userByUserExternalIDSql = ` +SELECT u.id, u.name, u.organization, u.user_external_id, u.email +FROM public.users u +WHERE u.user_external_id = $1 +` + +func (m *Manager) GetUserByUserExternalID(userExternalId string) *User { + row := m.DB.QueryRow(userByUserExternalIDSql, userExternalId) + return scanUser(row) +} +func scanUser(row *sql.Row) *User { + var user User + err := row.Scan(&user.Id, &user.Name, &user.Organization, &user.UserExternalId, &user.Email) + if err != nil { + switch err { + case sql.ErrNoRows: + fmt.Println("No rows were returned!") + return nil + default: + panic(err) + } + } + return &user +}