├── api.go ├── assignment.postman_collection ├── endpoints_test.go ├── entry.go ├── readme.md └── sql-dump.sql /api.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "database/sql" 6 | "encoding/csv" 7 | "encoding/json" 8 | "fmt" 9 | "io/ioutil" 10 | "net/http" 11 | "strconv" 12 | "time" 13 | 14 | _ "github.com/go-sql-driver/mysql" 15 | "github.com/gorilla/mux" 16 | ) 17 | 18 | var host = "http://localhost" 19 | var port = "12345" 20 | var connectionString = "root:7798775575@tcp(127.0.0.1:3306)/online_address_book?charset=utf8&parseTime=True&loc=Local" 21 | 22 | func main() { 23 | 24 | var router *mux.Router 25 | router = mux.NewRouter().StrictSlash(true) 26 | apiRouter := router.PathPrefix("/api").Subrouter() // /api will give access to all the API endpoints 27 | apiRouter.PathPrefix("/entries").HandlerFunc(GetEntries).Methods("GET") // /api/entries returns listing all the entries 28 | apiRouter.PathPrefix("/entry").HandlerFunc(GetEntryByID).Methods("GET") // GET /api/entry?id=1 returns the entry with id 1. 29 | apiRouter.PathPrefix("/entry").HandlerFunc(CreateEntry).Methods("POST") // POST /api/entry creates an entry 30 | apiRouter.PathPrefix("/entry").HandlerFunc(UpdateEntry).Methods("PUT") // PUT /api/entry updates an entry 31 | apiRouter.PathPrefix("/entry").HandlerFunc(DeleteEntry).Methods("DELETE") // DELETE /api/entry deletes an entry 32 | apiRouter.PathPrefix("/upload-entries-CSV").HandlerFunc(UploadEntriesThroughCSV).Methods("POST") // POST /api/upload-entries-CSV imports CSV into the database 33 | apiRouter.PathPrefix("/download-entries-CSV").HandlerFunc(DownloadEntriesToCSV).Methods("GET") //GET /api/download-entries-CSV exports CSV from the database 34 | fmt.Println("Listening on port :12345") 35 | http.ListenAndServe(":"+port, router) 36 | 37 | } 38 | 39 | // GetEntries : Get All Entries 40 | // URL : /entries 41 | // Parameters: none 42 | // Method: GET 43 | // Output: JSON Encoded Entries object if found else JSON Encoded Exception. 44 | func GetEntries(w http.ResponseWriter, r *http.Request) { 45 | db, err := sql.Open("mysql", connectionString) 46 | defer db.Close() 47 | if err != nil { 48 | respondWithError(w, http.StatusInternalServerError, "Could not connect to the database") 49 | return 50 | } 51 | var entries []entry 52 | rows, err := db.Query("SELECT * from address_book;") 53 | if err != nil { 54 | respondWithError(w, http.StatusInternalServerError, "Something went wrong.") 55 | return 56 | } 57 | defer rows.Close() 58 | for rows.Next() { 59 | var eachEntry entry 60 | var id int 61 | var firstName sql.NullString 62 | var lastName sql.NullString 63 | var emailAddress sql.NullString 64 | var phoneNumber sql.NullString 65 | 66 | err = rows.Scan(&id, &firstName, &lastName, &emailAddress, &phoneNumber) 67 | eachEntry.ID = id 68 | eachEntry.FirstName = firstName.String 69 | eachEntry.LastName = lastName.String 70 | eachEntry.EmailAddress = emailAddress.String 71 | eachEntry.PhoneNumber = phoneNumber.String 72 | entries = append(entries, eachEntry) 73 | } 74 | respondWithJSON(w, http.StatusOK, entries) 75 | } 76 | 77 | // GetEntryByID - Get Entry By ID 78 | // URL : /entries?id=1 79 | // Parameters: int id 80 | // Method: GET 81 | // Output: JSON Encoded Address Book Entry object if found else JSON Encoded Exception. 82 | func GetEntryByID(w http.ResponseWriter, r *http.Request) { 83 | db, err := sql.Open("mysql", connectionString) 84 | defer db.Close() 85 | if err != nil { 86 | respondWithError(w, http.StatusInternalServerError, "Could not connect to the database") 87 | return 88 | } 89 | id := r.URL.Query().Get("id") 90 | var firstName sql.NullString 91 | var lastName sql.NullString 92 | var emailAddress sql.NullString 93 | var phoneNumber sql.NullString 94 | err = db.QueryRow("SELECT first_name, last_name, email_address, phone_number from address_book where id=?", id).Scan(&firstName, &lastName, &emailAddress, &phoneNumber) 95 | switch { 96 | case err == sql.ErrNoRows: 97 | respondWithError(w, http.StatusBadRequest, "No entry found with the id="+id) 98 | return 99 | case err != nil: 100 | respondWithError(w, http.StatusInternalServerError, "Some problem occurred.") 101 | return 102 | default: 103 | var eachEntry entry 104 | eachEntry.ID, _ = strconv.Atoi(id) 105 | eachEntry.FirstName = firstName.String 106 | eachEntry.LastName = lastName.String 107 | eachEntry.EmailAddress = emailAddress.String 108 | eachEntry.PhoneNumber = phoneNumber.String 109 | respondWithJSON(w, http.StatusOK, eachEntry) 110 | } 111 | 112 | } 113 | 114 | // CreateEntry - Create Entry 115 | // URL : /entry 116 | // Method: POST 117 | // Body: 118 | /* 119 | * { 120 | * "first_name":"John", 121 | * "last_name":"Doe", 122 | * "email_address":"john.doe@gmail.com", 123 | * "phone_number":"1234567890", 124 | } 125 | */ 126 | // Output: JSON Encoded Address Book Entry object if created else JSON Encoded Exception. 127 | func CreateEntry(w http.ResponseWriter, r *http.Request) { 128 | db, err := sql.Open("mysql", connectionString) 129 | defer db.Close() 130 | if err != nil { 131 | respondWithError(w, http.StatusInternalServerError, "Could not connect to the database") 132 | return 133 | } 134 | decoder := json.NewDecoder(r.Body) 135 | var entry entry 136 | err = decoder.Decode(&entry) 137 | if err != nil { 138 | respondWithError(w, http.StatusInternalServerError, "Some problem occurred.") 139 | return 140 | } 141 | statement, err := db.Prepare("insert into address_book (first_name, last_name, email_address, phone_number) values(?,?,?,?)") 142 | if err != nil { 143 | respondWithError(w, http.StatusInternalServerError, "Some problem occurred.") 144 | return 145 | } 146 | defer statement.Close() 147 | res, err := statement.Exec(entry.FirstName, entry.LastName, entry.EmailAddress, entry.PhoneNumber) 148 | if err != nil { 149 | respondWithError(w, http.StatusInternalServerError, "There was problem entering the entry.") 150 | return 151 | } 152 | if rowsAffected, _ := res.RowsAffected(); rowsAffected == 1 { 153 | id, _ := res.LastInsertId() 154 | entry.ID = int(id) 155 | respondWithJSON(w, http.StatusOK, entry) 156 | } 157 | 158 | } 159 | 160 | // UpdateEntry - Update Entry 161 | // URL : /entry 162 | // Method: PUT 163 | // Body: 164 | /* 165 | * { 166 | * "id":1, 167 | * "first_name":"Krish", 168 | * "last_name":"Bhanushali", 169 | * "email_address":"krishsb2405@gmail.com", 170 | * "phone_number":"7798775575", 171 | } 172 | */ 173 | // Output: JSON Encoded Address Book Entry object if updated else JSON Encoded Exception. 174 | func UpdateEntry(w http.ResponseWriter, r *http.Request) { 175 | db, err := sql.Open("mysql", connectionString) 176 | defer db.Close() 177 | if err != nil { 178 | respondWithError(w, http.StatusInternalServerError, "Could not connect to the database") 179 | return 180 | } 181 | decoder := json.NewDecoder(r.Body) 182 | var entry entry 183 | err = decoder.Decode(&entry) 184 | if err != nil { 185 | respondWithError(w, http.StatusInternalServerError, "Some problem occurred.") 186 | return 187 | } 188 | 189 | statement, err := db.Prepare("update address_book set first_name=?, last_name=?, email_address=?, phone_number=? where id=?") 190 | if err != nil { 191 | respondWithError(w, http.StatusInternalServerError, "Some problem occurred.") 192 | return 193 | } 194 | defer statement.Close() 195 | res, err := statement.Exec(entry.FirstName, entry.LastName, entry.EmailAddress, entry.PhoneNumber, entry.ID) 196 | if err != nil { 197 | respondWithError(w, http.StatusInternalServerError, "There was problem entering the entry.") 198 | return 199 | } 200 | if rowsAffected, _ := res.RowsAffected(); rowsAffected == 1 { 201 | respondWithJSON(w, http.StatusOK, entry) 202 | } 203 | } 204 | 205 | // DeleteEntry - Delete Entry By ID 206 | // URL : /entries?id=1 207 | // Parameters: int id 208 | // Method: DELETE 209 | // Output: JSON Encoded Address Book Entry object if found & deleted else JSON Encoded Exception. 210 | func DeleteEntry(w http.ResponseWriter, r *http.Request) { 211 | db, err := sql.Open("mysql", connectionString) 212 | defer db.Close() 213 | if err != nil { 214 | respondWithError(w, http.StatusInternalServerError, "Could not connect to the database") 215 | return 216 | } 217 | 218 | id := r.URL.Query().Get("id") 219 | var firstName sql.NullString 220 | var lastName sql.NullString 221 | var emailAddress sql.NullString 222 | var phoneNumber sql.NullString 223 | err = db.QueryRow("SELECT first_name, last_name, email_address, phone_number from address_book where id=?", id).Scan(&firstName, &lastName, &emailAddress, &phoneNumber) 224 | switch { 225 | case err == sql.ErrNoRows: 226 | respondWithError(w, http.StatusBadRequest, "No entry found with the id="+id) 227 | return 228 | case err != nil: 229 | respondWithError(w, http.StatusInternalServerError, "Some problem occurred.") 230 | return 231 | default: 232 | 233 | res, err := db.Exec("DELETE from address_book where id=?", id) 234 | if err != nil { 235 | respondWithError(w, http.StatusInternalServerError, "Some problem occurred.") 236 | return 237 | } 238 | count, err := res.RowsAffected() 239 | if err != nil { 240 | respondWithError(w, http.StatusInternalServerError, "Some problem occurred.") 241 | return 242 | } 243 | if count == 1 { 244 | var eachEntry entry 245 | eachEntry.ID, _ = strconv.Atoi(id) 246 | eachEntry.FirstName = firstName.String 247 | eachEntry.LastName = lastName.String 248 | eachEntry.EmailAddress = emailAddress.String 249 | eachEntry.PhoneNumber = phoneNumber.String 250 | 251 | respondWithJSON(w, http.StatusOK, eachEntry) 252 | return 253 | } 254 | 255 | } 256 | } 257 | 258 | //UploadEntriesThroughCSV - Reads CSV, Parses the CSV and creates all the entries in the database 259 | func UploadEntriesThroughCSV(w http.ResponseWriter, r *http.Request) { 260 | //var buf bytes.Buffer 261 | file, _, err := r.FormFile("csvFile") 262 | if err != nil { 263 | respondWithError(w, http.StatusBadRequest, "Something went wrong while opening the CSV.") 264 | return 265 | } 266 | defer file.Close() 267 | 268 | reader := csv.NewReader(file) 269 | reader.FieldsPerRecord = 5 270 | csvData, err := reader.ReadAll() 271 | if err != nil { 272 | respondWithError(w, http.StatusBadRequest, "Something went wrong while parsing the CSV.") 273 | return 274 | } 275 | 276 | var entry entry 277 | 278 | for _, eachEntry := range csvData { 279 | if eachEntry[1] != "first_name" { 280 | entry.FirstName = eachEntry[1] 281 | } 282 | if eachEntry[2] != "last_name" { 283 | entry.LastName = eachEntry[2] 284 | } 285 | if eachEntry[3] != "email_address" { 286 | entry.EmailAddress = eachEntry[3] 287 | } 288 | if eachEntry[4] != "phone_number" { 289 | entry.PhoneNumber = eachEntry[4] 290 | } 291 | if entry.FirstName != "" && entry.LastName != "" && entry.EmailAddress != "" && entry.PhoneNumber != "" { 292 | jsonString, err := json.Marshal(entry) 293 | if err != nil { 294 | respondWithError(w, http.StatusBadRequest, "Something went wrong while parsing the CSV.") 295 | return 296 | } 297 | req, err := http.NewRequest("POST", host+":"+port+"/api/entry", bytes.NewBuffer(jsonString)) 298 | if err != nil { 299 | respondWithError(w, http.StatusBadRequest, "Something went wrong while requesting to the Creation endpoint.") 300 | return 301 | } 302 | req.Header.Set("Content-Type", "application/json") 303 | client := &http.Client{} 304 | resp, err := client.Do(req) 305 | if err != nil { 306 | respondWithError(w, http.StatusBadRequest, "Something went wrong while requesting to the Creation endpoint.") 307 | return 308 | } 309 | defer resp.Body.Close() 310 | if resp.Status == strconv.Itoa(http.StatusBadRequest) { 311 | respondWithError(w, http.StatusBadRequest, "Something went wrong while inserting.") 312 | return 313 | } else if resp.Status == strconv.Itoa(http.StatusInternalServerError) { 314 | respondWithError(w, http.StatusInternalServerError, "Something went wrong while inserting.") 315 | return 316 | } 317 | } 318 | } 319 | respondWithJSON(w, http.StatusOK, map[string]string{"success": "Upload successful"}) 320 | } 321 | 322 | //DownloadEntriesToCSV - GetAllEntries, creates a CSV and downloads the CSV. 323 | func DownloadEntriesToCSV(w http.ResponseWriter, r *http.Request) { 324 | response, err := http.Get(host + ":" + port + "/api/entries") 325 | if err != nil { 326 | respondWithError(w, http.StatusInternalServerError, "Somehow host could not be reached.") 327 | return 328 | } 329 | data, _ := ioutil.ReadAll(response.Body) 330 | var entries []entry 331 | err = json.Unmarshal(data, &entries) 332 | if err != nil { 333 | respondWithError(w, http.StatusInternalServerError, "Unable to unmarshal data.") 334 | return 335 | } 336 | b := &bytes.Buffer{} 337 | t := time.Now().Unix() 338 | fileName := "address-book-" + strconv.FormatInt(t, 10) + ".csv" 339 | writer := csv.NewWriter(b) 340 | heading := []string{"id", "first_name", "last_name", "email_address", "phone_number"} 341 | writer.Write(heading) 342 | for _, eachEntry := range entries { 343 | var record []string 344 | record = append(record, strconv.Itoa(eachEntry.ID)) 345 | record = append(record, eachEntry.FirstName) 346 | record = append(record, eachEntry.LastName) 347 | record = append(record, eachEntry.EmailAddress) 348 | record = append(record, eachEntry.PhoneNumber) 349 | writer.Write(record) 350 | } 351 | writer.Flush() 352 | w.Header().Set("Content-Type", "text/csv") // setting the content type header to text/csv 353 | w.Header().Set("Content-Disposition", "attachment;filename="+fileName) 354 | w.WriteHeader(http.StatusOK) 355 | w.Write(b.Bytes()) 356 | return 357 | } 358 | 359 | // RespondWithError is called on an error to return info regarding error 360 | func respondWithError(w http.ResponseWriter, code int, message string) { 361 | respondWithJSON(w, code, map[string]string{"error": message}) 362 | } 363 | 364 | // Called for responses to encode and send json data 365 | func respondWithJSON(w http.ResponseWriter, code int, payload interface{}) { 366 | //encode payload to json 367 | response, _ := json.Marshal(payload) 368 | 369 | // set headers and write response 370 | w.Header().Set("Content-Type", "application/json") 371 | w.WriteHeader(code) 372 | w.Write(response) 373 | } 374 | -------------------------------------------------------------------------------- /assignment.postman_collection: -------------------------------------------------------------------------------- 1 | { 2 | "id": "c17ce7e0-4b3a-6c82-22a0-8e10d3e35bb3", 3 | "name": "assignment", 4 | "description": "", 5 | "order": [ 6 | "9ee13aa6-676e-a17f-561d-003ef3473e25", 7 | "22e95e9f-757f-b04f-0898-c7a0fdeb8957", 8 | "7f1aaf9e-c059-e9ce-767a-3b9eb61c96b2", 9 | "0fb242cb-cea9-866e-0654-06c4138f391c", 10 | "9f5d12d4-451c-50fb-975a-8abf5b610cc9", 11 | "1e0ebdab-15a3-4ab1-3af9-6d82b75b0940", 12 | "f067b75d-c06f-94d5-f880-72c30a811901" 13 | ], 14 | "folders": [], 15 | "folders_order": [], 16 | "timestamp": 1547920236586, 17 | "owner": "4732652", 18 | "public": false, 19 | "requests": [ 20 | { 21 | "id": "0fb242cb-cea9-866e-0654-06c4138f391c", 22 | "headers": "Content-Type: application/json\n", 23 | "headerData": [ 24 | { 25 | "key": "Content-Type", 26 | "value": "application/json", 27 | "description": "", 28 | "enabled": true 29 | } 30 | ], 31 | "url": "http://localhost:12345/api/entry", 32 | "queryParams": [], 33 | "preRequestScript": null, 34 | "pathVariables": {}, 35 | "pathVariableData": [], 36 | "method": "PUT", 37 | "data": [], 38 | "dataMode": "raw", 39 | "tests": null, 40 | "currentHelper": "normal", 41 | "helperAttributes": {}, 42 | "time": 1548056861877, 43 | "name": "Update Entry: http://localhost:12345/api/entry", 44 | "description": "", 45 | "collectionId": "c17ce7e0-4b3a-6c82-22a0-8e10d3e35bb3", 46 | "responses": [], 47 | "rawModeData": "{\n\t\"id\":2,\n\t\"first_name\":\"Rick\",\n\t\"last_name\":\"Changed Surname\",\n\t\"email_address\":\"rick.parker@gmail.com\",\n\t\"phone_number\":\"1234567890\"\n}" 48 | }, 49 | { 50 | "id": "1e0ebdab-15a3-4ab1-3af9-6d82b75b0940", 51 | "headers": "", 52 | "headerData": [], 53 | "url": "http://localhost:12345/api/download-entries-CSV", 54 | "queryParams": [], 55 | "pathVariables": {}, 56 | "pathVariableData": [], 57 | "preRequestScript": null, 58 | "method": "GET", 59 | "collectionId": "c17ce7e0-4b3a-6c82-22a0-8e10d3e35bb3", 60 | "data": null, 61 | "dataMode": "params", 62 | "name": "Download CSV: http://localhost:12345/api/download-entries-CSV", 63 | "description": "", 64 | "descriptionFormat": "html", 65 | "time": 1548039662603, 66 | "version": 2, 67 | "responses": [], 68 | "tests": null, 69 | "currentHelper": "normal", 70 | "helperAttributes": {} 71 | }, 72 | { 73 | "id": "22e95e9f-757f-b04f-0898-c7a0fdeb8957", 74 | "headers": "", 75 | "headerData": [], 76 | "url": "http://localhost:12345/api/entries", 77 | "queryParams": [], 78 | "pathVariables": {}, 79 | "pathVariableData": [], 80 | "preRequestScript": null, 81 | "method": "GET", 82 | "collectionId": "c17ce7e0-4b3a-6c82-22a0-8e10d3e35bb3", 83 | "data": null, 84 | "dataMode": "params", 85 | "name": "Get All Entries: http://localhost:12345/api/entries", 86 | "description": "", 87 | "descriptionFormat": "html", 88 | "time": 1547920291208, 89 | "version": 2, 90 | "responses": [], 91 | "tests": null, 92 | "currentHelper": "normal", 93 | "helperAttributes": {} 94 | }, 95 | { 96 | "id": "7f1aaf9e-c059-e9ce-767a-3b9eb61c96b2", 97 | "headers": "Content-Type: application/json\n", 98 | "headerData": [ 99 | { 100 | "key": "Content-Type", 101 | "value": "application/json", 102 | "description": "", 103 | "enabled": true 104 | } 105 | ], 106 | "url": "http://localhost:12345/api/entry", 107 | "queryParams": [], 108 | "preRequestScript": null, 109 | "pathVariables": {}, 110 | "pathVariableData": [], 111 | "method": "POST", 112 | "data": [], 113 | "dataMode": "raw", 114 | "tests": null, 115 | "currentHelper": "normal", 116 | "helperAttributes": {}, 117 | "time": 1548056890771, 118 | "name": "Create Entry: http://localhost:12345/api/entry", 119 | "description": "", 120 | "collectionId": "c17ce7e0-4b3a-6c82-22a0-8e10d3e35bb3", 121 | "responses": [], 122 | "rawModeData": "{\n\t\"first_name\":\"abc\",\n\t\"last_name\":\"def\",\n\t\"email_address\":\"abc.def@g.com\",\n\t\"phone_number\":\"3231239009\",\n}" 123 | }, 124 | { 125 | "id": "9ee13aa6-676e-a17f-561d-003ef3473e25", 126 | "headers": "", 127 | "headerData": [], 128 | "url": "http://localhost:12345/api/entry?id=2", 129 | "queryParams": [ 130 | { 131 | "key": "id", 132 | "value": "2", 133 | "equals": true, 134 | "description": "", 135 | "enabled": true 136 | } 137 | ], 138 | "preRequestScript": null, 139 | "pathVariables": {}, 140 | "pathVariableData": [], 141 | "method": "GET", 142 | "data": null, 143 | "dataMode": "params", 144 | "tests": null, 145 | "currentHelper": "normal", 146 | "helperAttributes": {}, 147 | "time": 1548034269811, 148 | "name": "Get Entry: http://localhost:12345/api/entry?id=1", 149 | "description": "", 150 | "collectionId": "c17ce7e0-4b3a-6c82-22a0-8e10d3e35bb3", 151 | "responses": [] 152 | }, 153 | { 154 | "id": "9f5d12d4-451c-50fb-975a-8abf5b610cc9", 155 | "headers": "", 156 | "headerData": [], 157 | "url": "http://localhost:12345/api/entry?id=2", 158 | "queryParams": [ 159 | { 160 | "key": "id", 161 | "value": "2", 162 | "equals": true, 163 | "description": "", 164 | "enabled": true 165 | } 166 | ], 167 | "preRequestScript": null, 168 | "pathVariables": {}, 169 | "pathVariableData": [], 170 | "method": "DELETE", 171 | "data": null, 172 | "dataMode": "params", 173 | "tests": null, 174 | "currentHelper": "normal", 175 | "helperAttributes": {}, 176 | "time": 1548056900794, 177 | "name": "Delete Entry: http://localhost:12345/api/entry?id=32", 178 | "description": "", 179 | "collectionId": "c17ce7e0-4b3a-6c82-22a0-8e10d3e35bb3", 180 | "responses": [] 181 | }, 182 | { 183 | "id": "f067b75d-c06f-94d5-f880-72c30a811901", 184 | "headers": "//Content-Type: multipart/form-data\n", 185 | "headerData": [ 186 | { 187 | "key": "Content-Type", 188 | "value": "multipart/form-data", 189 | "description": "", 190 | "enabled": false 191 | } 192 | ], 193 | "url": "http://localhost:12345/api/upload-entries-CSV", 194 | "queryParams": [], 195 | "preRequestScript": null, 196 | "pathVariables": {}, 197 | "pathVariableData": [], 198 | "method": "POST", 199 | "data": [ 200 | { 201 | "key": "csvFile", 202 | "value": "address-book-1548037313.csv", 203 | "description": "", 204 | "type": "file", 205 | "enabled": true 206 | } 207 | ], 208 | "dataMode": "params", 209 | "tests": null, 210 | "currentHelper": "normal", 211 | "helperAttributes": {}, 212 | "time": 1548056935165, 213 | "name": "Upload CSV: http://localhost:12345/api/upload-entries-csv", 214 | "description": "", 215 | "collectionId": "c17ce7e0-4b3a-6c82-22a0-8e10d3e35bb3", 216 | "responses": [] 217 | } 218 | ] 219 | } -------------------------------------------------------------------------------- /endpoints_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "net/http" 6 | "net/http/httptest" 7 | "testing" 8 | ) 9 | 10 | func TestGetEntries(t *testing.T) { 11 | req, err := http.NewRequest("GET", "/entries", nil) 12 | if err != nil { 13 | t.Fatal(err) 14 | } 15 | rr := httptest.NewRecorder() 16 | handler := http.HandlerFunc(GetEntries) 17 | handler.ServeHTTP(rr, req) 18 | if status := rr.Code; status != http.StatusOK { 19 | t.Errorf("handler returned wrong status code: got %v want %v", 20 | status, http.StatusOK) 21 | } 22 | 23 | // Check the response body is what we expect. 24 | expected := `[{"id":1,"first_name":"Krish","last_name":"Bhanushali","email_address":"krishsb2405@gmail.com","phone_number":"0987654321"},{"id":2,"first_name":"Rick","last_name":"Parker","email_address":"rick.parker@gmail.com","phone_number":"1234567890"},{"id":3,"first_name":"Kelly","last_name":"Franco","email_address":"kelly.franco@gmail.com","phone_number":"1112223333"}]` 25 | if rr.Body.String() != expected { 26 | t.Errorf("handler returned unexpected body: got %v want %v", 27 | rr.Body.String(), expected) 28 | } 29 | } 30 | 31 | func TestGetEntryByID(t *testing.T) { 32 | 33 | req, err := http.NewRequest("GET", "/entry", nil) 34 | if err != nil { 35 | t.Fatal(err) 36 | } 37 | q := req.URL.Query() 38 | q.Add("id", "1") 39 | req.URL.RawQuery = q.Encode() 40 | rr := httptest.NewRecorder() 41 | handler := http.HandlerFunc(GetEntryByID) 42 | handler.ServeHTTP(rr, req) 43 | if status := rr.Code; status != http.StatusOK { 44 | t.Errorf("handler returned wrong status code: got %v want %v", 45 | status, http.StatusOK) 46 | } 47 | 48 | // Check the response body is what we expect. 49 | expected := `{"id":1,"first_name":"Krish","last_name":"Bhanushali","email_address":"krishsb2405@gmail.com","phone_number":"0987654321"}` 50 | if rr.Body.String() != expected { 51 | t.Errorf("handler returned unexpected body: got %v want %v", 52 | rr.Body.String(), expected) 53 | } 54 | } 55 | 56 | func TestGetEntryByIDNotFound(t *testing.T) { 57 | req, err := http.NewRequest("GET", "/entry", nil) 58 | if err != nil { 59 | t.Fatal(err) 60 | } 61 | q := req.URL.Query() 62 | q.Add("id", "123") 63 | req.URL.RawQuery = q.Encode() 64 | rr := httptest.NewRecorder() 65 | handler := http.HandlerFunc(GetEntryByID) 66 | handler.ServeHTTP(rr, req) 67 | if status := rr.Code; status == http.StatusOK { 68 | t.Errorf("handler returned wrong status code: got %v want %v", 69 | status, http.StatusBadRequest) 70 | } 71 | } 72 | 73 | func TestCreateEntry(t *testing.T) { 74 | 75 | var jsonStr = []byte(`{"id":11,"first_name":"xyz","last_name":"pqr","email_address":"xyz@pqr.com","phone_number":"1234567890"}`) 76 | 77 | req, err := http.NewRequest("POST", "/entry", bytes.NewBuffer(jsonStr)) 78 | if err != nil { 79 | t.Fatal(err) 80 | } 81 | req.Header.Set("Content-Type", "application/json") 82 | rr := httptest.NewRecorder() 83 | handler := http.HandlerFunc(CreateEntry) 84 | handler.ServeHTTP(rr, req) 85 | if status := rr.Code; status != http.StatusOK { 86 | t.Errorf("handler returned wrong status code: got %v want %v", 87 | status, http.StatusOK) 88 | } 89 | expected := `{"id":11,"first_name":"xyz","last_name":"pqr","email_address":"xyz@pqr.com","phone_number":"1234567890"}` 90 | if rr.Body.String() != expected { 91 | t.Errorf("handler returned unexpected body: got %v want %v", 92 | rr.Body.String(), expected) 93 | } 94 | } 95 | 96 | func TestEditEntry(t *testing.T) { 97 | 98 | var jsonStr = []byte(`{"id":11,"first_name":"xyz change","last_name":"pqr","email_address":"xyz@pqr.com","phone_number":"1234567890"}`) 99 | 100 | req, err := http.NewRequest("PUT", "/entry", bytes.NewBuffer(jsonStr)) 101 | if err != nil { 102 | t.Fatal(err) 103 | } 104 | req.Header.Set("Content-Type", "application/json") 105 | rr := httptest.NewRecorder() 106 | handler := http.HandlerFunc(UpdateEntry) 107 | handler.ServeHTTP(rr, req) 108 | if status := rr.Code; status != http.StatusOK { 109 | t.Errorf("handler returned wrong status code: got %v want %v", 110 | status, http.StatusOK) 111 | } 112 | expected := `{"id":11,"first_name":"xyz change","last_name":"pqr","email_address":"xyz@pqr.com","phone_number":"1234567890"}` 113 | if rr.Body.String() != expected { 114 | t.Errorf("handler returned unexpected body: got %v want %v", 115 | rr.Body.String(), expected) 116 | } 117 | } 118 | func TestDeleteEntry(t *testing.T) { 119 | req, err := http.NewRequest("DELETE", "/entry", nil) 120 | if err != nil { 121 | t.Fatal(err) 122 | } 123 | q := req.URL.Query() 124 | q.Add("id", "11") 125 | req.URL.RawQuery = q.Encode() 126 | rr := httptest.NewRecorder() 127 | handler := http.HandlerFunc(DeleteEntry) 128 | handler.ServeHTTP(rr, req) 129 | if status := rr.Code; status != http.StatusOK { 130 | t.Errorf("handler returned wrong status code: got %v want %v", 131 | status, http.StatusOK) 132 | } 133 | expected := `{"id":11,"first_name":"xyz change","last_name":"pqr","email_address":"xyz@pqr.com","phone_number":"1234567890"}` 134 | if rr.Body.String() != expected { 135 | t.Errorf("handler returned unexpected body: got %v want %v", 136 | rr.Body.String(), expected) 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /entry.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type entry struct { 4 | ID int `json:"id,omitempty"` 5 | FirstName string `json:"first_name,omitempty"` 6 | LastName string `json:"last_name,omitempty"` 7 | EmailAddress string `json:"email_address,omitempty"` 8 | PhoneNumber string `json:"phone_number,omitempty"` 9 | } 10 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Address Book Assignment 2 | 3 | ## How to run the application 4 | 5 | 1. Clone the application with `git@github.com:krishsb2405/assignment.git` 6 | 7 | 2. Use the mySQL dump `sql-dump.sql` to create the database, create the tables and insert some dump data. 8 | 9 | 3. Once the application is cloned and database is created, change the Connection String as per your mysql username, password and database name on line No. 20 in api.go. 10 | 11 | 4. Download the postman configuration file (optional). 12 | 13 | 5. There are number of dependencies which need to be imported before running the application. Please get the dependenices through the following commands - 14 | 15 | ```shell 16 | go get "github.com/go-sql-driver/mysql" 17 | go get "github.com/gorilla/mux" 18 | ``` 19 | 20 | 6. To run the application, please use the following command - 21 | 22 | ```shell 23 | go run api.go entry.go 24 | ``` 25 | > Note: By default the port number its being run on is **12345**. 26 | 27 | ## Endpoints Description 28 | 29 | ### Get All Entries 30 | 31 | ``` 32 | URL - *http://localhost:12345/api/entries* 33 | Method - GET 34 | ``` 35 | 36 | ### Get Entry By ID 37 | 38 | ```JSON 39 | URL - *http://localhost:12345/api/entry?id=1* 40 | Method - GET 41 | ``` 42 | 43 | ### Create Entry 44 | 45 | ```JSON 46 | URL - *http://localhost:12345/api/entry* 47 | Method - POST 48 | Body - (content-type = application/json) 49 | { 50 | "first_name":"John", 51 | "last_name":"Doe", 52 | "email_address":"john.doe@gmail.com", 53 | "phone_number":"1234567890" 54 | } 55 | ``` 56 | 57 | ### Update Entry 58 | 59 | ```JSON 60 | URL - *http://localhost:12345/api/entry* 61 | Method - PUT 62 | Body - (content-type = application/json) 63 | { 64 | "id":5, 65 | "first_name":"John", 66 | "last_name":"Doe", 67 | "email_address":"john.doe@gmail.com" 68 | } 69 | ``` 70 | 71 | ### Delete Entry 72 | 73 | ```JSON 74 | URL - *http://localhost:12345/api/entry?id=1* 75 | Method - DELETE 76 | ``` 77 | 78 | ### Import entries through CSV 79 | 80 | ```JSON 81 | URL - *http://localhost:12345/api/upload-entries-CSV* 82 | Method - POST 83 | Body - form-data 84 | { 85 | "csvFile": 86 | } 87 | ``` 88 | 89 | ### Download entries to CSV 90 | 91 | ```JSON 92 | URL - *http://localhost:12345/api/download-entries-CSV* 93 | Method - GET 94 | ``` 95 | 96 | ## Test Driven Development Description 97 | 98 | To run all the unit test cases, please do the following - 99 | 100 | 1. `go run api.go entry.go` 101 | 2. `go test -v` 102 | 103 | 104 | ## Hope everything works. Thank you. -------------------------------------------------------------------------------- /sql-dump.sql: -------------------------------------------------------------------------------- 1 | -- MySQL dump 10.13 Distrib 8.0.13, for macos10.14 (x86_64) 2 | -- 3 | -- Host: localhost Database: online_address_book 4 | -- ------------------------------------------------------ 5 | -- Server version 5.7.24 6 | 7 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; 8 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; 9 | /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; 10 | SET NAMES utf8 ; 11 | /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; 12 | /*!40103 SET TIME_ZONE='+00:00' */; 13 | /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; 14 | /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; 15 | /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; 16 | /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; 17 | 18 | -- 19 | -- Table structure for table `address_book` 20 | -- 21 | 22 | DROP TABLE IF EXISTS `address_book`; 23 | /*!40101 SET @saved_cs_client = @@character_set_client */; 24 | SET character_set_client = utf8mb4 ; 25 | CREATE TABLE `address_book` ( 26 | `id` int(11) NOT NULL AUTO_INCREMENT, 27 | `first_name` varchar(255) NOT NULL, 28 | `last_name` varchar(255) NOT NULL, 29 | `email_address` varchar(255) NOT NULL, 30 | `phone_number` varchar(10) NOT NULL, 31 | PRIMARY KEY (`id`) 32 | ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1; 33 | /*!40101 SET character_set_client = @saved_cs_client */; 34 | 35 | -- 36 | -- Dumping data for table `address_book` 37 | -- 38 | 39 | LOCK TABLES `address_book` WRITE; 40 | /*!40000 ALTER TABLE `address_book` DISABLE KEYS */; 41 | INSERT INTO `address_book` VALUES (1,'Krish','Bhanushali','krishsb2405@gmail.com','0987654321'),(2,'Rick','Parker','rick.parker@gmail.com','1234567890'),(3,'Kelly','Franco','kelly.franco@gmail.com','1112223333'); 42 | /*!40000 ALTER TABLE `address_book` ENABLE KEYS */; 43 | UNLOCK TABLES; 44 | /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; 45 | 46 | /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; 47 | /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; 48 | /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; 49 | /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; 50 | /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; 51 | /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; 52 | /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; 53 | 54 | -- Dump completed on 2019-01-20 23:43:47 55 | --------------------------------------------------------------------------------