Skip to content
This repository has been archived by the owner on Nov 15, 2022. It is now read-only.

Issue 41 #118

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions devops/migrations/20180523161835-create_subscribers_table.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@

-- +migrate Up
CREATE TABLE subscriptions (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@leandro-lugaresi @eminetto quando usamos serviços como o MailChimp e semelhantes, a gente precisa manter os dados das pessoas? ou o serviço lida sozinho com isso?

id SERIAL PRIMARY KEY,
organization_id INTEGER NOT NULL REFERENCES organizations (id) ON UPDATE CASCADE ON DELETE RESTRICT,
name VARCHAR(100) NOT NULL,
email VARCHAR(255) NOT NULL,
phone VARCHAR(45) NOT NULL,
date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);

-- +migrate Down
DROP TABLE subscriptions;
5 changes: 5 additions & 0 deletions server/cmd/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ func serveCmdFunc(cmd *cobra.Command, args []string) {

oR := repo.NewOrganizationRepository(conn)
nR := repo.NewNeedRepository(conn)
sR := repo.NewSubscriptionRepository(conn)

needResponseRepo := repo.NewNeedResponseRepository(conn)

Expand Down Expand Up @@ -83,6 +84,10 @@ func serveCmdFunc(cmd *cobra.Command, args []string) {
negroni.WrapFunc(handlers.DeleteOrganizationImageHandler(oR)),
)).Methods("DELETE")

v1.Path("/organization/{id:[0-9]+}/subscribe").HandlerFunc(
handlers.CreateSubscriptionHandler(sR),
).Methods("POST")

v1.HandleFunc("/need/{id}", handlers.GetNeedHandler(nR, oR)).Methods("GET")

v1.Path("/need").Handler(authMiddleware.With(
Expand Down
93 changes: 93 additions & 0 deletions server/db/repo/subscription.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package repo

import (
"database/sql"
"errors"
"fmt"
"strings"

"github.com/Coderockr/vitrine-social/server/model"
"github.com/jmoiron/sqlx"
)

// SubscriptionRepository is a implementation for Postgres
type SubscriptionRepository struct {
db *sqlx.DB
orgRepo *OrganizationRepository
}

// NewSubscriptionRepository creates a new repository
func NewSubscriptionRepository(db *sqlx.DB) *SubscriptionRepository {
return &SubscriptionRepository{
db: db,
orgRepo: NewOrganizationRepository(db),
}
}

// Create new subscription
func (r *SubscriptionRepository) Create(s model.Subscription) (model.Subscription, error) {
s, err := validate(r, s)

if err != nil {
return s, err
}

row := r.db.QueryRow(
`INSERT INTO subscriptions (organization_id, name, email, phone)
VALUES($1, $2, $3, $4)
RETURNING id
`,
s.OrganizationID,
s.Name,
s.Email,
s.Phone,
)

err = row.Scan(&s.ID)

if err != nil {
return s, err
}

return s, nil
}

func validate(r *SubscriptionRepository, s model.Subscription) (model.Subscription, error) {
s.Name = strings.TrimSpace(s.Name)
if len(s.Name) == 0 {
return s, errors.New("Deve ser informado um nome para a Inscrição")
}

s.Email = strings.TrimSpace(s.Email)
if len(s.Email) == 0 {
return s, errors.New("Deve ser informado um email para a Inscrição")
}

s.Phone = strings.TrimSpace(s.Phone)
if len(s.Phone) == 0 {
return s, errors.New("Deve ser informado um telefone para a Inscrição")
}

_, err := getBaseOrganization(r.db, s.OrganizationID)
switch {
case err == sql.ErrNoRows:
return s, fmt.Errorf("Não foi encontrada Organização com ID: %d", s.OrganizationID)
case err != nil:
return s, err
}

var found int64
err = r.db.QueryRow(`
SELECT COUNT(1) as found
FROM subscriptions
WHERE organization_id = $1 AND email LIKE $2`,
s.OrganizationID,
s.Email,
).Scan(&found)

if found > 0 {
return s, fmt.Errorf("Este email já está inscrito para a Organização %d", s.OrganizationID)
}

return s, nil
}
53 changes: 53 additions & 0 deletions server/handlers/subscription.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package handlers

import (
"fmt"
"net/http"
"strconv"
"time"

"github.com/Coderockr/vitrine-social/server/model"
"github.com/gorilla/mux"
)

type (
// SubscriptionRepository represet operations for subscription repository.
SubscriptionRepository interface {
Create(model.Subscription) (model.Subscription, error)
}
)

// CreateSubscriptionHandler create a new subscription
func CreateSubscriptionHandler(repo SubscriptionRepository) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
urlVars := mux.Vars(r)
id, err := strconv.ParseInt(urlVars["id"], 10, 64)
if err != nil {
HandleHTTPError(w, http.StatusBadRequest, fmt.Errorf("Não foi possível entender o número: %s", urlVars["id"]))
return
}

var bodyVars map[string]string
err = requestToJSONObject(r, &bodyVars)
if err != nil {
HandleHTTPError(w, http.StatusBadRequest, err)
return
}

now := time.Now()
s, err := repo.Create(model.Subscription{
OrganizationID: id,
Email: bodyVars["email"],
Name: bodyVars["name"],
Phone: bodyVars["phone"],
Date: &now,
})

if err != nil {
HandleHTTPError(w, http.StatusBadRequest, err)
return
}

HandleHTTPSuccess(w, map[string]int64{"id": s.ID})
}
}
113 changes: 113 additions & 0 deletions server/handlers/subscription_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package handlers_test

