/* AppEngine demo - a URL shortener */packageshortieimport("fmt""html/template""net/http""regexp""strings""time""appengine""appengine/datastore""appengine/user")const(counterKeyName="counter-key-name"counterKind="Counter"urlKind="Url")// Global counter of urlstypeCounterstruct{Countint64}// URL stored in databasetypeURLstruct{ShortstringLongstringUserstringCreatedtime.TimeHitsint64}// Parameters for homeTemplate.typehomeParamsstruct{UserstringLoginTitlestringLoginURLstringErrorstringCountint64ShortURLstring}varhomeTemplate*template.Templatefuncinit(){homeTemplate=template.Must(template.New("home").Parse(homeHTML))http.HandleFunc("/",rootHandler)}// rootHandler handles the main page.funcrootHandler(whttp.ResponseWriter,r*http.Request){ctx:=appengine.NewContext(r)varerrerrorparams:=new(homeParams)// Run at end. We check "err" and update params if needed. Then serve homeTemplate.deferfunc(){iferr!=nil{params.Error=err.Error()ctx.Errorf("%v",err)// Log error}homeTemplate.Execute(w,params)}()err=fillUser(r,ctx,params)iferr!=nil{return}params.Count,err=urlCount(ctx)ifr.Method=="POST"{longURL:=strings.TrimSpace(r.FormValue("url"))if!isValidURL(longURL){err=fmt.Errorf("Bad URL - %s",longURL)return}if!hasSchema(longURL){longURL=fmt.Sprintf("http://%s",longURL)}varidstringid,err=nextId(ctx)iferr!=nil{return}url:=&URL{Short:id,Long:longURL,User:params.User,Created:time.Now(),Hits:0,}key:=datastore.NewKey(ctx,urlKind,id,0,nil)_,err=datastore.Put(ctx,key,url)iferr!=nil{return}params.ShortURL=fullURL(r,id)}}// fillUser fills user details in template parametersfuncfillUser(r*http.Request,ctxappengine.Context,params*homeParams)error{varerrerroru:=user.Current(ctx)ifu!=nil{params.User=u.String()params.LoginTitle="Logout"params.LoginURL,err=user.LogoutURL(ctx,r.URL.String())}else{params.User="Stranger"params.LoginTitle="Login"params.LoginURL,err=user.LoginURL(ctx,r.URL.String())}returnerr}// urlCount return the current count of urls.funcurlCount(ctxappengine.Context)(int64,error){key:=datastore.NewKey(ctx,counterKind,counterKeyName,0,nil)counter:=new(Counter)iferr:=datastore.Get(ctx,key,counter);err!=nil&&err!=datastore.ErrNoSuchEntity{return0,err}returncounter.Count,nil}/* nextId returns the next short url. We use the global counter and then encode the last count in base62.*/funcnextId(ctxappengine.Context)(string,error){varcountint64err:=datastore.RunInTransaction(ctx,func(ctxappengine.Context)error{key:=datastore.NewKey(ctx,counterKind,counterKeyName,0,nil)counter:=new(Counter)iferr:=datastore.Get(ctx,key,counter);err!=nil&&err!=datastore.ErrNoSuchEntity{returnerr}counter.Count++if_,err:=datastore.Put(ctx,key,counter);err!=nil{returnerr}count=counter.Countreturnnil},nil)returnbase62Encode(uint64(count)),err}// isValidURL check that URL is validfuncisValidURL(urlstring)bool{return(len(url)>0)&&strings.Contains(url,".")}// hasSchema check if url has schema prefix.funchasSchema(urlstring)bool{match,_:=regexp.MatchString("^[a-zA-Z]+://",url)returnmatch}/* fullURL adds http://<host> suffix to short url. This works both locally and on AppEngine.*/funcfullURL(r*http.Request,idstring)string{returnfmt.Sprintf("http://%s/%s",r.Host,id)}funcnewShortUrl(ctxappengine.Context,longURL,userstring)(string,error){varidstringid,err:=nextId(ctx)iferr!=nil{return"",err}url:=&URL{Short:id,Long:longURL,User:user,Created:time.Now(),Hits:0,}key:=datastore.NewKey(ctx,urlKind,id,0,nil)_,err=datastore.Put(ctx,key,url)iferr!=nil{return"",err}returnid,nil}