diff --git a/README.md b/README.md
index b6e1c18..eb380de 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,7 @@
# Golang Base Project
+[](https://godoc.org/github.com/uberswe/golang-base-project)
+
A minimal Golang project with user authentication ready out of the box. All frontend assets should be less than 100 kB on every page load.
See a live example at: [https://www.golangbase.com](https://www.golangbase.com)
@@ -162,6 +164,10 @@ There is a workflow to deploy to my personal server whenever there is a merge to
I use [supervisor](http://supervisord.org/) with [docker-compose](https://docs.docker.com/compose/production/) to run my containers. [Caddy](https://caddyserver.com/) handles the SSL configuration and routing. I use [Ansible](https://docs.ansible.com/ansible/latest/user_guide/playbooks.html) to manage my configurations.
+## Documentation
+
+See [GoDoc](https://godoc.org/github.com/uberswe/golang-base-project) for further documentation.
+
## Contributions
Contributions are welcome and greatly appreciated. Please note that I am not looking to add any more features to this project but I am happy to take care of bugfixes, updates and other suggestions. If you have a question or suggestion please feel free to [open an issue](https://github.com/uberswe/golang-base-project/issues/new). To contribute code, please fork this repository, make your changes on a separate branch and then [open a pull request](https://github.com/uberswe/golang-base-project/compare).
diff --git a/config/main.go b/config/main.go
index 7019647..6aca277 100644
--- a/config/main.go
+++ b/config/main.go
@@ -1,6 +1,7 @@
// Package config defines the env configuration variables
package config
+// Config defines all the configuration variables for the golang-base-project
type Config struct {
Port string
CookieSecret string
diff --git a/email/main.go b/email/main.go
index f21b7c0..6a7964f 100644
--- a/email/main.go
+++ b/email/main.go
@@ -5,23 +5,26 @@ import (
"bytes"
"fmt"
"github.com/uberswe/golang-base-project/config"
- "github.com/uberswe/golang-base-project/util"
+ "github.com/uberswe/golang-base-project/text"
"log"
"mime/multipart"
"net/smtp"
"strings"
)
+// Service holds a golang-base-project config.Config and provides functions to send emails
type Service struct {
Config config.Config
}
+// New takes a golang-base-project config.Config and returns an instance of Service
func New(c config.Config) Service {
return Service{
Config: c,
}
}
+// Send sends an email with the provided subject and message to the provided email.
func (s Service) Send(to string, subject string, message string) {
// Authentication.
auth := smtp.PlainAuth("", s.Config.SMTPUsername, s.Config.SMTPPassword, s.Config.SMTPHost)
@@ -34,8 +37,8 @@ func (s Service) Send(to string, subject string, message string) {
_, _ = fmt.Fprintf(&b, "Content-Type: multipart/alternative; charset=\"UTF-8\"; boundary=\"%s\"\r\n", writer.Boundary())
_, _ = fmt.Fprintf(&b, "\r\n\r\n--%s\r\nContent-Type: %s; charset=UTF-8;\nContent-Transfer-Encoding: 8bit\r\n\r\n", writer.Boundary(), "text/plain")
b.Write([]byte(message))
- htmlMessage := util.StringLinkToHTMLLink(message)
- htmlMessage = util.NL2BR(htmlMessage)
+ htmlMessage := text.LinkToHTMLLink(message)
+ htmlMessage = text.Nl2Br(htmlMessage)
_, _ = fmt.Fprintf(&b, "\r\n\r\n--%s\r\nContent-Type: %s; charset=UTF-8;\nContent-Transfer-Encoding: 8bit\r\n\r\n", writer.Boundary(), "text/html")
b.Write([]byte(htmlMessage))
@@ -43,7 +46,7 @@ func (s Service) Send(to string, subject string, message string) {
sender := s.Config.SMTPSender
if strings.Contains(sender, "<") {
- sender = util.GetStringBetweenStrings(sender, "<", ">")
+ sender = text.BetweenStrings(sender, "<", ">")
}
// Sending email.
diff --git a/main.go b/main.go
index 2940d60..5033fed 100644
--- a/main.go
+++ b/main.go
@@ -15,58 +15,85 @@ import (
"time"
)
+// staticFS is an embedded file system
//go:embed dist/*
var staticFS embed.FS
+// Run is the main function that runs the entire package and starts the webserver, this is called by /cmd/base/main.go
func Run() {
// When generating random strings we need to provide a seed otherwise we always get the same strings the next time our application starts
rand.Seed(time.Now().UnixNano())
- var t *template.Template
+ // We load environment variables, these are only read when the application launches
conf := loadEnvVariables()
+ // We connect to the database using the configuration generated from the environment variables.
db, err := connectToDatabase(conf)
if err != nil {
log.Fatalln(err)
}
+ // Once a database connection is established we run any needed migrations
err = migrateDatabase(db)
if err != nil {
log.Fatalln(err)
}
+ // t will hold all our html templates used to render pages
+ var t *template.Template
+ // We parse and load the html files into our t variable
t, err = loadTemplates()
if err != nil {
log.Fatalln(err)
}
+ // A gin Engine instance with the default configuration
r := gin.Default()
+ // We create a new cookie store with a key used to secure cookies with HMAC
store := cookie.NewStore([]byte(conf.CookieSecret))
+
+ // We define our session middleware to be used globally on all routes
r.Use(sessions.Sessions("golang_base_project_session", store))
+ // We pase our template variable t to the gin engine so it can be used to render html pages
r.SetHTMLTemplate(t)
+ // our assets are only located in a section of our file system. so we create a sub file system.
subFS, err := fs.Sub(staticFS, "dist/assets")
if err != nil {
log.Fatalln(err)
}
+ // All static assets are under the /assets path so we make this its own group called assets
assets := r.Group("/assets")
+
+ // This middleware sets the Cache-Control header and is applied to the assets group only
assets.Use(middleware.Cache(conf.CacheMaxAge))
+ // All requests to /assets will use the sub fil system which contains all our static assets
assets.StaticFS("/", http.FS(subFS))
+ // Session middleware is applied to all groups after this point.
r.Use(middleware.Session(db))
+
+ // A General middleware is defined to add default headers to improve site security
r.Use(middleware.General())
+ // A new instance of the routes controller is created
controller := routes.New(db, conf)
+ // Any request to / will call controller.Index
r.GET("/", controller.Index)
+
+ // We want to handle both POST and GET requests on the /search route. We define both but use the same function to handle the requests.
r.GET("/search", controller.Search)
r.POST("/search", controller.Search)
+
+ // We define our 404 handler for when a page can not be found
r.NoRoute(controller.NoRoute)
+ // noAuth is a group for routes which should only be accessed if the user is not authenticated
noAuth := r.Group("/")
noAuth.Use(middleware.NoAuth())
@@ -77,6 +104,7 @@ func Run() {
noAuth.GET("/user/password/forgot", controller.ForgotPassword)
noAuth.GET("/user/password/reset/:token", controller.ResetPassword)
+ // We make a separate group for our post requests on the same endpoints so that we can define our throttling middleware on POST requests only.
noAuthPost := noAuth.Group("/")
noAuthPost.Use(middleware.Throttle(conf.RequestsPerMinute))
@@ -86,6 +114,7 @@ func Run() {
noAuthPost.POST("/user/password/forgot", controller.ForgotPasswordPost)
noAuthPost.POST("/user/password/reset/:token", controller.ResetPasswordPost)
+ // the admin group handles routes that should only be accessible to authenticated users
admin := r.Group("/")
admin.Use(middleware.Auth())
admin.Use(middleware.Sensitive())
@@ -95,6 +124,8 @@ func Run() {
admin.POST("/admin", controller.Admin)
admin.GET("/logout", controller.Logout)
+ // This starts our webserver, our application will not stop running or go past this point unless
+ // an error occurs or the web server is stopped for some reason. It is designed to run forever.
err = r.Run(conf.Port)
if err != nil {
log.Fatalln(err)
diff --git a/middleware/auth.go b/middleware/auth.go
index b7c991b..aad38d5 100644
--- a/middleware/auth.go
+++ b/middleware/auth.go
@@ -6,8 +6,10 @@ import (
"net/http"
)
+// UserIDKey is the key used to set and get the user id in the context of the current request
const UserIDKey = "UserID"
+// Auth middleware redirects to /login and aborts the current request if there is no authenticated user
func Auth() gin.HandlerFunc {
return func(c *gin.Context) {
_, exists := c.Get(UserIDKey)
diff --git a/middleware/cache.go b/middleware/cache.go
index 306e5fc..1f7d22f 100644
--- a/middleware/cache.go
+++ b/middleware/cache.go
@@ -5,6 +5,7 @@ import (
"github.com/gin-gonic/gin"
)
+// Cache middleware sets the Cache-Control header
func Cache(maxAge int) gin.HandlerFunc {
return func(c *gin.Context) {
c.Header("Cache-Control", fmt.Sprintf("public, max-age=%d", maxAge))
diff --git a/middleware/noauth.go b/middleware/noauth.go
index 36f10e7..5fdee1e 100644
--- a/middleware/noauth.go
+++ b/middleware/noauth.go
@@ -5,8 +5,6 @@ import (
"net/http"
)
-var SessionIdentifierKey = "SESSION_IDENTIFIER"
-
// NoAuth is for routes that can only be accessed when the user is unauthenticated
func NoAuth() gin.HandlerFunc {
return func(c *gin.Context) {
diff --git a/middleware/session.go b/middleware/session.go
index a791477..e91af0c 100644
--- a/middleware/session.go
+++ b/middleware/session.go
@@ -8,10 +8,14 @@ import (
"log"
)
+// SessionIDKey is the key used to set and get the session id in the context of the current request
+const SessionIDKey = "SessionID"
+
+// Session middleware checks for an active session and sets the UserIDKey to the context of the current request if found
func Session(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
session := sessions.Default(c)
- sessionIdentifierInterface := session.Get(SessionIdentifierKey)
+ sessionIdentifierInterface := session.Get(SessionIDKey)
if sessionIdentifier, ok := sessionIdentifierInterface.(string); ok {
ses := models.Session{
diff --git a/middleware/throttle.go b/middleware/throttle.go
index 86d8b3f..501a9ef 100644
--- a/middleware/throttle.go
+++ b/middleware/throttle.go
@@ -8,6 +8,7 @@ import (
"time"
)
+// Throttle middleware takes a limit per minute and blocks any additional requests that go over this limit
func Throttle(limit int) gin.HandlerFunc {
store := memory.NewStore()
diff --git a/models/session.go b/models/session.go
index 0c631c8..726a3aa 100644
--- a/models/session.go
+++ b/models/session.go
@@ -5,6 +5,7 @@ import (
"time"
)
+// Session holds information about user sessions and when they expire
type Session struct {
gorm.Model
Identifier string
@@ -12,6 +13,7 @@ type Session struct {
ExpiresAt time.Time
}
+// HasExpired is a helper function that checks if the current time is after the session expire datetime
func (s Session) HasExpired() bool {
return s.ExpiresAt.Before(time.Now())
}
diff --git a/models/token.go b/models/token.go
index 40e589b..24f4158 100644
--- a/models/token.go
+++ b/models/token.go
@@ -5,6 +5,7 @@ import (
"time"
)
+// Token holds tokens typically used for user activation and password resets
type Token struct {
gorm.Model
Value string
@@ -14,11 +15,14 @@ type Token struct {
ExpiresAt time.Time
}
+// HasExpired is a helper function that checks if the current time is after the token expiration time
func (t Token) HasExpired() bool {
return t.ExpiresAt.Before(time.Now())
}
const (
+ // TokenUserActivation is a constant used to identify tokens used for user activation
TokenUserActivation string = "user_activation"
- TokenPasswordReset string = "password_reset"
+ // TokenPasswordReset is a constant used to identify tokens used for password resets
+ TokenPasswordReset string = "password_reset"
)
diff --git a/models/user.go b/models/user.go
index 6e7f944..cada53a 100644
--- a/models/user.go
+++ b/models/user.go
@@ -6,6 +6,7 @@ import (
"time"
)
+// User holds information relating to users that use the application
type User struct {
gorm.Model
Email string
diff --git a/models/website.go b/models/website.go
index e3f2e45..97b5f02 100644
--- a/models/website.go
+++ b/models/website.go
@@ -2,6 +2,7 @@ package models
import "gorm.io/gorm"
+// Website holds information about different websites
type Website struct {
gorm.Model
Title string
diff --git a/routes/activate.go b/routes/activate.go
index b8ff4e6..ed88169 100644
--- a/routes/activate.go
+++ b/routes/activate.go
@@ -8,6 +8,7 @@ import (
"time"
)
+// Activate handles requests used to activate a users account
func (controller Controller) Activate(c *gin.Context) {
activationError := "Please provide a valid activation token"
activationSuccess := "Account activated. You may now proceed to login to your account."
diff --git a/routes/admin.go b/routes/admin.go
index e436eaf..726492f 100644
--- a/routes/admin.go
+++ b/routes/admin.go
@@ -5,6 +5,7 @@ import (
"net/http"
)
+// Admin renders the admin dashboard
func (controller Controller) Admin(c *gin.Context) {
pd := PageData{
Title: "Admin",
diff --git a/routes/forgotpassword.go b/routes/forgotpassword.go
index 6e7be55..775bfa3 100644
--- a/routes/forgotpassword.go
+++ b/routes/forgotpassword.go
@@ -5,7 +5,7 @@ import (
"github.com/gin-gonic/gin"
email2 "github.com/uberswe/golang-base-project/email"
"github.com/uberswe/golang-base-project/models"
- "github.com/uberswe/golang-base-project/util"
+ "github.com/uberswe/golang-base-project/ulid"
"gorm.io/gorm"
"log"
"net/http"
@@ -14,6 +14,7 @@ import (
"time"
)
+// ForgotPassword renders the HTML page where a password request can be initiated
func (controller Controller) ForgotPassword(c *gin.Context) {
pd := PageData{
Title: "Forgot Password",
@@ -23,6 +24,7 @@ func (controller Controller) ForgotPassword(c *gin.Context) {
c.HTML(http.StatusOK, "forgotpassword.html", pd)
}
+// ForgotPasswordPost handles the POST request which requests a password reset and then renders the HTML page with the appropriate message
func (controller Controller) ForgotPasswordPost(c *gin.Context) {
pd := PageData{
Title: "Forgot Password",
@@ -47,7 +49,7 @@ func (controller Controller) ForgotPasswordPost(c *gin.Context) {
func (controller Controller) forgotPasswordEmailHandler(userID uint, email string) {
forgotPasswordToken := models.Token{
- Value: util.GenerateULID(),
+ Value: ulid.Generate(),
Type: models.TokenPasswordReset,
}
diff --git a/routes/index.go b/routes/index.go
index 4cc8dac..e92081d 100644
--- a/routes/index.go
+++ b/routes/index.go
@@ -5,6 +5,7 @@ import (
"net/http"
)
+// Index renders the HTML of the index page
func (controller Controller) Index(c *gin.Context) {
pd := PageData{
Title: "Home",
diff --git a/routes/login.go b/routes/login.go
index 7aeb0b8..a65d42c 100644
--- a/routes/login.go
+++ b/routes/login.go
@@ -5,13 +5,14 @@ import (
"github.com/gin-gonic/gin"
"github.com/uberswe/golang-base-project/middleware"
"github.com/uberswe/golang-base-project/models"
- "github.com/uberswe/golang-base-project/util"
+ "github.com/uberswe/golang-base-project/ulid"
"golang.org/x/crypto/bcrypt"
"log"
"net/http"
"time"
)
+// Login renders the HTML of the login page
func (controller Controller) Login(c *gin.Context) {
pd := PageData{
Title: "Login",
@@ -21,6 +22,7 @@ func (controller Controller) Login(c *gin.Context) {
c.HTML(http.StatusOK, "login.html", pd)
}
+// LoginPost handles login requests and returns the appropriate HTML and messages
func (controller Controller) LoginPost(c *gin.Context) {
loginError := "Could not login, please make sure that you have typed in the correct email and password. If you have forgotten your password, please click the forgot password link below."
pd := PageData{
@@ -72,7 +74,7 @@ func (controller Controller) LoginPost(c *gin.Context) {
}
// Generate a ulid for the current session
- sessionIdentifier := util.GenerateULID()
+ sessionIdentifier := ulid.Generate()
ses := models.Session{
Identifier: sessionIdentifier,
@@ -94,7 +96,7 @@ func (controller Controller) LoginPost(c *gin.Context) {
}
session := sessions.Default(c)
- session.Set(middleware.SessionIdentifierKey, sessionIdentifier)
+ session.Set(middleware.SessionIDKey, sessionIdentifier)
err = session.Save()
if err != nil {
diff --git a/routes/logout.go b/routes/logout.go
index 8dd22b2..7346fcc 100644
--- a/routes/logout.go
+++ b/routes/logout.go
@@ -8,9 +8,10 @@ import (
"net/http"
)
+// Logout deletes the current user session and redirects the user to the index page
func (controller Controller) Logout(c *gin.Context) {
session := sessions.Default(c)
- session.Delete(middleware.SessionIdentifierKey)
+ session.Delete(middleware.SessionIDKey)
err := session.Save()
log.Println(err)
diff --git a/routes/main.go b/routes/main.go
index 19b94ab..77f27ce 100644
--- a/routes/main.go
+++ b/routes/main.go
@@ -8,11 +8,13 @@ import (
"gorm.io/gorm"
)
+// Controller holds all the variables needed for routes to perform their logic
type Controller struct {
db *gorm.DB
config config.Config
}
+// New creates a new instance of the routes.Controller
func New(db *gorm.DB, c config.Config) Controller {
return Controller{
db: db,
@@ -20,6 +22,7 @@ func New(db *gorm.DB, c config.Config) Controller {
}
}
+// PageData holds the default data needed for HTML pages to render
type PageData struct {
Title string
Messages []Message
@@ -27,11 +30,13 @@ type PageData struct {
CacheParameter string
}
+// Message holds a message which can be rendered as responses on HTML pages
type Message struct {
Type string // success, warning, error, etc.
Content string
}
+// isAuthenticated checks if the current user is authenticated or not
func isAuthenticated(c *gin.Context) bool {
_, exists := c.Get(middleware.UserIDKey)
return exists
diff --git a/routes/noroute.go b/routes/noroute.go
index ddd89d9..66d16c5 100644
--- a/routes/noroute.go
+++ b/routes/noroute.go
@@ -5,6 +5,7 @@ import (
"net/http"
)
+// NoRoute handles rendering of the 404 page
func (controller Controller) NoRoute(c *gin.Context) {
pd := PageData{
Title: "404 Not Found",
diff --git a/routes/register.go b/routes/register.go
index 9b1a880..5bf58ce 100644
--- a/routes/register.go
+++ b/routes/register.go
@@ -6,7 +6,7 @@ import (
"github.com/go-playground/validator/v10"
email2 "github.com/uberswe/golang-base-project/email"
"github.com/uberswe/golang-base-project/models"
- "github.com/uberswe/golang-base-project/util"
+ "github.com/uberswe/golang-base-project/ulid"
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
"log"
@@ -16,6 +16,7 @@ import (
"time"
)
+// Register renders the HTML content of the register page
func (controller Controller) Register(c *gin.Context) {
pd := PageData{
Title: "Register",
@@ -25,6 +26,7 @@ func (controller Controller) Register(c *gin.Context) {
c.HTML(http.StatusOK, "register.html", pd)
}
+// RegisterPost handles requests to register users and returns appropriate messages as HTML content
func (controller Controller) RegisterPost(c *gin.Context) {
passwordError := "Your password must be 8 characters in length or longer"
registerError := "Could not register, please make sure the details you have provided are correct and that you do not already have an existing account."
@@ -121,7 +123,7 @@ func (controller Controller) RegisterPost(c *gin.Context) {
func (controller Controller) activationEmailHandler(userID uint, email string) {
activationToken := models.Token{
- Value: util.GenerateULID(),
+ Value: ulid.Generate(),
Type: models.TokenUserActivation,
}
diff --git a/routes/resendactivation.go b/routes/resendactivation.go
index 2d710d1..0103d1f 100644
--- a/routes/resendactivation.go
+++ b/routes/resendactivation.go
@@ -7,6 +7,7 @@ import (
"net/http"
)
+// ResendActivation renders the HTML page used to request a new activation email
func (controller Controller) ResendActivation(c *gin.Context) {
pd := PageData{
Title: "Resend Activation Email",
@@ -16,6 +17,7 @@ func (controller Controller) ResendActivation(c *gin.Context) {
c.HTML(http.StatusOK, "resendactivation.html", pd)
}
+// ResendActivationPost handles the post request for requesting a new activation email
func (controller Controller) ResendActivationPost(c *gin.Context) {
pd := PageData{
Title: "Resend Activation Email",
diff --git a/routes/resetpassword.go b/routes/resetpassword.go
index 4892400..41f5a24 100644
--- a/routes/resetpassword.go
+++ b/routes/resetpassword.go
@@ -8,11 +8,13 @@ import (
"net/http"
)
+// ResetPasswordPageData defines additional data needed to render the reset password page
type ResetPasswordPageData struct {
PageData
Token string
}
+// ResetPassword renders the HTML page for resetting the users password
func (controller Controller) ResetPassword(c *gin.Context) {
token := c.Param("token")
pd := ResetPasswordPageData{
@@ -26,6 +28,7 @@ func (controller Controller) ResetPassword(c *gin.Context) {
c.HTML(http.StatusOK, "resetpassword.html", pd)
}
+// ResetPasswordPost handles post request used to reset users passwords
func (controller Controller) ResetPasswordPost(c *gin.Context) {
passwordError := "Your password must be 8 characters in length or longer"
resetError := "Could not reset password, please try again"
diff --git a/routes/search.go b/routes/search.go
index 2bf771d..e079ff4 100644
--- a/routes/search.go
+++ b/routes/search.go
@@ -8,11 +8,13 @@ import (
"net/http"
)
+// SearchData holds additional data needed to render the search HTML page
type SearchData struct {
PageData
Results []models.Website
}
+// Search renders the search HTML page and any search results
func (controller Controller) Search(c *gin.Context) {
pd := SearchData{
PageData: PageData{
diff --git a/text/html.go b/text/html.go
new file mode 100644
index 0000000..d305bc6
--- /dev/null
+++ b/text/html.go
@@ -0,0 +1,24 @@
+package text
+
+import (
+ "fmt"
+ "net/url"
+ "strings"
+)
+
+// Nl2Br converts \n to HTML
tags
+func Nl2Br(s string) string {
+ return strings.Replace(s, "\n", "
", -1)
+}
+
+// LinkToHTMLLink converts regular links to HTML links
+func LinkToHTMLLink(s string) string {
+ s = strings.Replace(s, "\n", " \n ", -1)
+ for _, p := range strings.Split(s, " ") {
+ u, err := url.ParseRequestURI(p)
+ if err == nil && u.Scheme != "" {
+ s = strings.Replace(s, p, fmt.Sprintf("%s", p, p), 1)
+ }
+ }
+ return strings.Replace(s, " \n ", "\n", -1)
+}
diff --git a/text/random.go b/text/random.go
new file mode 100644
index 0000000..699b202
--- /dev/null
+++ b/text/random.go
@@ -0,0 +1,15 @@
+package text
+
+import "math/rand"
+
+// RandomString generates a random string of n length. Based on https://stackoverflow.com/a/22892986/1260548
+func RandomString(n int) string {
+ // remove vowels to make it less likely to generate something offensive
+ var letters = []rune("bcdfghjklmnpqrstvwxzBCDFGHJKLMNPQRSTVWXZ")
+
+ b := make([]rune, n)
+ for i := range b {
+ b[i] = letters[rand.Intn(len(letters))]
+ }
+ return string(b)
+}
diff --git a/text/segment.go b/text/segment.go
new file mode 100644
index 0000000..7c72ef7
--- /dev/null
+++ b/text/segment.go
@@ -0,0 +1,17 @@
+package text
+
+import "strings"
+
+// BetweenStrings returns a string between the starting and ending string or an empty string if none could be found
+func BetweenStrings(str string, start string, end string) (result string) {
+ s := strings.Index(str, start)
+ if s == -1 {
+ return
+ }
+ s += len(start)
+ e := strings.Index(str[s:], end)
+ if e == -1 {
+ return
+ }
+ return str[s : s+e]
+}
diff --git a/util/ulid.go b/ulid/generate.go
similarity index 76%
rename from util/ulid.go
rename to ulid/generate.go
index 52ae290..c60203a 100644
--- a/util/ulid.go
+++ b/ulid/generate.go
@@ -1,4 +1,4 @@
-package util
+package ulid
import (
"github.com/oklog/ulid/v2"
@@ -6,7 +6,8 @@ import (
"time"
)
-func GenerateULID() string {
+// Generate a new ULID string
+func Generate() string {
entropy := ulid.Monotonic(rand.New(rand.NewSource(time.Now().UnixNano())), 0)
res := ulid.MustNew(ulid.Timestamp(time.Now()), entropy)
return res.String()
diff --git a/util/text.go b/util/text.go
deleted file mode 100644
index 73af946..0000000
--- a/util/text.go
+++ /dev/null
@@ -1,49 +0,0 @@
-// Package util provides utility functions for various things such as strings
-package util
-
-import (
- "fmt"
- "math/rand"
- "net/url"
- "strings"
-)
-
-func NL2BR(s string) string {
- return strings.Replace(s, "\n", "
", -1)
-}
-
-func StringLinkToHTMLLink(s string) string {
- s = strings.Replace(s, "\n", " \n ", -1)
- for _, p := range strings.Split(s, " ") {
- u, err := url.ParseRequestURI(p)
- if err == nil && u.Scheme != "" {
- s = strings.Replace(s, p, fmt.Sprintf("%s", p, p), 1)
- }
- }
- return strings.Replace(s, " \n ", "\n", -1)
-}
-
-func GetStringBetweenStrings(str string, start string, end string) (result string) {
- s := strings.Index(str, start)
- if s == -1 {
- return
- }
- s += len(start)
- e := strings.Index(str[s:], end)
- if e == -1 {
- return
- }
- return str[s : s+e]
-}
-
-// RandomString generates a random string of n length. Based on https://stackoverflow.com/a/22892986/1260548
-func RandomString(n int) string {
- // remove vowels to make it less likely to generate something offensive
- var letters = []rune("bcdfghjklmnpqrstvwxzBCDFGHJKLMNPQRSTVWXZ")
-
- b := make([]rune, n)
- for i := range b {
- b[i] = letters[rand.Intn(len(letters))]
- }
- return string(b)
-}