import (
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"strings"
"testing"

"github.com/Coderockr/vitrine-social/server/handlers"
"github.com/Coderockr/vitrine-social/server/model"
"github.com/gorilla/mux"
"github.com/stretchr/testify/require"
)

type (
subscriptionRepositoryMock struct {
CreateFN func(model.Subscription) (model.Subscription, error)
}
)

func TestCreateSubscriptionHandler(t *testing.T) {
type params struct {
organizationID string
repository handlers.SubscriptionRepository
}

tests := map[string]struct {
body string
status int
response string
params params
}{
"should fail beacuse trying to create without parameters": {
body: ``,
status: http.StatusBadRequest,
response: ``,
params: params{
organizationID: "1",
repository: &subscriptionRepositoryMock{
CreateFN: func(model.Subscription) (model.Subscription, error) {
s := model.Subscription{}
return s, errors.New("Deve ser informado um nome para a Inscrição")
},
},
},
},
"should fail beacuse trying to create with no valid organization": {
body: ``,
status: http.StatusBadRequest,
response: ``,
params: params{
organizationID: "5",
repository: &subscriptionRepositoryMock{
CreateFN: func(model.Subscription) (model.Subscription, error) {
s := model.Subscription{}
return s, fmt.Errorf("Não foi encontrada Organização com ID: 5")
},
},
},
},
"should success beacuse the right values were sent": {
body: `{
"name": "Coderockr Test",
"email": "test@coderockr.com",
"phone": "(54) 99999-9999"
}`,
status: http.StatusOK,
response: `{
"id": 1
}`,
params: params{
organizationID: "1",
repository: &subscriptionRepositoryMock{
CreateFN: func(model.Subscription) (model.Subscription, error) {
s := model.Subscription{
ID: 1,
Name: "Coderockr Test",
Email: "test@coderockr.com",
Phone: "(54) 99999-9999",
}
return s, nil
},
},
},
},
}

for name, v := range tests {
t.Run(name, func(t *testing.T) {
r, _ := http.NewRequest("POST", "/v1/organization/"+v.params.organizationID+"/subscribe", strings.NewReader(v.body))
r = mux.SetURLVars(r, map[string]string{"id": v.params.organizationID})

resp := httptest.NewRecorder()

handlers.CreateSubscriptionHandler(v.params.repository)(resp, r)

result := resp.Result()
body, _ := ioutil.ReadAll(result.Body)

if len(v.response) > 0 {
require.JSONEq(t, v.response, string(body))
}
require.Equal(t, v.status, resp.Code)
})
}
}

func (r *subscriptionRepositoryMock) Create(s model.Subscription) (model.Subscription, error) {
return r.CreateFN(s)
}
10 changes: 10 additions & 0 deletions server/model/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,16 @@ type Category struct {
Icon string `valid:"required" db:"icon"`
}

// Subscription relacionada a uma organização
type Subscription struct {
ID int64 `valid:"required" db:"id"`
OrganizationID int64 `valid:"required" db:"organization_id"`
Name string `valid:"required" db:"name"`
Email string `valid:"required" db:"email"`
Phone string `valid:"required" db:"phone"`
Date *time.Time `db:"date"`
}

func (s *needStatus) Scan(src interface{}) error {
var str string

Expand Down