README ¶
TWReporter's Golang Backend API
Environment
Development
Please make sure that you install Glide package manager in the environment.
cd $GOPATH/src/twreporter.org/go-api
glide install # Install packages and dependencies
// use Makefile
make start
// or
go run main.go # Run without live-reloading
Live Reloading
Note that GOPATH/bin
should be in your PATH
.
go get github.com/codegangsta/gin
gin # Run with live-reloading
Production
go build
./go-api
Dependencies Setup and Configurations
There are two major dependencies of go-api, one is MySQL database,
another is MongoDB.
MySQL DB stores membership data, which is related to users.
MongoDB stores news entities, which is the content that go-api provides.
Install docker-compose
Start/Stop MySQL and MongoDB with default settings
// start MySQL and MongoDB
make env-up
// stop MySQL and MongoDB
make env-down
Configure MySQL Connection
Copy configs/config.example.json
and rename as configs/config.json
.
Change DBSettings
fields to connect to your own database, like following example.
"DBSettings": {
"Name": "test_membership",
"User": "test_membership",
"Password": "test_membership",
"Address": "127.0.0.1",
"Port": "3306"
},
Configure MongoDB Connection
Copy configs/config.example.json
and rename as configs/config.json
.
Change MongoDBSettings
fields to connect to your own database, like following example.
"MongoDBSettings": {
"URL": "localhost",
"DBName": "plate",
"Timeout": 5
},
AWS SES Setup
Currently the source code sends email through AWS SES,
If you want to send email through your AWS SES, just put your AWS SES config under ~/.aws/credentials
[default]
aws_access_key_id = ${AWS_ACCESS_KEY_ID}
aws_secret_access_key = ${AWS_SECRET_ACCESS_KEY}
Otherwise, you have to change the utils/mail.go
to integrate with your email service.
Functional Testing
Prerequisite
- Make sure the environment you run the test has a running
MySQL
server andMongoDB
server
How To Run Tests
// use Makefile
make test
// or
go test $(glide novendor)
// or print logs
go test -v $(glide novendor)
RESTful API
go-api
is a RESTful API built by golang.
It provides several RESTful web services, including
- User login/oauth(facebook & google)
- Read posts
- Read topics
- Read the combination of sections on index page
- Read the posts of multiple categories on index page
- Create/Read/Update/Delete bookmarks of a user
- Create/Read a web push subscription
- Create/Read/Update/Delete registration(s)
- Create/Read/Update/Delete service(s)
USERS
Signin
- workflow:
- user send
POST
request tov1/signin
endpoint - system will send activation email to user
- user click activation link(
<a>
link) in the email body - go-api server verifies the token
- if verified, user will get a jwt(Json Web Token)
- user can use JWT to send personal requests(go-api server will verfiy the jwt).
- user send
OAuth
Before Oauth signin, you have to setup the oauth config in configs/config.json
"OauthSettings": {
"FacebookSettings": {
"ID": "${ID_YOU_GET_FROM_FACEBOOK_DEVELOPER}",
"Secret": "${SECRECT_YOU_GET_FROM_FACEBOOK_DEVELOPER}",
"URL": "http://${GO_API_SERVER_HOST_NAME}:8080/v1/auth/facebook/callback",
"Statestr": "${THE_STATE_YOU_WANT_TO_USE_IN_AUTHORIZE_URL}"
},
"GoogleSettings": {
"Id": "${ID_YOU_GET_FROM_GOOGLE_DEVELOPER}",
"Secret": "${SECRECT_YOU_GET_FROM_FACEBOOK_DEVELOPER}",
"Url": "http://${GO_API_SERVER_HOST_NAME}:8080/v1/auth/google/callback",
"Statestr": "THE_STATE_YOU_WANT_TO_USE_IN_AUTHORIZE_URL"
}
},
"ConsumerSettings": {
"Domain": "${CONSUMER_DOMAIN_NAME}",
"Protocol": "http",
"Host": "${CONSUMER_HOST_NAME}",
"Port": "3000"
},
- workflow
- users click oauth login button, broswer send GET request to
/v1/oauth/goolge
or/v1/oauth/facebook
endpoints(on go-api server) - go-api server redirect users to google or facebook oauth confirmation page
- on goolge/facebook oauth page, user input account and password
- if verified by facebook/google, facebook/google will redirect user to
/v1/oauth/google/callback
or/v1/oauth/facebook/callback
endpoints on go-api server. - if verified by go-api server, go-api server will redirect user to customer page(here, will be
${ConsumerSettings.Protocol}://${ConsumerSettings.Host}:${ConsumerSettings.Port}/
). - jwt(Json Web Token) will be set in the response header(
Set-Cookie: ${cookie}
), and user can get the jwt from browsercookie
. - user can use JWT to send personal requests(go-api server will verfiy the jwt).
- users click oauth login button, broswer send GET request to
Example
TWReporter main site is using the above workflow, you can try to signin on our site.
Signin Endpoint
- URL:
/v1/signin
- Method:
POST
- Data Params:
{
"email": "nickhsine@twreporter.org",
"destination": "https://www.twreporter.org"
}
- Required: `email`
- Optional:`destination`
- Explain:
`email` is the user email, the activation email will be sent to.
`destionation` is the redirect URL after user signed in.
- Response:
- Code: 200
Content:{ "data": { "email": "nickhsine@twreporter.org", "destination": "https://www.twreporter.org" }, "status": "success" }
- Code: 400
Content:{"status": "fail", "data": "{"email":"email is required", "destination":"destination is optional"}"}
- Code: 500
Content:{"status": "error", "message": "Internal server error: Sending activation email occurs error"}
- Code: 200
User Activation Endpoint
- URL:
/v1/activate
- Method:
GET
- URL Param:
- Required:
email
andtoken
- Required:
- Response:
- Code: 200
Content:{ "status": "success", "id": "USER_ID", "privilege": "PRIVILEGE", "firstname": "Nick", "lastname": "Li", "email": "nickhsine@twreporter.org", "jwt": "JSON_WEB_TOKEN" }
- Code: 401
Content:{"status": "error", "message": "ActivateToken is expired"}
- Code: 500
Content:{"status": "error", "message": "Generating JWT occurs error"}
- Code: 200
Renew JWT Endpoint
- URL:
/v1/token/:userID
- example:
/v1/token/100
- example:
- Method:
GET
- Response:
- Code: 200
Content:{ "status": "success", "data": { "token": "NEW_JSON_WEB_TOKEN", "token_type": "Bearer" } }
- Code: 401
Content:{"status": "error", "message": ""}
- Code: 500
Content:{"status": "error", "message": "Renewing JWT occurs error"}
- Code: 200
OAuth Endpoints
- URL:
/v1/auth/google
|/v1/auth/facebook
- Method:
GET
- Response:
- Code: 302
Header:
Redirect URL:"Set-Cookie: auth_info={\"id\":100,\"privilege\":0,\"firstname\":\"\",\"lastname\":\"\",\"email\":\"nickhsine97753017@gmail.com\",\"jwt\":\"jwt_token_goes_here\"}; Domain=twreporter.org; Max-Age=100 HttpOnly"
http://testtest.twreporter.org:3000/?login=google
- Code: 401
Content:{"status": "error", "message": ""}
- Code: 500
Content:{"status": "error", "message": "Renewing JWT occurs error"}
- Code: 302
POSTS
Read posts
-
URL:
/v1/posts
-
Method:
GET
-
URL param:
- Optional:
where=[string] offset=[integer] limit=[integer] sort=[string] full=[boolean]
- Explain:
offset
: the number you want to skiplimit
: the number you want server to returnsort
: the field to sort by in the returned recordsfull
: if true, each record in the returued records will have all the embedded assets- example:
?where={"tags":{"in":["57bab17eab5c6c0f00db77d1"]}}&offset=10&limit=10&sort=-publishedDate&full=true
this example will get 10 full records tagged by 57bab17eab5c6c0f00db77d1 and sorted by publishedDate ascendingly.
- Optional:
-
Response:
- Code: 200
Content:{ "records": [{ // post data structure goes here }], "status": "ok" }
- Code: 500
Content:{"status": "Internal server error", "error": "${here_goes_error_msg}"}
- Code: 200
TOPICS
Read topics
-
URL:
/v1/topics
-
Method:
GET
-
URL param:
- Optional:
where=[string] offset=[integer] limit=[integer] sort=[string] full=[boolean]
- Explain:
offset
: the number you want to skiplimit
: the number you want server to returnsort
: the field to sort by in the returned recordsfull
: if true, each record in the returued records will have all the embedded assets- example:
?where={"slug":"far-sea-fishing-investigative-report"}&full=true
this example will get 1 full topic.
- Optional:
-
Response:
- Code: 200
Content:{ "records": [{ // topic goes here }], "status": "ok" }
- Code: 500
Content:{"status": "Internal server error", "error": "${here_goes_error_msg}"}
- Code: 200
INDEX_PAGE
Read posts of latest, editor picked, latest topic, reviews, topics, photography and infographic sections of index page
- URL:
/v1/index_page
- Method:
GET
- Response:
- Code: 200
Content:{ "records": { "latest": [{ // post goes here }, { // post goes here }, { // post goes here }, ... ], "editor_picks": [{ // post goes here }, { // post goes here }, { // post goes here }, ... ], "latest_topic": [{ // topic goes here }], "reviews": [{ // post goes here }, { } ... ], "topics": [{ // topic goes here }, { // topic goes here } ... ], "photos": [{ // post goes here }], "infographics": [{ // post goes here },{ // post goes here }] }, "status": "ok" }
- Code: 500
Content:{"status": "Internal server error", "error": "${here_goes_error_msg}"}
- Code: 200
Read posts of character, culture_movie, human_rights, international, land_environment, photo_audio, political_society and transformed_justice categories.
- URL:
/v1/index_page_categories
- Method:
GET
- Response:
- Code: 200
Content:{ "records": { "character": [{ // post goes here }, { // post goes here }, ... ], "culture_movie": [{ // post goes here }, { // post goes here }, ... ], "human_rights": [{ // post goes here }, ... ], "international": [{ // post goes here }, { } ... ], "land_environment": [{ // post goes here }, { // post goes here } ... ], "photo_audio": [{ // post goes here }], "political_society": [{ // post goes here } ... ], "transformed_justice": [{ // post goes here } ... ], }, "status": "ok" }
- Code: 500
Content:{"status": "Internal server error", "error": "${here_goes_error_msg}"}
- Code: 200
BOOKMARKS
Get bookmarks
-
URL:
/v1/users/:userID/bookmarks
- example:
/v1/users/1/bookmarks
- example:
-
Authorization of Header:
Bearer ${JWT_TOKEN}
-
Method:
GET
-
Response:
- Code: 200
Content:{ "records": [{ "id": bookmarkID_1, "created_at": "2017-05-09T11:42:50.084994666+08:00", "updated_at": "2017-05-09T11:42:50.084994666+08:00", "deleted_at": null, "slug": "about-us-footer", "host_name": "www.twreporter.org", "is_external": false, "title": "關於我們", "desc": "《報導者》是「財團法人報導者文化基金會」成立的非營利網路媒體...", "thumbnail": "https://www.twreporter.org/asset/logo-desk.svg" }, ... ], "status": "ok" }
- Code: 401
- Code: 403
- Code: 404
Content:{"status": "Record not found", "error": "${here_goes_error_msg}"}
- Code: 500
Content:{"status": "Internal server error", "error": "${here_goes_error_msg}"}
- Code: 200
Create a bookmark
- URL: /users/:userID/bookmarks
- Authorization of Header:
Bearer ${JWT_TOKEN}
- Content-Type of Header:
application/json
- Method:
POST
- Data Params:
{
"slug": "about-us-footer",
"host_name": "www.twreporter.org",
"is_external": false,
"title": "關於我們",
"desc": "《報導者》是「財團法人報導者文化基金會」成立的非營利網路媒體...",
"thumbnail": "https://www.twreporter.org/asset/logo-desk.svg"
}
- Response:
- Code: 201
Content:{ "status": "ok" }
- Code: 400
- Code: 401
- Code: 403
Content:{"status": "Bad request", "error": "${here_goes_error_msg}"}
- Code: 404
Content:{"status": "Record not found", "error": "${here_goes_error_msg}"}
- Code: 500
Content:{"status": "Internal server error", "error": "${here_goes_error_msg}"}
- Code: 201
Delete a bookmark
-
URL: /users/:userID/bookmarks/:bookmarkID
-
Authorization of Header:
Bearer ${JWT_TOKEN}
-
Method:
DELETE
-
Response:
- Code: 204
- Code: 401
- Code: 403
- Code: 404
Content:{"status": "Record not found", "error": "${here_goes_error_msg}"}
- Code: 500
Content:{"status": "Internal server error", "error": "${here_goes_error_msg}"}
- Code: 204
WEB PUSH SUBSCRIPTIONS
Read a web push subscription
-
Method:
GET
-
URL:
/v1/web-push/subscriptions
-
URL Param:
- Required:
ednpoint=[string]
- example:
/v1/web-push/subscriptions?endpoint=https://fcm.googleapis.com/fcm/send/cHB8zjJfX14:APA91bGYoY_R4trCoq2-94pDVUoHcLajBVwaBTkRzJ3q7QiykGXWQW6xN1k7JMUYP4qfgLnRknmQ03WrirHf1eJdR1GHLLmxhX9ZgrZIwb2_mbatDI0Uervod0i5dw_8xRX9TnVms4t8
- Required:
-
Response:
- Code: 200
Content:{ "status": "success", "data": { "id": 1, "created_at": "2018-05-21T10:42:58Z", "updated_at": "2018-05-21T10:42:58Z", "deleted_at": null, "endpoint": "https://fcm.googleapis.com/fcm/send/cHB8zjJfX14:APA91bGYoY_R4trCoq2-94pDVUoHcLajBVwaBTkRzJ3q7QiykGXWQW6xN1k7JMUYP4qfgLnRknmQ03WrirHf1eJdR1GHLLmxhX9ZgrZIwb2_mbatDI0Uervod0i5dw_8xRX9TnVms4t8", "hash_endpoint": "709c44db8951ece74e1893ba558efefa", "expiration_time": null, "user_id": null } }
- Code: 404
Content:{ "status": "error", "message": "Fail to get a web push subscription" }
- Code: 500
Content:{ "status": "error", "message": "${here_goes_error_msg}" }
- Code: 200
Create a web push subscription
- URL:
/v1/web-push/subscriptions
- Content-Type of Header:
application/json
orapplication/x-www-form-urlencoded
- Method:
POST
- Data Params:
- Required:
endpoint: [string]
keys: [string]
- Optional:
expiration_time: [string|null]
user_id: [string|null]
- Required:
// Content-Type: application/json
{
"endpoint": "https://fcm.googleapis.com/fcm/send/f4Stnx6WC5s:APA91bFGo-JD8bDwezv1fx3RRyBVq6XxOkYIo8_7vCAJ3HFHLppKAV6GNmOIZLH0YeC2lM_Ifs9GkLK8Vi_8ASEYLBC1aU9nJy2rZSUfH7DE0AqIIbLrs93SdEdkwr5uL6skPMjJsMRQ",
"keys": "{\"p256dh\":\"BDmY8OGe-LfW0ENPIADvmdZMo3GfX2J2yqURpsDOn5tT8lQV-VVHyhRUgzjnmx_RRoobwdLULdBr26oULtLML3w\",\"auth\":\"P_AJ9QSqcgM-KJi_GRN3fQ\"}",
"expiration_time": "1526959900",
"user_id": "2"
}
- Response:
- Code: 201
Content:{ "data": { "endpoint": "https://fcm.googleapis.com/fcm/send/f4Stnx6WC5s:APA91bFGo-JD8bDwezv1fx3RRyBVq6XxOkYIo8_7vCAJ3HFHLppKAV6GNmOIZLH0YeC2lM_Ifs9GkLK8Vi_8ASEYLBC1aU9nJy2rZSUfH7DE0AqIIbLrs93SdEdkwr5uL6skPMjJsMRQ", "keys": "{\"p256dh\":\"BDmY8OGe-LfW0ENPIADvmdZMo3GfX2J2yqURpsDOn5tT8lQV-VVHyhRUgzjnmx_RRoobwdLULdBr26oULtLML3w\",\"auth\":\"P_AJ9QSqcgM-KJi_GRN3fQ\"}", "expiration_time": "1526959900", "user_id": "2", }, "status": "success" }
- Code: 400
Content:{ "data": { "endpoint": "endpoint is required, and need to be a string", "keys": "keys is required, and need to be a string", "expiration_time": "expirationTime is optional, if provide, need to be a string of timestamp", "user_id": "user_id is optional, if provide, need to be a string", }, "status": "fail"
- Code: 500
Content:{ "status": "error", "message": "${here_goes_error_msg}" }
- Code: 201
SERVICES
Create a service
- URL:
/v1/services/
- Content-Type of Header:
application/json
- Method:
POST
- Data Params:
{
"name": "news_letter"
}
- Response:
- Code: 201
Content:{ "record": { "ID": 1, "CreatedAt": "2017-05-09T11:42:50.084994666+08:00", "UpdatedAt": "2017-05-09T11:42:50.084994666+08:00", "DeletedAt": null, "Name": "news_letter" }, "status": "ok" }
- Code: 400
Content:{"status": "Bad request", "error": "${here_goes_error_msg}"}
- Code: 500
Content:{"status": "Internal server error", "error": "${here_goes_error_msg}"}
- Code: 201
Read a service
- URL:
/v1/services/:id
- Method:
GET
- Response:
- Code: 200
Content:{ "record": { "ID": 1, "CreatedAt": "2017-05-09T11:42:50.084994666+08:00", "UpdatedAt": "2017-05-09T11:42:50.084994666+08:00", "DeletedAt": null, "Name": "news_letter" }, "status": "ok" }
- Code: 404
Content:{"status": "Resource not found", "error": "${here_goes_error_msg}"}
- Code: 500
Content:{"status": "Internal server error", "error": "${here_goes_error_msg}"}
- Code: 200
Update a service
Update a service or create a service if not existed
- URL:
/v1/services/:id
- Method:
PUT
- Response:
- Data Params:
{
"name": "news_letter"
}
- Response:
- Code: 200
Content:{ "record": { "ID": 1, "CreatedAt": "2017-05-09T11:42:50.084994666+08:00", "UpdatedAt": "2017-05-09T11:42:50.084994666+08:00", "DeletedAt": null, "Name": "news_letter" }, "status": "ok" }
- Code: 201
Content:{ "record": { "ID": 1, "CreatedAt": "2017-05-09T11:42:50.084994666+08:00", "UpdatedAt": "2017-05-09T11:42:50.084994666+08:00", "DeletedAt": null, "Name": "news_letter" }, "status": "ok" }
- Code: 400
Content:{"status": "Bad request", "error": "${here_goes_error_msg}"}
- Code: 500
Content:{"status": "Internal server error", "error": "${here_goes_error_msg}"}
- Code: 200
Delete a service
- URL:
/v1/services/:id
- Method:
DELETE
- Response:
- Code: 204
- Code: 404
Content:{"status": "Resource not found", "error": "${here_goes_error_msg}"}
- Code: 500
Content:{"status": "Internal server error", "error": "${here_goes_error_msg}"}
- Code: 204
REGISTRATIONS
Create a registration
- URL:
/v1/registrations/:service/
- example:
/v1/registrations/news_letter/
- example:
- Content-Type of Header:
application/json
- Method:
POST
- Data Params:
{
"email": "nickhsine@twreporter.org"
}
- Response:
- Code: 201
Content:{ "record": { "CreatedAt": "2017-05-09T11:42:50.084994666+08:00", "UpdatedAt": "2017-05-09T11:42:50.084994666+08:00", "DeletedAt": null, "Email": "nickhsine@twreporter.org", "Service": "news_letter", "Active": false, "ActivateToken": "" }, "status": "ok" }
- Code: 400
Content:{"status": "Bad request", "error": "${here_goes_error_msg}"}
- Code: 500
Content:{"status": "Internal server error", "error": "${here_goes_error_msg}"}
- Code: 201
Read a registration
- URL:
/v1/registrations/:service/:email
- example:
/v1/registrations/news_letter/nickhsine%40twreporter.org
- example:
- Method:
GET
- Response:
- Code: 200
Content:{ "record": { "CreatedAt": "2017-05-09T11:42:50.084994666+08:00", "UpdatedAt": "2017-05-09T11:42:50.084994666+08:00", "DeletedAt": null, "Email": "nickhsine@twreporter.org", "Service": "news_letter", "Active": false, "ActivateToken": "" }, "status": "ok" }
- Code: 404
Content:{"status": "Resource not found", "error": "${here_goes_error_msg}"}
- Code: 500
Content:{"status": "Internal server error", "error": "${here_goes_error_msg}"}
- Code: 200
Read registrations
- URL:
/v1/registrations/:service
- example:
/v1/registrations/news_letter
- example:
- Method:
GET
- URL param:
- Optional:
offset=[integer] limit=[integer] order_by=[string] active_code=[integer]
- example:
?offset=10&limit=10&order=updated_at&active_code=2
- Optional:
- Response:
- Code: 200
Content:{ "record": { "CreatedAt": "2017-05-09T11:42:50.084994666+08:00", "UpdatedAt": "2017-05-09T11:42:50.084994666+08:00", "DeletedAt": null, "Email": "nickhsine@twreporter.org", "Service": "news_letter", "Active": false, "ActivateToken": "" }, "status": "ok" }
- Code: 400
Content:{"status": "Bad request", "error": "${here_goes_error_msg}"}
- Code: 500
Content:{"status": "Internal server error", "error": "${here_goes_error_msg}"}
- Code: 200
Delete a registration
- URL:
/v1/registrations/:service/:email
- example:
/v1/registrations/news_letter/nickhsine%40twreporter.org
- example:
- Method:
DELETE
- Response:
- Code: 204
- Code: 404
Content:{"status": "Resource not found", "error": "${here_goes_error_msg}"}
- Code: 500
Content:{"status": "Internal server error", "error": "${here_goes_error_msg}"}
- Code: 204
License
Go-api is MIT licensed
Documentation ¶
There is no documentation for this package.