Finished
This commit is contained in:
31
lib/email.go
Normal file
31
lib/email.go
Normal file
@ -0,0 +1,31 @@
|
||||
package lib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/resendlabs/resend-go"
|
||||
)
|
||||
|
||||
var client *resend.Client
|
||||
|
||||
// init function
|
||||
func init() {
|
||||
client = resend.NewClient(os.Getenv("RESEND_API_KEY"))
|
||||
}
|
||||
|
||||
func SendEmail(to_email string, from_email string, from_name string, html string, subject string) {
|
||||
params := &resend.SendEmailRequest{
|
||||
From: from_name + "<" + from_email + ">",
|
||||
To: []string{to_email},
|
||||
Html: html,
|
||||
Subject: subject,
|
||||
}
|
||||
|
||||
sent, err := client.Emails.Send(params)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
fmt.Println(sent.Id)
|
||||
}
|
59
lib/markdown.go
Normal file
59
lib/markdown.go
Normal file
@ -0,0 +1,59 @@
|
||||
package lib
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/yuin/goldmark"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
func ExtractFrontMatter(file os.DirEntry, dir string) (CardLink, error) {
|
||||
f, err := os.Open(dir + file.Name())
|
||||
if err != nil {
|
||||
return CardLink{}, fmt.Errorf("failed to open file: %w", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
scanner := bufio.NewScanner(f)
|
||||
var lines []string
|
||||
for scanner.Scan() {
|
||||
lines = append(lines, scanner.Text())
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return CardLink{}, fmt.Errorf("failed to read file: %w", err)
|
||||
}
|
||||
|
||||
content := strings.Join(lines, "\n")
|
||||
splitContent := strings.SplitN(content, "---", 3)
|
||||
if len(splitContent) < 3 {
|
||||
return CardLink{}, fmt.Errorf("invalid file format: %s", file.Name())
|
||||
}
|
||||
|
||||
frontMatter := CardLink{}
|
||||
if err := yaml.Unmarshal([]byte(splitContent[1]), &frontMatter); err != nil {
|
||||
return CardLink{}, fmt.Errorf("failed to unmarshal frontmatter: %w", err)
|
||||
}
|
||||
|
||||
md := goldmark.New(goldmark.WithExtensions())
|
||||
var buf bytes.Buffer
|
||||
if err := md.Convert([]byte(splitContent[2]), &buf); err != nil {
|
||||
return CardLink{}, fmt.Errorf("failed to convert markdown: %w", err)
|
||||
}
|
||||
|
||||
return frontMatter, nil
|
||||
}
|
||||
|
||||
func SplitFrontmatter(md []byte) (frontmatter []byte, content []byte, err error) {
|
||||
parts := bytes.SplitN(md, []byte("---"), 3)
|
||||
|
||||
if len(parts) < 3 {
|
||||
return nil, nil, errors.New("invalid or missing frontmatter")
|
||||
}
|
||||
|
||||
return parts[1], parts[2], nil
|
||||
}
|
57
lib/markdown_test.go
Normal file
57
lib/markdown_test.go
Normal file
@ -0,0 +1,57 @@
|
||||
package lib_test
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/alecthomas/assert/v2"
|
||||
"goth.stack/lib"
|
||||
)
|
||||
|
||||
func TestExtractFrontMatter(t *testing.T) {
|
||||
// Create a temporary file with some front matter
|
||||
tmpfile, err := os.CreateTemp("../content", "example.*.md")
|
||||
println(tmpfile.Name())
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer os.Remove(tmpfile.Name()) // clean up
|
||||
|
||||
text := `---
|
||||
name: "Test Title"
|
||||
description: "Test Description"
|
||||
---
|
||||
|
||||
# Test Content
|
||||
|
||||
`
|
||||
if _, err := tmpfile.Write([]byte(text)); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := tmpfile.Close(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Get the directory entry for the temporary file
|
||||
dirEntry, err := os.ReadDir(filepath.Dir(tmpfile.Name()))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
var tmpFileEntry fs.DirEntry
|
||||
for _, entry := range dirEntry {
|
||||
if entry.Name() == filepath.Base(tmpfile.Name()) {
|
||||
tmpFileEntry = entry
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Now we can test ExtractFrontMatter
|
||||
frontMatter, err := lib.ExtractFrontMatter(tmpFileEntry, "../content/")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "Test Title", frontMatter.Name)
|
||||
assert.Equal(t, "Test Description", frontMatter.Description)
|
||||
}
|
48
lib/redis.go
Normal file
48
lib/redis.go
Normal file
@ -0,0 +1,48 @@
|
||||
package lib
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
var ctx = context.Background()
|
||||
|
||||
var RedisClient *redis.Client
|
||||
|
||||
func NewClient() *redis.Client {
|
||||
godotenv.Load(".env")
|
||||
redis_host := os.Getenv("REDIS_HOST")
|
||||
redis_password := os.Getenv("REDIS_PASSWORD")
|
||||
|
||||
log.Printf("Connecting to Redis at %s", redis_host)
|
||||
return redis.NewClient(&redis.Options{
|
||||
Addr: redis_host,
|
||||
Password: redis_password,
|
||||
DB: 0,
|
||||
})
|
||||
}
|
||||
|
||||
func Publish(client *redis.Client, channel string, message string) error {
|
||||
if client == nil {
|
||||
client = NewClient()
|
||||
}
|
||||
|
||||
return client.Publish(ctx, channel, message).Err()
|
||||
}
|
||||
|
||||
func Subscribe(client *redis.Client, channel string) (*redis.PubSub, string) {
|
||||
if client == nil {
|
||||
client = NewClient()
|
||||
}
|
||||
|
||||
pubsub := client.Subscribe(ctx, channel)
|
||||
_, err := pubsub.Receive(ctx)
|
||||
if err != nil {
|
||||
log.Fatalf("Error receiving subscription: %v", err)
|
||||
}
|
||||
return pubsub, channel
|
||||
}
|
27
lib/redis_test.go
Normal file
27
lib/redis_test.go
Normal file
@ -0,0 +1,27 @@
|
||||
package lib_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/go-redis/redismock/v9"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"goth.stack/lib"
|
||||
)
|
||||
|
||||
func TestPublish(t *testing.T) {
|
||||
db, mock := redismock.NewClientMock()
|
||||
mock.ExpectPublish("mychannel", "mymessage").SetVal(1)
|
||||
|
||||
err := lib.Publish(db, "mychannel", "mymessage")
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, mock.ExpectationsWereMet())
|
||||
}
|
||||
|
||||
// Then you can check the channel name in your test
|
||||
func TestSubscribe(t *testing.T) {
|
||||
db, _ := redismock.NewClientMock()
|
||||
|
||||
pubsub, channel := lib.Subscribe(db, "mychannel")
|
||||
assert.NotNil(t, pubsub)
|
||||
assert.Equal(t, "mychannel", channel)
|
||||
}
|
20
lib/sse.go
Normal file
20
lib/sse.go
Normal file
@ -0,0 +1,20 @@
|
||||
package lib
|
||||
|
||||
func SendSSE(channel string, message string) error {
|
||||
// Create a channel to receive an error from the goroutine
|
||||
errCh := make(chan error, 1)
|
||||
|
||||
// Use a goroutine to send the message asynchronously
|
||||
go func() {
|
||||
err := Publish(RedisClient, channel, message)
|
||||
errCh <- err // Send the error to the channel
|
||||
}()
|
||||
|
||||
// Wait for the goroutine to finish and check for errors
|
||||
err := <-errCh
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
37
lib/types.go
Normal file
37
lib/types.go
Normal file
@ -0,0 +1,37 @@
|
||||
package lib
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
)
|
||||
|
||||
type IconLink struct {
|
||||
Name string
|
||||
Href string
|
||||
Icon template.HTML
|
||||
}
|
||||
|
||||
type CardLink struct {
|
||||
Name string
|
||||
Href string
|
||||
Description string
|
||||
Date string
|
||||
Tags []string
|
||||
Internal bool
|
||||
}
|
||||
|
||||
type Post struct {
|
||||
Content template.HTML
|
||||
Name string
|
||||
Date string
|
||||
Tags []string
|
||||
}
|
||||
type FrontMatter struct {
|
||||
Name string
|
||||
Date string
|
||||
Tags []string
|
||||
}
|
||||
|
||||
type PubSubMessage struct {
|
||||
Channel string `json:"channel"`
|
||||
Data string `json:"data"`
|
||||
}
|
Reference in New Issue
Block a user