Skip to content

Prototype HTTP API #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
# issuehunter-api

## Configuration

* `PORT` HTTP server port, default: 8080
4 changes: 4 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
https://stevenwhite.com/building-a-rest-service-with-golang-2/
https://codegangsta.gitbooks.io/building-web-apps-with-go/content/deployment/index.html

https://derekchiang.com/posts/reusable-and-type-safe-options-for-go-apis/
53 changes: 53 additions & 0 deletions db/schema.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
DROP TABLE IF EXISTS campaigns;

CREATE TABLE IF NOT EXISTS campaigns(
uid VARCHAR(255) NOT NULL PRIMARY KEY,
rewarded BOOLEAN,
total NUMERIC(100), -- uint256
created_by VARCHAR(255),
pre_reward_period_expires_at TIMESTAMP,
reward_period_expires_at TIMESTAMP,
resolved_by VARCHAR(255),
patch_verifier VARCHAR(255),
tip_per_mille INT,
tips_amount NUMERIC(100),
repo_url VARCHAR(255),
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL,
created_at_block_id VARCHAR(255) NOT NULL,
updated_at_block_id VARCHAR(255) NOT NULL
);

CREATE INDEX campaigns_total_idx ON campaigns (total);
CREATE INDEX campaigns_created_at_idx ON campaigns (created_at);
CREATE INDEX campaigns_updated_at_idx ON campaigns (updated_at);

DROP TABLE IF EXISTS funds;

CREATE TABLE IF NOT EXISTS funds(
funder_address VARCHAR(255),
amount NUMERIC(100), -- uint256
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL,
created_at_block_id VARCHAR(255) NOT NULL,
updated_at_block_id VARCHAR(255) NOT NULL
);

CREATE INDEX funder_address_idx ON funds (funder_address);
CREATE INDEX funds_created_at_idx ON funds (created_at);
CREATE INDEX funds_updated_at_idx ON funds (updated_at);

DROP TABLE IF EXISTS patches;

CREATE TABLE IF NOT EXISTS patches(
author_address VARCHAR(255),
ref VARCHAR(255),
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL,
created_at_block_id VARCHAR(255) NOT NULL,
updated_at_block_id VARCHAR(255) NOT NULL
);

CREATE INDEX author_address_idx ON funds (author_address);
CREATE INDEX patches_created_at_idx ON patches (created_at);
CREATE INDEX patches_updated_at_idx ON patches (updated_at);
89 changes: 89 additions & 0 deletions server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package main

import (
"database/sql"
"fmt"
"log"
"net/http"
"os"
"time"

"github.com/julienschmidt/httprouter"
_ "github.com/lib/pq"
)

// Logger is a cool type
type Logger struct {
handler http.Handler
}

func (l *Logger) ServeHTTP(w http.ResponseWriter, req *http.Request) {
start := time.Now()

l.handler.ServeHTTP(w, req)

log.Printf("- %s %s (%s)", req.Method, req.URL.Path, time.Since(start).String())
}

func serveIndex(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
http.ServeFile(w, req, "static/index.html")
}

// HelloWorld is a cool function
func HelloWorld(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
// Simply write some test data for now
fmt.Fprintln(w, "Hello World!")
}

// App is a cool function
func App() http.Handler {
router := httprouter.New()
db := NewDB()

// Add a handler on /hello
router.GET("/hello", HelloWorld)

router.GET("/campaigns", CampaignsIndex(db))

// Serve static assets
router.GET("/", serveIndex)
router.ServeFiles("/stylesheets/*filepath", http.Dir("static/stylesheets"))

return &Logger{router}
}

func CampaignsIndex(db *sql.DB) func(http.ResponseWriter, *http.Request, httprouter.Params) {
return func(rw http.ResponseWriter, r *http.Request, _ httprouter.Params) {
var title, author string
err := db.QueryRow("SELECT title, author FROM campaigns").Scan(&title, &author)
if err != nil && err == sql.ErrNoRows {
fmt.Fprintf(rw, "No campaigns found")
return
}
if err != nil {
panic(err)
}

fmt.Fprintf(rw, "The first campaign is '%s' by '%s'", title, author)
}
}

func NewDB() *sql.DB {
db, err := sql.Open("postgres", "postgres://gcappellotto@localhost/issuehunter?sslmode=disable")
if err != nil {
panic(err)
}

return db
}

func main() {
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}

// Fire up the server
fmt.Println("Server listening on port " + port)
http.ListenAndServe(":"+port, App())
}
50 changes: 50 additions & 0 deletions server_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package main

import (
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
)

func Test_HelloWorld(t *testing.T) {
req, err := http.NewRequest("GET", "http://example.com/foo", nil)
if err != nil {
t.Fatal(err)
}

res := httptest.NewRecorder()
HelloWorld(res, req, nil)

exp := "Hello World!\n"
act := res.Body.String()
if exp != act {
t.Fatalf("Expected '%s' got '%s'", exp, act)
}
}

func Test_App(t *testing.T) {
ts := httptest.NewServer(App())
defer ts.Close()

res, err := http.Get(ts.URL)
if err != nil {
t.Fatal(err)
}

body, err := ioutil.ReadAll(res.Body)
res.Body.Close()

if err != nil {
t.Fatal(err)
}

exp, err := ioutil.ReadFile("static/index.html")
if err != nil {
t.Fatal(err)
}

if string(exp) != string(body) {
t.Fatalf("Expected '%s' got '%s'", exp, body)
}
}
15 changes: 15 additions & 0 deletions static/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="utf-8" />
<title>issuehunter</title>
<meta name="description" content="Open Source Crowdfunding on the Ethereum Blockchain">
<link rel="stylesheet" type="text/css" href="/stylesheets/style.css" />
</head>

<body>
<h1>issuehunter</h1>
<h2>Open Source Crowdfunding on the Ethereum Blockchain</h2>
</body>
</html>
3 changes: 3 additions & 0 deletions static/stylesheets/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
body {
font-family: monospace;
}