From 8c37faf016ea24396fb9060ff904ef23a0520a1a Mon Sep 17 00:00:00 2001 From: uberswe Date: Sun, 9 Jan 2022 14:22:43 +0100 Subject: [PATCH] Adds godoc comments --- README.md | 6 ++++ config/main.go | 1 + email/main.go | 11 ++++--- main.go | 33 ++++++++++++++++++++- middleware/auth.go | 2 ++ middleware/cache.go | 1 + middleware/noauth.go | 2 -- middleware/session.go | 6 +++- middleware/throttle.go | 1 + models/session.go | 2 ++ models/token.go | 6 +++- models/user.go | 1 + models/website.go | 1 + routes/activate.go | 1 + routes/admin.go | 1 + routes/forgotpassword.go | 6 ++-- routes/index.go | 1 + routes/login.go | 8 ++++-- routes/logout.go | 3 +- routes/main.go | 5 ++++ routes/noroute.go | 1 + routes/register.go | 6 ++-- routes/resendactivation.go | 2 ++ routes/resetpassword.go | 3 ++ routes/search.go | 2 ++ text/html.go | 24 ++++++++++++++++ text/random.go | 15 ++++++++++ text/segment.go | 17 +++++++++++ util/ulid.go => ulid/generate.go | 5 ++-- util/text.go | 49 -------------------------------- 30 files changed, 154 insertions(+), 68 deletions(-) create mode 100644 text/html.go create mode 100644 text/random.go create mode 100644 text/segment.go rename util/ulid.go => ulid/generate.go (76%) delete mode 100644 util/text.go 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 +[![GoDoc](https://godoc.org/github.com/uberswe/golang-base-project?status.svg)](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) -}