From 72f4cb7a9910e95ee99e555d447e3a2c2c700441 Mon Sep 17 00:00:00 2001 From: G Date: Tue, 18 Sep 2018 19:42:33 -0700 Subject: [PATCH] bonded and tested --- handlers/handlers.go | 33 ++++++++++++++++- integration_test.go | 68 +++++++++++++++++++++++++++++++++++ migrations/0000_createDbs.sql | 10 +++--- models/datastore.go | 2 ++ models/note.go | 51 ++++++++++++++++++++++++++ static/js/base.js | 32 +++++++++++++++++ templates/base.tmpl | 1 + 7 files changed, 191 insertions(+), 6 deletions(-) create mode 100644 static/js/base.js diff --git a/handlers/handlers.go b/handlers/handlers.go index c59a045..1e04b41 100644 --- a/handlers/handlers.go +++ b/handlers/handlers.go @@ -5,6 +5,7 @@ import ( "fmt" "html/template" "net/http" + "strconv" "strings" "time" @@ -299,8 +300,38 @@ func HandleNoteApiRequest( fmt.Fprint(responseWriter, string(noteString)) + case http.MethodDelete: + + id, err := strconv.ParseInt(request.URL.Query().Get("id"), 10, 64) + if err != nil { + http.Error(responseWriter, err.Error(), http.StatusBadRequest) + return + } + + noteId := models.NoteId(id) + + noteMap, err := env.Db.GetUsersNotes(userId) + if err != nil { + http.Error(responseWriter, err.Error(), http.StatusInternalServerError) + return + } + + if _, ok := noteMap[noteId]; !ok { + errorString := "No note with that Id written by you was found" + http.Error(responseWriter, errorString, http.StatusBadRequest) + return + } + + err = env.Db.DeleteNoteById(noteId) + if err != nil { + http.Error(responseWriter, err.Error(), http.StatusInternalServerError) + return + } + + responseWriter.WriteHeader(http.StatusOK) + default: - respondWithMethodNotAllowed(responseWriter, http.MethodGet, http.MethodPost) + respondWithMethodNotAllowed(responseWriter, http.MethodGet, http.MethodPost, http.MethodDelete) } } diff --git a/integration_test.go b/integration_test.go index 9aa0e8f..b5900c5 100644 --- a/integration_test.go +++ b/integration_test.go @@ -8,10 +8,15 @@ import ( "net/http" "net/http/cookiejar" "net/http/httptest" + "strconv" + // "net/url" + // "io" + "io/ioutil" "path/filepath" "reflect" "runtime" "testing" + "time" "github.com/atmiguel/cerealnotes/handlers" "github.com/atmiguel/cerealnotes/models" @@ -145,6 +150,59 @@ func TestAuthenticatedFlow(t *testing.T) { equals(t, http.StatusCreated, resp.StatusCode) } + // Delete note + { + mockDb.Func_GetUsersNotes = func(userId models.UserId) (models.NoteMap, error) { + return models.NoteMap(map[models.NoteId]*models.Note{ + models.NoteId(noteIdAsInt): &models.Note{ + AuthorId: models.UserId(userIdAsInt), + Content: content, + CreationTime: time.Now(), + }, + }), nil + } + + mockDb.Func_DeleteNoteById = func(noteid models.NoteId) error { + if int64(noteid) == noteIdAsInt { + return nil + } + + return errors.New("Somehow you didn't get the correct error") + } + + resp, err := sendDeleteRequest(client, server.URL+paths.NoteApi+"?id="+strconv.FormatInt(noteIdAsInt, 10)) + ok(t, err) + // printBody(resp) + + equals(t, http.StatusOK, resp.StatusCode) + } + +} + +// func sendDeleteRequest(client *http.Client, myUrl string, contentType string, body io.Reader) (resp *http.Response, err error) { +func sendDeleteRequest(client *http.Client, myUrl string) (resp *http.Response, err error) { + + req, err := http.NewRequest("DELETE", myUrl, nil) + + if err != nil { + return nil, err + } + + return client.Do(req) + +} + +func printBody(resp *http.Response) { + buf, bodyErr := ioutil.ReadAll(resp.Body) + if bodyErr != nil { + fmt.Print("bodyErr ", bodyErr.Error()) + return + } + + rdr1 := ioutil.NopCloser(bytes.NewBuffer(buf)) + rdr2 := ioutil.NopCloser(bytes.NewBuffer(buf)) + fmt.Printf("BODY: %q", rdr1) + resp.Body = rdr2 } // Helpers @@ -155,6 +213,8 @@ type DiyMockDataStore struct { Func_StoreNewUser func(string, *models.EmailAddress, string) error Func_AuthenticateUserCredentials func(*models.EmailAddress, string) error Func_GetIdForUserWithEmailAddress func(*models.EmailAddress) (models.UserId, error) + Func_GetUsersNotes func(models.UserId) (models.NoteMap, error) + Func_DeleteNoteById func(models.NoteId) error } func (mock *DiyMockDataStore) StoreNewNote(note *models.Note) (models.NoteId, error) { @@ -177,6 +237,14 @@ func (mock *DiyMockDataStore) GetIdForUserWithEmailAddress(email *models.EmailAd return mock.Func_GetIdForUserWithEmailAddress(email) } +func (mock *DiyMockDataStore) GetUsersNotes(userId models.UserId) (models.NoteMap, error) { + return mock.Func_GetUsersNotes(userId) +} + +func (mock *DiyMockDataStore) DeleteNoteById(noteId models.NoteId) error { + return mock.Func_DeleteNoteById(noteId) +} + // assert fails the test if the condition is false. func assert(tb testing.TB, condition bool, msg string, v ...interface{}) { if !condition { diff --git a/migrations/0000_createDbs.sql b/migrations/0000_createDbs.sql index 9774b6c..f272b35 100644 --- a/migrations/0000_createDbs.sql +++ b/migrations/0000_createDbs.sql @@ -19,17 +19,17 @@ CREATE TABLE IF NOT EXISTS publication ( CREATE TABLE IF NOT EXISTS note ( id bigserial PRIMARY KEY, - author_id bigint references app_user(id) NOT NULL, + author_id bigint references app_user(id) ON DELETE CASCADE NOT NULL, content text NOT NULL, creation_time timestamp NOT NULL ); CREATE TABLE IF NOT EXISTS note_to_publication_relationship ( - note_id bigint PRIMARY KEY references note(id), - publication_id bigint references publication(id) NOT NULL + note_id bigint PRIMARY KEY references note(id) ON DELETE CASCADE, + publication_id bigint references publication(id) ON DELETE CASCADE NOT NULL ); CREATE TABLE IF NOT EXISTS note_to_category_relationship ( - note_id bigint PRIMARY KEY references note(id), - category category_type NOT NULL + note_id bigint PRIMARY KEY references note(id) ON DELETE CASCADE, + type category_type NOT NULL ); \ No newline at end of file diff --git a/models/datastore.go b/models/datastore.go index 5779ee9..b3b4d3b 100644 --- a/models/datastore.go +++ b/models/datastore.go @@ -24,6 +24,8 @@ type Datastore interface { StoreNewUser(string, *EmailAddress, string) error AuthenticateUserCredentials(*EmailAddress, string) error GetIdForUserWithEmailAddress(*EmailAddress) (UserId, error) + GetUsersNotes(UserId) (NoteMap, error) + DeleteNoteById(NoteId) error } type DB struct { diff --git a/models/note.go b/models/note.go index 7b3b47e..6fbcb75 100644 --- a/models/note.go +++ b/models/note.go @@ -1,6 +1,7 @@ package models import ( + "errors" "time" ) @@ -12,6 +13,8 @@ type Note struct { CreationTime time.Time `json:"creationTime"` } +var NoNoteFoundError = errors.New("No note with that information could be found") + // DB methods func (db *DB) StoreNewNote( @@ -33,3 +36,51 @@ func (db *DB) StoreNewNote( } return NoteId(noteId), nil } + +func (db *DB) GetUsersNotes(userId UserId) (NoteMap, error) { + noteMap := make(map[NoteId]*Note) + + { + sqlQuery := ` + SELECT id, author_id, content, creation_time FROM note + WHERE author_id = $1` + rows, err := db.Query(sqlQuery, int64(userId)) + if err != nil { + return nil, convertPostgresError(err) + } + defer rows.Close() + + for rows.Next() { + var tempId int64 + tempNote := &Note{} + if err := rows.Scan(&tempId, &tempNote.AuthorId, &tempNote.Content, &tempNote.CreationTime); err != nil { + return nil, convertPostgresError(err) + } + + noteMap[NoteId(tempId)] = tempNote + } + } + + return noteMap, nil +} + +func (db *DB) DeleteNoteById(noteId NoteId) error { + sqlQuery := ` + DELETE FROM note + WHERE id = $1` + + num, err := db.execNoResults(sqlQuery, int64(noteId)) + if err != nil { + return err + } + + if num == 0 { + return NoNoteFoundError + } + + if num != 1 { + return errors.New("Somewhere we more than 1 note was deleted") + } + + return nil +} diff --git a/static/js/base.js b/static/js/base.js new file mode 100644 index 0000000..2143555 --- /dev/null +++ b/static/js/base.js @@ -0,0 +1,32 @@ +$(function() { + // http://stepansuvorov.com/blog/2014/04/jquery-put-and-delete/ + jQuery.each( [ "put", "delete" ], function( i, method ) { + jQuery[ method ] = function( url, data, callback, type ) { + if ( jQuery.isFunction( data ) ) { + type = type || callback; + callback = data; + data = undefined; + } + + return jQuery.ajax({ + url: url, + type: method, + dataType: type, + data: data, + success: callback + }); + }; + }); + + jQuery.prototype.getDOM = function() { + if (this.length === 1) { + return this[0]; + } + + if (this.length === 0) { + throw "jQuery object is empty" + } + throw "jQuery Object contains more than 1 object"; + }; + +}); \ No newline at end of file diff --git a/templates/base.tmpl b/templates/base.tmpl index 02de033..6a2d76c 100644 --- a/templates/base.tmpl +++ b/templates/base.tmpl @@ -20,6 +20,7 @@ + {{ block "js" . }} {{ end }}