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
go run main.go # Run without live-reloading
Note that GOPATH/bin
should be in your PATH
.
go get github.com/codegangsta/gin
gin # Run with live-reloading
go build
./go-api
- Make sure the environment you run the test has a running
MySQL
server andMongoDB
server - Execute the following commands after logining into MySQL server.
CREATE USER 'gorm'@'localhost' IDENTIFIED BY 'gorm';
CREATE DATABASE gorm;
GRANT ALL ON gorm.* TO 'gorm'@'localhost';
go test $(glide novendor)
// or print logs
go test -v $(glide novendor)
// create membership_user database
mysqladmin -u root -p create membership_user
// import defined mysql tables into membership_user database
mysql -u root -p membership_user < membership_user.sql
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": "membership_user",
"User": "root",
"Password": "root_password",
"Address": "127.0.0.1",
"Port": "3306"
},
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.
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/Update/Delete registration(s)
- Create/Read/Update/Delete service(s)
- 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
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
TWReporter main site is using the above workflow, you can try to signin on our site.
- URL:
/v1/signin
- Method:
POST
- Data Params:
{
"email": "[email protected]",
"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": "[email protected]", "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
- 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": "[email protected]", "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
- 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
- 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\":\"[email protected]\",\"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
-
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
-
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
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
-
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
- 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
-
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
- 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
- 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 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
- 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
- URL:
/v1/registrations/:service/
- example:
/v1/registrations/news_letter/
- example:
- Content-Type of Header:
application/json
- Method:
POST
- Data Params:
{
"email": "[email protected]"
}
- 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": "[email protected]", "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
- 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": "[email protected]", "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
- 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": "[email protected]", "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
- 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
Go-api is MIT licensed