Skip to content

Commit

Permalink
Merge pull request #7 from coddo/release/release-v0.1-beta
Browse files Browse the repository at this point in the history
Release/release v0.1 beta
  • Loading branch information
coddo committed Apr 25, 2016
2 parents 4768ef1 + 989a902 commit a6a6ea5
Show file tree
Hide file tree
Showing 94 changed files with 3,278 additions and 2,341 deletions.
1 change: 1 addition & 0 deletions .gitignore
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ sftp-config.json
*.a
*.so
gost/gost
debug

# Folders
_obj
Expand Down
Empty file modified LICENSE.md
100644 → 100755
Empty file.
4 changes: 1 addition & 3 deletions README.md
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,9 @@ This is the skeleton of a Web API written in [Golang](https://golang.org). In or

This template contains basic endpoints for Users (+ login system) and Transactions (payments made between users). Both the endpoints are fully working ones, however the user is free to modify/delete them as they will.

###!NOTE that deleting the Users model completely from the app will make this template to malfunction.

# Configuration steps for the API

1. Install Go and set up your [GOPATH](http://golang.org/doc/code.html#GOPATH)
1. Install Go and set up your [GOPATH](http://golang.org/doc/code.html#GOPATH). Starting with version Go1.4, you also need to set the *GOROOT_BOOTSTRAP* variable, to the same path as your *GOROOT*.

2. Install [MongoDb](https://scotch.io/tutorials/an-introduction-to-mongodb#installation-and-running-mongodb)

Expand Down
53 changes: 33 additions & 20 deletions api/api.go
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Package containing the API functionality helpers
// Package api contains the API functionality helpers
//
// Each package represents the API functionality of a
// certain endpoint which may implement some of the
Expand All @@ -7,42 +7,55 @@ package api

import (
"errors"
"gost/auth/identity"
"net/http"
"net/url"
)

const (
GET = "GET"
POST = "POST"
PUT = "PUT"
// GET constant represents a GET type http request
GET = "GET"
// POST constant represents a POST type http request
POST = "POST"
// PUT constant represents a PUT type http request
PUT = "PUT"
// DELETE constant represents a DELETE type http request
DELETE = "DELETE"
)

const (
// ContentTextPlain represents a HTTP transfer with simple text data
ContentTextPlain = "text/plain"
ContentHTML = "text/html"
ContentJSON = "application/json"
// ContentHTML represents a HTTP transfer with html data
ContentHTML = "text/html"
// ContentJSON represents a HTTP transfer with JSON data
ContentJSON = "application/json"
)

// Common errors returned by the API
var (
EntityFormatError = errors.New("The entity was not in the correct format")
EntityIntegrityError = errors.New("The entity doesn't comply to the integrity requirements")
EntityProcessError = errors.New("The entity could not be processed")
EntityNotFoundError = errors.New("No entity with the specified data was found")

IdParamNotSpecifiedError = errors.New("No id was specified for the entity to be updated")
LimitParamError = errors.New("The limit cannot be 0. Use the value -1 for retrieving all the entities")
ErrEntityFormat = errors.New("The entity was not in the correct format")
ErrEntityIntegrity = errors.New("The entity doesn't comply to the integrity requirements")
ErrEntityProcessing = errors.New("The entity could not be processed")
ErrEntityNotFound = errors.New("No entity with the specified data was found")
ErrIDParamNotSpecified = errors.New("No id was specified for the entity to be updated")
ErrInvalidIDParam = errors.New("The id parameter is not a valid bson.ObjectId")
ErrInvalidInput = errors.New("The needed url paramters were inexistent or invalid")
)

type ApiVar struct {
RequestHeader http.Header
RequestForm url.Values
RequestContentLength int64
RequestBody []byte
// A Request contains the important and processable data from a HTTP request
type Request struct {
Header http.Header
Form url.Values
ContentLength int64
Body []byte
Identity *identity.Identity
}

type ApiResponse struct {
Message []byte
// A Response contains the information that will be sent back to the user
// through a HTTP response
type Response struct {
Content []byte
StatusCode int
ErrorMessage string
ContentType string
Expand Down
40 changes: 40 additions & 0 deletions api/app/transactionapi/transactionapi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package transactionapi

import (
"gost/api"
"gost/bll"
"gost/filter"
"gost/filter/apifilter"
"gost/orm/models"
"gost/util"
)

// TransactionsAPI defines the API endpoint for application transactions of any kind
type TransactionsAPI int

// GetTransaction endpoint retrieves a certain transaction based on its Id
func (t *TransactionsAPI) GetTransaction(params *api.Request) api.Response {
transactionID, found, err := filter.GetIDParameter("transactionId", params.Form)

if err != nil {
return api.BadRequest(err)
}

if !found {
return api.NotFound(err)
}

return bll.GetTransaction(transactionID)
}

// CreateTransaction endpoint creates a new transaction with the valid transfer tokens and data
func (t *TransactionsAPI) CreateTransaction(params *api.Request) api.Response {
transaction := &models.Transaction{}

err := util.DeserializeJSON(params.Body, transaction)
if err != nil || !apifilter.CheckTransactionIntegrity(transaction) {
return api.BadRequest(api.ErrEntityFormat)
}

return bll.CreateTransaction(transaction)
}
108 changes: 108 additions & 0 deletions api/app/transactionapi/transactionapi_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package transactionapi

import (
"gost/api"
"gost/auth/identity"
"gost/orm/models"
"gost/service/transactionservice"
"gost/tests"
"net/http"
"net/url"
"testing"

"gopkg.in/mgo.v2/bson"
)

const (
ActionGet = "GetTransaction"
ActionCreate = "CreateTransaction"
)

const endpointPath = "/transactions"

type dummyTransaction struct {
BadField string
}

func TestTransactionsApi(t *testing.T) {
var id bson.ObjectId

tests.InitializeServerConfigurations(new(TransactionsAPI))

// Cleanup function
defer func() {
recover()
transactionservice.DeleteTransaction(id)
}()

testPostTransactionInBadFormat(t)
testPostTransactionNotIntegral(t)
id = testPostTransactionInGoodFormat(t)
testGetTransactionWithInexistentIDInDB(t)
testGetTransactionWithBadIDParam(t)
testGetTransactionWithGoodIDParam(t, id)
}

func testGetTransactionWithInexistentIDInDB(t *testing.T) {
params := url.Values{}
params.Add("transactionId", bson.NewObjectId().Hex())

tests.PerformTestRequest(endpointPath, ActionGet, api.GET, http.StatusNotFound, params, nil, t)
}

func testGetTransactionWithBadIDParam(t *testing.T) {
params := url.Values{}
params.Add("transactionId", "2as456fas4")

tests.PerformTestRequest(endpointPath, ActionGet, api.GET, http.StatusBadRequest, params, nil, t)
}

func testGetTransactionWithGoodIDParam(t *testing.T, id bson.ObjectId) {
params := url.Values{}
params.Add("transactionId", id.Hex())

rw := tests.PerformTestRequest(endpointPath, ActionGet, api.GET, http.StatusOK, params, nil, t)

body := rw.Body.String()
if len(body) == 0 {
t.Error("Response body is empty or in a corrupt format:", body)
}
}

func testPostTransactionInBadFormat(t *testing.T) {
dTransaction := &dummyTransaction{
BadField: "bad value",
}

tests.PerformTestRequest(endpointPath, ActionCreate, api.POST, http.StatusBadRequest, nil, dTransaction, t)
}

func testPostTransactionNotIntegral(t *testing.T) {
transaction := &models.Transaction{
ID: bson.NewObjectId(),
Payer: identity.ApplicationUser{ID: bson.NewObjectId()},
Currency: "USD",
}

tests.PerformTestRequest(endpointPath, ActionCreate, api.POST, http.StatusBadRequest, nil, transaction, t)
}

func testPostTransactionInGoodFormat(t *testing.T) bson.ObjectId {
transaction := &models.Transaction{
ID: bson.NewObjectId(),
Payer: identity.ApplicationUser{ID: bson.NewObjectId()},
Receiver: identity.ApplicationUser{ID: bson.NewObjectId()},
Type: models.TransactionTypeCash,
Ammount: 216.365,
Currency: "USD",
}

rw := tests.PerformTestRequest(endpointPath, ActionCreate, api.POST, http.StatusCreated, nil, transaction, t)

body := rw.Body.String()
if len(body) == 0 {
t.Error("Response body is empty or in deteriorated format:", body)
}

return transaction.ID
}
49 changes: 27 additions & 22 deletions api/basic_responses.go
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,51 +1,56 @@
package api

import (
"gost/models"
"gost/util"
"io/ioutil"
)

func SingleDataResponse(statusCode int, data models.Modeler) ApiResponse {
jsonData, err := models.SerializeJson(data)
// JSONResponse creates a Response from the api, containing a single entity encoded as JSON
func JSONResponse(statusCode int, data interface{}) Response {
jsonData, err := util.SerializeJSON(data)
if err != nil {
return InternalServerError(err)
}

return ApiResponse{
return Response{
StatusCode: statusCode,
Message: jsonData,
Content: jsonData,
}
}

func MultipleDataResponse(statusCode int, data []models.Modeler) ApiResponse {
jsonData, err := models.SerializeJson(data)
if err != nil {
return InternalServerError(err)
}
// StatusResponse creates a Response from the api, containing just a status code
func StatusResponse(statusCode int) Response {
return Response{StatusCode: statusCode}
}

return ApiResponse{
StatusCode: statusCode,
Message: jsonData,
}
// PlainTextResponse creates a Response from the api, containing a status code and a text message
func PlainTextResponse(statusCode int, text string) Response {
return DataResponse(statusCode, []byte(text), ContentTextPlain)
}

func StatusResponse(statusCode int) ApiResponse {
return ApiResponse{StatusCode: statusCode}
// TextResponse creates a Response from the api, containing a status code, a text message and a custom content-type
func TextResponse(statusCode int, text, contentType string) Response {
return DataResponse(statusCode, []byte(text), contentType)
}

func ByteResponse(statusCode int, data []byte) ApiResponse {
return ApiResponse{
StatusCode: statusCode,
Message: data,
// DataResponse creates a Response from the api, containing a status code,
// a custom content-type and a message in the form of a byte array
func DataResponse(statusCode int, data []byte, contetType string) Response {
return Response{
StatusCode: statusCode,
Content: data,
ContentType: contetType,
}
}

func FileResponse(statusCode int, contentType, fullFilePath string) ApiResponse {
// FileResponse creates a Response from the api, containing a file path (download, load or stream)
// and the content type of the file that is returned
func FileResponse(statusCode int, contentType, fullFilePath string) Response {
if _, err := ioutil.ReadFile(fullFilePath); err != nil {
return InternalServerError(err)
}

return ApiResponse{
return Response{
StatusCode: statusCode,
File: fullFilePath,
ContentType: contentType,
Expand Down
22 changes: 22 additions & 0 deletions api/custom_statuses.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package api

import "net/http"

const (
// StatusTooManyRequests is used for issuing errors regarding the number of request made by a client
StatusTooManyRequests = 429
)

var statusText = map[int]string{
StatusTooManyRequests: "Too many requests",
}

// StatusText returns the message associated to a http status code
func StatusText(statusCode int) string {
msg := http.StatusText(statusCode)
if len(msg) > 0 {
return msg
}

return statusText[statusCode]
}
Loading

0 comments on commit a6a6ea5

Please sign in to comment.