Explorar o código

Initial commit

Kelly Norton %!s(int64=10) %!d(string=hai) anos
achega
a5514045d6
Modificáronse 8 ficheiros con 567 adicións e 0 borrados
  1. 1 0
      .gitignore
  2. 7 0
      Makefile
  3. 1 0
      README.md
  4. 113 0
      bindata.go
  5. 301 0
      main.go
  6. 56 0
      pub/index.css
  7. 24 0
      pub/index.html
  8. 64 0
      pub/index.js

+ 1 - 0
.gitignore

@@ -0,0 +1 @@
+.build

+ 7 - 0
Makefile

@@ -0,0 +1,7 @@
+ALL: bindata.go
+
+.build/bin/go-bindata:
+	GOPATH=$(shell pwd)/.build go get github.com/jteeuwen/go-bindata/...
+
+bindata.go: .build/bin/go-bindata $(wildcard pub/**)
+	$< -o $@ -pkg main -prefix pub -nomemcopy pub/...

+ 1 - 0
README.md

@@ -0,0 +1 @@
+## The Go short link service that all Xooglers must write.

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 113 - 0
bindata.go


+ 301 - 0
main.go

@@ -0,0 +1,301 @@
+package main
+
+import (
+	"bytes"
+	"encoding/base64"
+	"encoding/binary"
+	"encoding/json"
+	"flag"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"log"
+	"math/rand"
+	"net/http"
+	"os"
+	"path/filepath"
+	"strings"
+	"time"
+
+	"github.com/syndtr/goleveldb/leveldb"
+)
+
+const (
+	dbFilename = "keys.db"
+)
+
+type Route struct {
+	Url  string
+	Time time.Time
+}
+
+func (r *Route) Write(w io.Writer) error {
+	if err := binary.Write(w, binary.LittleEndian, r.Time.UnixNano()); err != nil {
+		return err
+	}
+
+	if _, err := w.Write([]byte(r.Url)); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (o *Route) Read(r io.Reader) error {
+	var t int64
+	if err := binary.Read(r, binary.LittleEndian, &t); err != nil {
+		return err
+	}
+
+	b, err := ioutil.ReadAll(r)
+	if err != nil {
+		return err
+	}
+
+	o.Url = string(b)
+	o.Time = time.Unix(0, t)
+	return nil
+}
+
+type Context struct {
+	path string
+}
+
+func (c *Context) Init() error {
+	if _, err := os.Stat(c.path); err != nil {
+		if err := os.MkdirAll(c.path, os.ModePerm); err != nil {
+			return err
+		}
+	}
+
+	db, err := openDb(c.path)
+	if err != nil {
+		return err
+	}
+
+	return db.Close()
+}
+
+func openDb(path string) (*leveldb.DB, error) {
+	return leveldb.OpenFile(filepath.Join(path, dbFilename), nil)
+}
+
+func (c *Context) Get(key string) (*Route, error) {
+	db, err := openDb(c.path)
+	if err != nil {
+		return nil, err
+	}
+	defer db.Close()
+
+	val, err := db.Get([]byte(key), nil)
+	if err != nil {
+		return nil, err
+	}
+
+	r := &Route{}
+	if err := r.Read(bytes.NewBuffer(val)); err != nil {
+		return nil, err
+	}
+
+	return r, nil
+}
+
+func (c *Context) Put(key string, r *Route) error {
+	db, err := openDb(c.path)
+	if err != nil {
+		return err
+	}
+	defer db.Close()
+
+	var buf bytes.Buffer
+
+	if err := r.Write(&buf); err != nil {
+		return err
+	}
+
+	return db.Put([]byte(key), buf.Bytes(), nil)
+}
+
+func MakeName() string {
+	var buf bytes.Buffer
+	binary.Write(&buf, binary.LittleEndian, rand.Int63())
+	return base64.URLEncoding.EncodeToString(buf.Bytes())
+}
+
+func ParseName(base, path string) string {
+	t := path[len(base):]
+	ix := strings.Index(t, "/")
+	if ix == -1 {
+		return t
+	} else {
+		return t[:ix]
+	}
+}
+
+func WriteJson(w http.ResponseWriter, data interface{}, status int) {
+	w.Header().Set("Content-Type", "application/json;charset=utf-8")
+	if err := json.NewEncoder(w).Encode(data); err != nil {
+		log.Panic(err)
+	}
+}
+
+func WriteJsonError(w http.ResponseWriter, error string, status int) {
+	WriteJson(w, map[string]interface{}{
+		"error": error,
+	}, status)
+}
+
+func WriteJsonRoute(w http.ResponseWriter, name string, rt *Route) {
+	res := struct {
+		Name string    `json:"name"`
+		URL  string    `json:"url"`
+		Time time.Time `json:"time"`
+	}{
+		name,
+		rt.Url,
+		rt.Time,
+	}
+
+	WriteJson(w, &res, http.StatusOK)
+}
+
+func ServeAsset(w http.ResponseWriter, r *http.Request, name string) {
+	n, err := AssetInfo(name)
+	if err != nil {
+		http.NotFound(w, r)
+		return
+	}
+
+	a, err := Asset(name)
+	if err != nil {
+		http.NotFound(w, r)
+		return
+	}
+
+	http.ServeContent(w, r, n.Name(), n.ModTime(), bytes.NewReader(a))
+}
+
+type DefaultHandler struct {
+	ctx *Context
+}
+
+func (h *DefaultHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	p := ParseName("/", r.URL.Path)
+
+	if p == "" {
+		http.Redirect(w, r,
+			fmt.Sprintf("/edit/%s", MakeName()),
+			http.StatusTemporaryRedirect)
+		return
+	}
+
+	rt, err := h.ctx.Get(p)
+	if err == leveldb.ErrNotFound {
+		http.Redirect(w, r,
+			fmt.Sprintf("/edit/%s", p),
+			http.StatusTemporaryRedirect)
+		return
+	} else if err != nil {
+		log.Panic(err)
+	}
+
+	http.Redirect(w, r,
+		rt.Url,
+		http.StatusTemporaryRedirect)
+}
+
+type EditHandler struct {
+	ctx *Context
+}
+
+func (h *EditHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	p := ParseName("/edit/", r.URL.Path)
+
+	if p == "" {
+		http.Redirect(w, r,
+			fmt.Sprintf("/edit/%s", MakeName()),
+			http.StatusTemporaryRedirect)
+		return
+	}
+
+	ServeAsset(w, r, "index.html")
+}
+
+type ApiHandler struct {
+	ctx *Context
+}
+
+func (h *ApiHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	p := ParseName("/api/url/", r.URL.Path)
+	if p == "" {
+		WriteJsonError(w,
+			http.StatusText(http.StatusNotFound),
+			http.StatusNotFound)
+		return
+	}
+
+	if r.Method == "POST" {
+		var req struct {
+			URL string `json:"url"`
+		}
+
+		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+			WriteJsonError(w, "invalid json", http.StatusBadRequest)
+			return
+		}
+
+		if req.URL == "" {
+			WriteJsonError(w, "url required", http.StatusBadRequest)
+			return
+		}
+
+		rt := Route{
+			Url:  req.URL,
+			Time: time.Now(),
+		}
+
+		if err := h.ctx.Put(p, &rt); err != nil {
+			log.Panic(err)
+		}
+
+		WriteJsonRoute(w, p, &rt)
+	} else if r.Method == "GET" {
+		rt, err := h.ctx.Get(p)
+		if err == leveldb.ErrNotFound {
+			WriteJsonError(w, "no such route", http.StatusNotFound)
+			return
+		} else if err != nil {
+			log.Panic(err)
+		}
+
+		WriteJsonRoute(w, p, rt)
+	} else {
+		WriteJsonError(w,
+			http.StatusText(http.StatusMethodNotAllowed),
+			http.StatusMethodNotAllowed)
+	}
+}
+
+func main() {
+	flagData := flag.String("data", "data", "data")
+	flagAddr := flag.String("addr", ":8067", "addr")
+	flag.Parse()
+
+	ctx := &Context{
+		path: *flagData,
+	}
+
+	if err := ctx.Init(); err != nil {
+		log.Panic(err)
+	}
+
+	mux := http.NewServeMux()
+	mux.Handle("/", &DefaultHandler{ctx})
+	mux.Handle("/edit/", &EditHandler{ctx})
+	mux.Handle("/api/url/", &ApiHandler{ctx})
+	mux.HandleFunc("/s/", func(w http.ResponseWriter, r *http.Request) {
+		ServeAsset(w, r, r.URL.Path[len("/s/"):])
+	})
+
+	log.Panic(http.ListenAndServe(*flagAddr, mux))
+}

+ 56 - 0
pub/index.css

@@ -0,0 +1,56 @@
+body {
+  background: #fff;
+  font-family: 'Raleway', sans-serif;
+  font-size: 42px;
+  font-weight: 300;
+}
+
+form {
+  text-align: center;
+}
+
+#url {
+  font-family: 'Raleway', sans-serif;
+  font-size: 32px;
+  font-weight: 300;
+  width: 600px;
+  margin: 0 auto;
+  padding: 25px;
+  color: #999;
+  border-radius: 4px;
+  border: 1px solid #ccc;
+  outline: none;
+  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
+}
+
+#url:focus {
+  border: 1px solid #09f;
+}
+
+#url::-webkit-input-placeholder {
+   color: #ddd;
+}
+
+#url::-moz-placeholder {
+   color: #ddd;
+}
+
+#cmp {
+  background-color: #eee;
+  padding: 25px;
+  width: 560px;
+  margin: 0 auto;
+  text-align: left;
+  font-size: 21px;
+  transition: transform 200ms ease-in-out;
+  transform: scaleY(0);
+  transform-origin: top center;
+  border-bottom-left-radius: 4px;
+  border-bottom-right-radius: 4px;
+  border: 1px solid #ccc;
+}
+
+#cmp > a {
+  color: #09f;
+  text-decoration: none;
+}

+ 24 - 0
pub/index.html

@@ -0,0 +1,24 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <title>Go</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+    <link href="/s/index.css"
+        rel="stylesheet"
+        type="text/css"> 
+    <link href="http://fonts.googleapis.com/css?family=Raleway:400,300"
+        rel="stylesheet"
+        type="text/css">
+  </head>
+  <body>
+    <form autocomplete="off">
+      <input type="text" id="url" placeholder="Enter the url to shorten"></input>
+      <div id="cmp">
+      Hello
+      </div>
+    </form>
+
+    <script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
+    <script src="/s/index.js"></script>
+  </body>
+</html>

+ 64 - 0
pub/index.js

@@ -0,0 +1,64 @@
+(function() {
+
+var resize = function() {
+  var rect = form.get(0).getBoundingClientRect();
+  form.css('margin-top', window.innerHeight/3 - rect.height/2);
+};
+
+var nameFrom = function(uri) {
+  var parts = uri.substring(1).split('/');
+  return parts[1];
+};
+
+var load = function() {
+  var name = nameFrom(location.pathname);
+  $.ajax({
+    url: '/api/url/' + name,
+    dataType: 'json'
+  }).success(function(data) {
+    var url = data.url || '';
+    $('#url').val(url);
+  });
+}
+
+var showLink = function(name) {
+  var cmp = $('#cmp'),
+      lnk = location.origin + '/' + name;
+
+  var a = $(document.createElement('a'))
+    .attr('href', lnk)
+    .text(lnk)
+    .appendTo(cmp.text(''));
+
+  cmp.css('transform', 'scaleY(1)');
+
+  getSelection().setBaseAndExtent(a.get(0), 0, a.get(0), 1);
+};
+
+var form = $('form').on('submit', function(e) {
+  e.preventDefault();
+  var name = nameFrom(location.pathname),
+      url = $('#url').val().trim();
+
+  if (!url) {
+    return;
+  }
+
+  $.ajax({
+    type: 'POST',
+    url : '/api/url/' + name,
+    data : JSON.stringify({ url : url }),
+    dataType : 'json'
+  }).success(function(data) {
+    var url = data.url || '';
+    if (url) {
+      showLink(name);
+    }
+  });
+});
+
+window.addEventListener('resize', resize);
+resize();
+load();
+
+})();

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio