Skip to content
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
33 changes: 32 additions & 1 deletion handlers/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"html/template"
"net/http"
"strconv"
"strings"
"time"

Expand Down Expand Up @@ -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)
}
}

Expand Down
68 changes: 68 additions & 0 deletions integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand All @@ -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) {
Expand All @@ -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 {
Expand Down
10 changes: 5 additions & 5 deletions migrations/0000_createDbs.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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
);
2 changes: 2 additions & 0 deletions models/datastore.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
51 changes: 51 additions & 0 deletions models/note.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package models

import (
"errors"
"time"
)

Expand All @@ -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(
Expand All @@ -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
}
32 changes: 32 additions & 0 deletions static/js/base.js
Original file line number Diff line number Diff line change
@@ -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";
};

});
1 change: 1 addition & 0 deletions templates/base.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
<script src="//cdn.muicss.com/mui-0.9.36/js/mui.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.2/moment.min.js"></script>
<script src="/static/js/base.js"></script>
{{ block "js" . }} {{ end }}
</head>
<body>
Expand Down