diff --git a/go.mod b/go.mod index 80ad589..f495fed 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,17 @@ module ersteller-lib go 1.24 + +require ( + github.com/jackc/pgx/v5 v5.7.5 + github.com/mattn/go-sqlite3 v1.14.29 + golang.org/x/crypto v0.40.0 +) + +require ( + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect + golang.org/x/sync v0.16.0 // indirect + golang.org/x/text v0.27.0 // indirect +) diff --git a/hash.go b/hash.go new file mode 100644 index 0000000..775505a --- /dev/null +++ b/hash.go @@ -0,0 +1,16 @@ +package ersteller_lib + +// https://medium.com/@rnp0728/secure-password-hashing-in-go-a-comprehensive-guide-5500e19e7c1f +import "golang.org/x/crypto/bcrypt" + +// HashPassword generates a bcrypt hash for the given password. +func HashPassword(password string) (string, error) { + bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14) + return string(bytes), err +} + +// VerifyPassword verifies if the given password matches the stored hash. +func VerifyPassword(password, hash string) bool { + err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) + return err == nil +} diff --git a/logger.go b/logger.go new file mode 100644 index 0000000..d91df7a --- /dev/null +++ b/logger.go @@ -0,0 +1,32 @@ +package ersteller_lib + +import ( + "fmt" + "strings" +) + +func LogDebug(message string, a ...any) { + println(fmt.Sprintf(message, a...)) +} + +func Debug(a ...any) { + stringValue := joinStrings(a) + println(stringValue) +} + +func joinStrings(a []any) string { + elementsToLog := []string{} + + for i := 0; i < len(a); i++ { + elementsToLog = append(elementsToLog, strings.TrimSpace(fmt.Sprint(a[i]))) + } + return strings.Join(elementsToLog, " ") +} + +func LogError(message string, a ...any) { + println(fmt.Sprintf("Error: %v", fmt.Sprintf(message, a...))) +} + +func Error(a ...any) { + println(fmt.Sprint("Error: ", joinStrings(a))) +} diff --git a/postgres.go b/postgres.go new file mode 100644 index 0000000..f96883b --- /dev/null +++ b/postgres.go @@ -0,0 +1,54 @@ +package ersteller_lib + +import ( + "context" + "github.com/jackc/pgx/v5/pgxpool" + "time" +) + +func CreatePostgresConnpool(dbUrl string) (*pgxpool.Pool, error) { + connPool, err := pgxpool.NewWithConfig(context.Background(), config(dbUrl)) + if err != nil { + Error("Error while creating connection to the database!!", err) + return nil, err + } + + connection, err := connPool.Acquire(context.Background()) + if err != nil { + LogError("Error while acquiring connection from the database pool!! %v", err) + return nil, err + } + defer connection.Release() + + err = connection.Ping(context.Background()) + if err != nil { + LogError("Could not ping database") + return nil, err + } + + return connPool, nil +} + +func config(dbUrl string) *pgxpool.Config { + const defaultMaxConns = int32(4) + const defaultMinConns = int32(0) + const defaultMaxConnLifetime = time.Hour + const defaultMaxConnIdleTime = time.Minute * 30 + const defaultHealthCheckPeriod = time.Minute + const defaultConnectTimeout = time.Second * 5 + + dbConfig, err := pgxpool.ParseConfig(dbUrl) + if err != nil { + Error("Failed to create a config, error: ", err) + return nil + } + + dbConfig.MaxConns = defaultMaxConns + dbConfig.MinConns = defaultMinConns + dbConfig.MaxConnLifetime = defaultMaxConnLifetime + dbConfig.MaxConnIdleTime = defaultMaxConnIdleTime + dbConfig.HealthCheckPeriod = defaultHealthCheckPeriod + dbConfig.ConnConfig.ConnectTimeout = defaultConnectTimeout + + return dbConfig +} diff --git a/sqlite.go b/sqlite.go new file mode 100644 index 0000000..78e3001 --- /dev/null +++ b/sqlite.go @@ -0,0 +1,29 @@ +package ersteller_lib + +import ( + "context" + "database/sql" + _ "github.com/mattn/go-sqlite3" +) + +// CreateSQLiteConnpool creates a new SQLite connection pool +func CreateSQLiteConnpool(dbPath string) (*sql.DB, error) { + db, err := sql.Open("sqlite3", dbPath) + if err != nil { + Error("Error while creating connection to the SQLite database!!", err) + return nil, err + } + + // Test the connection + err = db.PingContext(context.Background()) + if err != nil { + LogError("Could not ping SQLite database: %v", err) + return nil, err + } + + // Set connection pool settings + db.SetMaxOpenConns(4) + db.SetMaxIdleConns(2) + + return db, nil +} diff --git a/strings.go b/strings.go new file mode 100644 index 0000000..09f0ab2 --- /dev/null +++ b/strings.go @@ -0,0 +1,37 @@ +package ersteller_lib + +import ( + "bytes" + "fmt" + "regexp" + "strings" + "text/template" +) + +func FirstLetterToUpper(name string) string { + return strings.ToUpper(name[:1]) + name[1:] +} + +func FirstLetterToLower(name string) string { + return strings.ToLower(name[:1]) + name[1:] +} + +func InlineTemplate(templateString string, data any) string { + dollarRegex := regexp.MustCompile(`\$(\w+)\$`) + templateString = dollarRegex.ReplaceAllStringFunc(templateString, func(s string) string { + if strings.HasPrefix(s, "$.") || strings.HasPrefix(s, "$end") || strings.HasPrefix(s, "$else") { + return s + } + return dollarRegex.ReplaceAllString(s, "$.$1$") + }) + tmpl, err := template.New("inline").Delims("$", "$").Parse(templateString) + if err != nil { + return fmt.Sprint("Failed to parse template ", err) + } + var buf bytes.Buffer + err = tmpl.Execute(&buf, data) + if err != nil { + return fmt.Sprint("Failed to execute template ", err) + } + return buf.String() +} diff --git a/tracing.go b/tracing.go new file mode 100644 index 0000000..0de8dfb --- /dev/null +++ b/tracing.go @@ -0,0 +1,14 @@ +package ersteller_lib + +import ( + "encoding/json" + "fmt" +) + +func Trace(object any) string { + marshalled, err := json.Marshal(object) + if err != nil { + return fmt.Sprint(object) + } + return string(marshalled) +}