forked from electricdusk/rushlink
		
	Put existing pages in templates
This commit is contained in:
		
							parent
							
								
									372d7c0487
								
							
						
					
					
						commit
						60fd92c956
					
				| @ -5,6 +5,10 @@ | ||||
|         <title>{{block "title" .}}rushlink{{end}}</title>{{block "head-append" .}}{{end}} | ||||
|     </head> | ||||
|     <body> | ||||
|         {{block "body" .}}{{end}} | ||||
|         {{block "body" .}} | ||||
|         <pre> | ||||
| {{call .Pre}} | ||||
|         </pre> | ||||
|         {{end}} | ||||
|     </body> | ||||
| </html> | ||||
| @ -1,8 +1,3 @@ | ||||
| {{define "title"}} | ||||
| Error - rushlink | ||||
| {{end}} | ||||
| {{define "body"}} | ||||
| <pre> | ||||
| {{.Message}} | ||||
| </pre> | ||||
| {{end}} | ||||
							
								
								
									
										3
									
								
								assets/templates/html/pasteMeta.html.tmpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								assets/templates/html/pasteMeta.html.tmpl
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | ||||
| {{define "title"}} | ||||
| '{{.Paste.Key}}' meta info - rushlink | ||||
| {{end}} | ||||
							
								
								
									
										5
									
								
								assets/templates/txt/pasteMeta.txt.tmpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								assets/templates/txt/pasteMeta.txt.tmpl
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | ||||
| key: {{.Paste.Key}} | ||||
| type: {{.Paste.Type}} | ||||
| state: {{.Paste.State}} | ||||
| created: {{.Paste.TimeCreated}} | ||||
| owner: {{if .IsOwner}}yes{{else}}no{{end}} | ||||
| @ -1,7 +1,6 @@ | ||||
| package handlers | ||||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"crypto/rand" | ||||
| 	"crypto/subtle" | ||||
| 	"encoding/base64" | ||||
| @ -28,7 +27,7 @@ type StoredPaste struct { | ||||
| 	Type        PasteType | ||||
| 	State       PasteState | ||||
| 	Content     []byte | ||||
| 	Key         []byte | ||||
| 	Key         string | ||||
| 	OwnerToken  [16]byte | ||||
| 	TimeCreated time.Time | ||||
| } | ||||
| @ -46,31 +45,31 @@ const ( | ||||
| const CookieOwnerToken = "owner_token" | ||||
| 
 | ||||
| // These keys are designated reserved, and will not be randomly chosen | ||||
| var ReservedPasteKeys [][]byte = [][]byte{[]byte("xd42"), []byte("example")} | ||||
| var ReservedPasteKeys = []string{"xd42", "example"} | ||||
| 
 | ||||
| // Base64 encoding and decoding | ||||
| var base64Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" | ||||
| var base64Encoder = base64.RawURLEncoding.WithPadding(base64.NoPadding) | ||||
| 
 | ||||
| func (t PasteType) String() (string, error) { | ||||
| func (t PasteType) String() string { | ||||
| 	switch t { | ||||
| 	case TypePaste: | ||||
| 		return "paste", nil | ||||
| 		return "paste" | ||||
| 	case TypeRedirect: | ||||
| 		return "redirect", nil | ||||
| 		return "redirect" | ||||
| 	default: | ||||
| 		return "", fmt.Errorf("invalid PasteType (%v)", t) | ||||
| 		return "invalid" | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (t PasteState) String() (string, error) { | ||||
| func (t PasteState) String() string { | ||||
| 	switch t { | ||||
| 	case StatePresent: | ||||
| 		return "present", nil | ||||
| 		return "present" | ||||
| 	case StateDeleted: | ||||
| 		return "deleted", nil | ||||
| 		return "deleted" | ||||
| 	default: | ||||
| 		return "", fmt.Errorf("invalid PasteState (%v)", t) | ||||
| 		return "invalid" | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| @ -134,31 +133,17 @@ func pasteGetHandlerInner(w http.ResponseWriter, r *http.Request, noRedirect, sh | ||||
| 	} | ||||
| 
 | ||||
| 	if showMeta { | ||||
| 		typeString, err := storedPaste.Type.String() | ||||
| 		if err != nil { | ||||
| 			log.Printf("error: %v\n", err) | ||||
| 			RenderInternalServerError(w, r, err) | ||||
| 			return | ||||
| 		} | ||||
| 		stateString, err := storedPaste.State.String() | ||||
| 		if err != nil { | ||||
| 			log.Printf("error: %v\n", err) | ||||
| 			RenderInternalServerError(w, r, err) | ||||
| 			return | ||||
| 		} | ||||
| 		isOwner := "no" | ||||
| 		isOwner := false | ||||
| 		ownerToken, ok := getOwnerTokenFromRequest(r) | ||||
| 		if ok && subtle.ConstantTimeCompare(ownerToken[:], storedPaste.OwnerToken[:]) == 1 { | ||||
| 			isOwner = "yes" | ||||
| 			isOwner = true | ||||
| 		} | ||||
| 
 | ||||
| 		// TODO(dsprenkels) This should be put into a template | ||||
| 		w.WriteHeader(http.StatusOK) | ||||
| 		fmt.Fprintf(w, "key: %v\n", string(storedPaste.Key)) | ||||
| 		fmt.Fprintf(w, "type: %v\n", typeString) | ||||
| 		fmt.Fprintf(w, "state: %v\n", stateString) | ||||
| 		fmt.Fprintf(w, "created: %v\n", storedPaste.TimeCreated.String()) | ||||
| 		fmt.Fprintf(w, "are you the owner: %v\n", isOwner) | ||||
| 		data := map[string]interface{}{ | ||||
| 			"Paste":   storedPaste, | ||||
| 			"IsOwner": isOwner, | ||||
| 		} | ||||
| 		Render(w, r, "pasteMeta", data) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| @ -265,7 +250,7 @@ func shortenURL(tx *bolt.Tx, userURL *url.URL, ownerKey [16]byte) (*StoredPaste, | ||||
| 	// Generate a key until it is not in the database, this occurs in O(log N), | ||||
| 	// where N is the amount of keys stored in the url-shorten database. | ||||
| 	epoch := 0 | ||||
| 	var urlKey []byte | ||||
| 	var urlKey string | ||||
| 	for { | ||||
| 		var err error | ||||
| 		urlKey, err = generateURLKey(epoch) | ||||
| @ -273,14 +258,14 @@ func shortenURL(tx *bolt.Tx, userURL *url.URL, ownerKey [16]byte) (*StoredPaste, | ||||
| 			return nil, errors.Wrap(err, "url-key generation failed") | ||||
| 		} | ||||
| 
 | ||||
| 		found := shortenBucket.Get(urlKey) | ||||
| 		found := shortenBucket.Get([]byte(urlKey)) | ||||
| 		if found == nil { | ||||
| 			break | ||||
| 		} | ||||
| 
 | ||||
| 		isReserved := false | ||||
| 		for _, reservedKey := range ReservedPasteKeys { | ||||
| 			if bytes.HasPrefix(urlKey, reservedKey) { | ||||
| 			if strings.HasPrefix(urlKey, reservedKey) { | ||||
| 				isReserved = true | ||||
| 				break | ||||
| 			} | ||||
| @ -305,17 +290,17 @@ func shortenURL(tx *bolt.Tx, userURL *url.URL, ownerKey [16]byte) (*StoredPaste, | ||||
| 	if err != nil { | ||||
| 		return nil, errors.Wrap(err, "encoding for database failed") | ||||
| 	} | ||||
| 	if err := shortenBucket.Put(urlKey, storedBytes); err != nil { | ||||
| 	if err := shortenBucket.Put([]byte(urlKey), storedBytes); err != nil { | ||||
| 		return nil, errors.Wrap(err, "database transaction failed") | ||||
| 	} | ||||
| 	return &storedPaste, nil | ||||
| } | ||||
| 
 | ||||
| func generateURLKey(epoch int) ([]byte, error) { | ||||
| func generateURLKey(epoch int) (string, error) { | ||||
| 	urlKey := make([]byte, 4+epoch) | ||||
| 	_, err := rand.Read(urlKey) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 		return "", err | ||||
| 	} | ||||
| 	// Put all the values in the range 0..64 for easier base64-encoding | ||||
| 	for i := 0; i < len(urlKey); i++ { | ||||
| @ -343,7 +328,7 @@ func generateURLKey(epoch int) ([]byte, error) { | ||||
| 	for i := range urlKey { | ||||
| 		urlKey[i] = base64Alphabet[urlKey[i]] | ||||
| 	} | ||||
| 	return urlKey, nil | ||||
| 	return string(urlKey), nil | ||||
| } | ||||
| 
 | ||||
| func generateOwnerToken() ([16]byte, error) { | ||||
|  | ||||
| @ -5,6 +5,7 @@ package handlers | ||||
| //go:generate go-bindata -pkg $GOPACKAGE -prefix ../assets ../assets/... | ||||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"fmt" | ||||
| 	html "html/template" | ||||
| 	"io" | ||||
| @ -72,7 +73,7 @@ func parseFail(tmplName string, err error) { | ||||
| 	panic(err) | ||||
| } | ||||
| 
 | ||||
| func Render(w http.ResponseWriter, r *http.Request, tmplName string, data interface{}) { | ||||
| func Render(w http.ResponseWriter, r *http.Request, tmplName string, data map[string]interface{}) { | ||||
| 	contentType, err := resolveResponseContentType(r, []string{"text/plain", "text/html"}) | ||||
| 	if err != nil { | ||||
| 		w.WriteHeader(http.StatusNotAcceptable) | ||||
| @ -95,8 +96,23 @@ func Render(w http.ResponseWriter, r *http.Request, tmplName string, data interf | ||||
| 			err = fmt.Errorf("'%v' not in HTMLTemplates", tmplName) | ||||
| 			break | ||||
| 		} | ||||
| 
 | ||||
| 		// Construct a (lazy) plain-text view for inclusion in <pre> | ||||
| 		pre := func() string { | ||||
| 			tmpl := TextTemplates[tmplName] | ||||
| 			if tmpl == nil { | ||||
| 				panic(fmt.Errorf("'%v' not in TextTemplates", tmplName)) | ||||
| 			} | ||||
| 			var buf bytes.Buffer | ||||
| 			if err := tmpl.Execute(&buf, data); err != nil { | ||||
| 				panic(err) | ||||
| 			} | ||||
| 			return buf.String() | ||||
| 		} | ||||
| 		data = mergeData(map[string]interface{}{"Pre": pre}, data) | ||||
| 		err = tmpl.Execute(w, data) | ||||
| 	default: | ||||
| 		// Fall back to plain text without template | ||||
| 		w.WriteHeader(http.StatusNotAcceptable) | ||||
| 		io.WriteString(w, "could not resolve an acceptable content-type\n") | ||||
| 	} | ||||
| @ -108,7 +124,7 @@ func Render(w http.ResponseWriter, r *http.Request, tmplName string, data interf | ||||
| 
 | ||||
| func RenderError(w http.ResponseWriter, r *http.Request, status int, msg string) { | ||||
| 	w.WriteHeader(status) | ||||
| 	Render(w, r, "error", struct{ Message string }{msg}) | ||||
| 	Render(w, r, "error", map[string]interface{}{"Message": msg}) | ||||
| } | ||||
| 
 | ||||
| func RenderInternalServerError(w http.ResponseWriter, r *http.Request, err interface{}) { | ||||
| @ -116,6 +132,15 @@ func RenderInternalServerError(w http.ResponseWriter, r *http.Request, err inter | ||||
| 	RenderError(w, r, http.StatusInternalServerError, msg) | ||||
| } | ||||
| 
 | ||||
| // Merge the second data map into the first one, overwriting any key that is | ||||
| // already present. | ||||
| func mergeData(into, from map[string]interface{}) map[string]interface{} { | ||||
| 	for k, v := range from { | ||||
| 		into[k] = v | ||||
| 	} | ||||
| 	return into | ||||
| } | ||||
| 
 | ||||
| // Try to resolve the preferred content-type for the response to this request. | ||||
| // | ||||
| // This is done by reading from the `types` argument. If one of them matches | ||||
|  | ||||
| @ -23,20 +23,20 @@ func StartMetricsServer() { | ||||
| 	var ( | ||||
| 		_ = promauto.NewGaugeFunc(prometheus.GaugeOpts{ | ||||
| 			Namespace: "rushlink", | ||||
| 			Subsystem: "shorten", | ||||
| 			Subsystem: "pastes", | ||||
| 			Name:      "urls_total", | ||||
| 			Help:      "The current amount of shortened urls in the database.", | ||||
| 			Help:      "The current amount of pastes in the database.", | ||||
| 		}, func() float64 { | ||||
| 			var metric float64 | ||||
| 			if err := db.DB.View(func(tx *bolt.Tx) error { | ||||
| 				bucket := tx.Bucket([]byte("shorten")) | ||||
| 				bucket := tx.Bucket([]byte("pastes")) | ||||
| 				if bucket == nil { | ||||
| 					return errors.New("bucket 'shorten' could not be found") | ||||
| 					return errors.New("bucket 'pastes' could not be found") | ||||
| 				} | ||||
| 				metric = float64(bucket.Stats().KeyN) | ||||
| 				return nil | ||||
| 			}); err != nil { | ||||
| 				log.Printf("error: %v", errors.Wrap(err, "fetching shorten_urls_total metric")) | ||||
| 				log.Printf("error: %v", errors.Wrap(err, "fetching pastes_total metric")) | ||||
| 				return 0 | ||||
| 			} | ||||
| 			return metric | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user