all repos — goldfinch @ dff0c06826b3b917bec7d06431a50751b0a0afdf

Merge restructure

FossilOrigin-Name: d7cc4d9e3961ba37753081a509ad517fa16ede8e0bfe3193d53ee36e916b89be
wing
Sun, 10 Mar 2024 01:33:38 +0000
commit

dff0c06826b3b917bec7d06431a50751b0a0afdf

parent

5bd61368c88b2e2d47146851332b073e1328e816

9 files changed, 786 insertions(+), 772 deletions(-)

jump to
M config.goconfig.go

@@ -11,22 +11,23 @@

var config Config type Config struct { - SiteName string `yaml:"siteName"` - SiteDescription string `yaml:"siteDescription"` - SiteURL string `yaml:"siteURL"` - Nav []map[string]string `yaml:"nav"` - Feeds map[string]string `yaml:"feeds"` - ListenAddr string `yaml:"listenAddr"` - StartPage string `yaml:"startPage"` - Template string `yaml:"template"` - BaseKey string `yaml:"baseKey"` - SiteKey string `yaml:"siteKey"` - FolderData string `yaml:"dataFolder"` - FolderTemplates string `yaml:"templatesFolder"` - FolderLogs string `yaml:"logsFolder"` - FolderContent string - FolderStatic string - ActiveTemplate string + SiteName string `yaml:"siteName"` + SiteDescription string `yaml:"siteDescription"` + SiteURL string `yaml:"siteURL"` + Nav []map[string]string `yaml:"nav"` + Feeds map[string]string `yaml:"feeds"` + ListenAddr string `yaml:"listenAddr"` + StartPage string `yaml:"startPage"` + Template string `yaml:"template"` + BaseKey string `yaml:"baseKey"` + SiteKey string `yaml:"siteKey"` + DatabaseLocation string `yaml:"databaseLocation"` + DirectoryData string `yaml:"dataDirectory"` + DirectoryTemplates string `yaml:"templatesDirectory"` + DirectoryLogs string `yaml:"logsDirectory"` + DirectoryContent string + DirectoryStatic string + ActiveTemplate string } // Load and parse the config file, then save it to the config variable

@@ -44,9 +45,9 @@ log.Fatal("Couldn't parse config file")

} // Create content and static folder paths - config.FolderContent = path.Join(config.FolderData, "content") - config.FolderStatic = path.Join(config.FolderData, "static") + config.DirectoryContent = path.Join(config.DirectoryData, "content") + config.DirectoryStatic = path.Join(config.DirectoryData, "static") // Add the current template path - config.ActiveTemplate = path.Join(config.FolderTemplates, config.Template) + config.ActiveTemplate = path.Join(config.DirectoryTemplates, config.Template) }
D db.go

@@ -1,659 +0,0 @@

-package main - -import ( - "database/sql" - "html/template" - "os" - "path" - - _ "modernc.org/sqlite" -) - -var db *sql.DB - -type page struct { - ID int64 - Title string `yaml:"title"` - Published string `yaml:"published"` - Edited string `yaml:"edited"` - Tags []string `yaml:"tags"` - Status string `yaml:"status"` - AudioFile string `yaml:"audioFile"` - PrivateKey string `yaml:"privateKey"` - AppendIndex string `yaml:"appendIndex"` - Content template.HTML -} - -type indexPage struct { - Title string - Published string - URLPath string -} - -type feedItem struct { - Title string - Published string - Content string - AudioFile string - URLPath string -} - -type tag struct { - ID int64 - Description string -} - -type tagPageCount struct { - Description string - Count int -} - -type pagePath struct { - pageID int64 - filepath string - urlPath string -} - -type keyPath struct { - key string - path string -} - -// Create the database and build pages -func buildDB() error { - // Close database if it's running - if db != nil { - _ = db.Close() - } - - // Remove old database - dbLocation := path.Join(config.FolderData, "goldfinch.db") - _ = os.Remove(dbLocation) - - // Create connection pool - DB, err := sql.Open("sqlite", dbLocation+"?_pragma=foreign_keys(1)") - if err != nil { - return err - } - - db = DB - - // Create tables - createTables() - - // Create site key - key := generateKey(config.SiteKey) - _, err = insertKey(key) - if err != nil { - logger.Fatal("Failed to create site key", err) - } - - // Build pages and write keys file - buildPages() - keysToFile() - return nil -} - -// Create required tables -func createTables() { - query := ` -CREATE TABLE IF NOT EXISTS pages ( - pageID INTEGER PRIMARY KEY, - title TEXT NOT NULL, - content TEXT, - audioFile TEXT DEFAULT '', - published TEXT, - edited TEXT, - status TEXT, - appendIndex TEXT -); -CREATE TABLE IF NOT EXISTS tags ( - tagID INTEGER PRIMARY KEY, - description TEXT NOT NULL UNIQUE -); -CREATE TABLE IF NOT EXISTS keys ( - keyID INTEGER PRIMARY KEY, - key TEXT NOT NULL -); -CREATE TABLE IF NOT EXISTS paths ( - pageID INTEGER, - filepath TEXT, - urlPath TEXT, - FOREIGN KEY(pageID) REFERENCES pages(pageID) -); -CREATE TABLE IF NOT EXISTS tagToPage ( - pageID INTEGER NOT NULL, - tagID INTEGER NOT NULL, - FOREIGN KEY(pageID) REFERENCES pages(pageID), - FOREIGN KEY(tagID) REFERENCES tags(tagID) -); -CREATE TABLE IF NOT EXISTS keyToPage ( - pageID INTEGER NOT NULL, - keyID INTEGER NOT NULL, - FOREIGN KEY(pageID) REFERENCES pages(pageID), - FOREIGN KEY(keyID) REFERENCES keys(keyID) -); -` - - _, err := db.Exec(query) - if err != nil { - logger.Fatalf("Failed to create tables\n%v", err) - } -} - -// Insert a page entry -func insertPage(p *page) (int64, error) { - query := ` -INSERT INTO pages (title, content, audioFile, published, edited, status, appendIndex) VALUES (?, ?, ?, ?, ?, ?, ?) -` - result, err := db.Exec(query, p.Title, p.Content, p.AudioFile, p.Published, p.Edited, p.Status, p.AppendIndex) - if err != nil { - return 0, err - } - - id, err := result.LastInsertId() - if err != nil { - return 0, err - } - - return id, nil -} - -// Create a tag entry -func insertTag(t *tag) (int64, error) { - // Check if tag exists - existingTag, _ := tagFromDescription(t.Description) - if existingTag.ID != 0 { - return existingTag.ID, nil - } - - // Add tag if it doesn't - result, err := db.Exec("INSERT INTO tags (description) VALUES (?)", t.Description) - if err != nil { - return 0, err - } - - // Get tag ID to return - id, err := result.LastInsertId() - if err != nil { - return 0, err - } - - return id, nil -} - -// Create a path entry -func insertPath(p *pagePath) error { - query := ` -INSERT INTO paths (pageID, filepath, urlPath) VALUES (?, ?, ?) -` - - _, err := db.Exec( - query, - p.pageID, - p.filepath, - p.urlPath, - ) - if err != nil { - return err - } - - return nil -} - -// Create tag to page relation -func insertTagToPage(pageID, tagID int64) error { - _, err := db.Exec("INSERT INTO tagToPage (pageID, tagID) VALUES (?, ?)", pageID, tagID) - if err != nil { - logger.Printf("Failed to associate tag ID: %v with page ID: %v\n%v\n", tagID, pageID, err) - return err - } - - return nil -} - -// Add key -func insertKey(key string) (int64, error) { - query := ` -INSERT INTO keys (key) VALUES (?) -` - result, err := db.Exec(query, key) - if err != nil { - return 0, err - } - - id, err := result.LastInsertId() - if err != nil { - return 0, err - } - - return id, nil -} - -// Add key to page association -func insertKeyToPage(keyID, pageID int64) error { - _, err := db.Exec("INSERT INTO keyToPage (pageID, keyID) VALUES (?, ?)", pageID, keyID) - if err != nil { - logger.Printf("Failed to associate key ID: %v with page ID: %v\n%v\n", keyID, pageID, err) - return err - } - - return nil -} - -// Return a single page based on urlPath given -func pageFromURLPath(urlPath string) (page, error) { - var p page - - query := ` -SELECT * -FROM - pages -WHERE - pageID = ( - SELECT - pageID - FROM - paths - WHERE - urlPath = ? - ) -` - - row := db.QueryRow(query, urlPath) - - err := row.Scan(&p.ID, &p.Title, &p.Content, &p.AudioFile, &p.Published, &p.Edited, &p.Status, &p.AppendIndex) - if err != nil { - return p, err - } - - return p, nil -} - -// Returns an slice of indexPages that have the given tag -func indexPagesFromTag(searchTag string) ([]indexPage, error) { - var searchResults []indexPage - - query := ` -SELECT pages.title, pages.published, paths.urlPath -FROM - pages -INNER JOIN paths ON paths.pageID = pages.pageID -WHERE - pages.pageID IN ( - SELECT - pageID - FROM - tagToPage - WHERE - tagID = ( - SELECT - tagID - FROM - tags - WHERE - description = ? - ) - ) -AND - status = '' -ORDER BY published DESC -` - - // Perform query - rows, err := db.Query(query, searchTag) - if err != nil { - return nil, err - } - defer rows.Close() - - // Add each page to the slice - for rows.Next() { - var single indexPage - - // Add query data to page - err = rows.Scan(&single.Title, &single.Published, &single.URLPath) - if err != nil { - return nil, err - } - - // Add page to slice - searchResults = append(searchResults, single) - } - - // Check for final errors - err = rows.Err() - if err != nil { - return nil, err - } - - return searchResults, nil -} - -// Returns an slice of pages that start with the given URL path -func indexPagesFromURLRoot(root string) ([]indexPage, error) { - var index []indexPage - - query := ` -SELECT pages.title, pages.published, paths.urlPath -FROM - pages -INNER JOIN paths ON paths.pageID = pages.pageID -WHERE - pages.pageID IN ( - SELECT - pageID - FROM - paths - WHERE - urlPath LIKE ? - ) -AND - status = '' -ORDER BY published DESC -` - - // Perform query - rows, err := db.Query(query, root+"%") - if err != nil { - return nil, err - } - defer rows.Close() - - // Add each page to the slice - for rows.Next() { - var single indexPage - - // Add query data to page - err = rows.Scan(&single.Title, &single.Published, &single.URLPath) - if err != nil { - return nil, err - } - - // Add page to slice - index = append(index, single) - } - - // Check for final errors - err = rows.Err() - if err != nil { - return nil, err - } - - return index, nil -} - -// Returns an slice of pages that start with the given URL path -func getFeedItems(feed string) ([]feedItem, error) { - var feedItems []feedItem - - query := ` -SELECT - pages.title, pages.published, pages.content, pages.audioFile, paths.urlPath -FROM - pages -INNER JOIN - paths ON paths.pageID = pages.pageID -WHERE - pages.pageID IN ( - SELECT - pageID - FROM - paths - WHERE - urlPath LIKE ? - ) -AND - status = '' -ORDER BY - published DESC -` - - // Perform query - rows, err := db.Query(query, config.Feeds[feed]+"%") - if err != nil { - return nil, err - } - defer rows.Close() - - // Add each page to the slice - for rows.Next() { - var single feedItem - - // Add query data to page - err = rows.Scan(&single.Title, &single.Published, &single.Content, &single.AudioFile, &single.URLPath) - if err != nil { - return nil, err - } - - // Add page to slice - feedItems = append(feedItems, single) - } - - // Check for final errors - err = rows.Err() - if err != nil { - return nil, err - } - - return feedItems, nil -} - -// Return pagePath based on ID given -func getPathFromID(id int64) (pagePath, error) { - var pPath pagePath - - row := db.QueryRow("SELECT * FROM paths WHERE pageID=?", id) - - err := row.Scan(&pPath.pageID, &pPath.filepath, &pPath.urlPath) - if err != nil { - logger.Println(err) - return pPath, err - } - - return pPath, nil -} - -// Return tag from description -func tagFromDescription(desc string) (tag, error) { - var t tag - - row := db.QueryRow("SELECT * FROM tags WHERE description = ?", desc) - - err := row.Scan(&t.ID, &t.Description) - if err != nil { - return t, err - } - - return t, nil -} - -// Return a slice of tag that match a page ID -func tagsFromPageID(pageID int64) ([]tag, error) { - query := ` - SELECT - description - FROM - tags - WHERE - tagID IN ( - SELECT tagID FROM tagToPage WHERE pageID=? - ) - ORDER BY description ASC -` - - var result []tag - - // Perform query - rows, err := db.Query(query, pageID) - if err != nil { - return nil, err - } - defer rows.Close() - - // Add each page to the slice - for rows.Next() { - var single tag - - // Add query data to single - err = rows.Scan(&single.Description) - if err != nil { - return nil, err - } - - // Add entry to slice - result = append(result, single) - } - - // Check for final errors - err = rows.Err() - if err != nil { - return nil, err - } - - return result, nil -} - -// Return a slice of tags with the number of pages that use them -func tagsWithPageCount() ([]tagPageCount, error) { - query := ` -SELECT - tags.description, COUNT(DISTINCT tagToPage.pageID) AS count -FROM - tags -INNER JOIN - tagToPage ON tags.tagID = tagToPage.tagID -INNER JOIN - pages ON tagToPage.pageID = pages.pageID -WHERE - pages.Status = '' -GROUP BY - tags.tagID -ORDER BY - tags.description ASC; -` - - var tagsWithCounts []tagPageCount - - // Perform query - rows, err := db.Query(query) - if err != nil { - return nil, err - } - defer rows.Close() - - // Add each page to the slice - for rows.Next() { - var single tagPageCount - - // Add query data to single - err = rows.Scan(&single.Description, &single.Count) - if err != nil { - return nil, err - } - - // Add entry to slice - tagsWithCounts = append(tagsWithCounts, single) - } - - // Check for final errors - err = rows.Err() - if err != nil { - return nil, err - } - - return tagsWithCounts, nil -} - -// Check if provided key is valid -func isValidKey(key, URLPath string) bool { - var result string - - query := ` - SELECT - keys.key - FROM - keys - INNER JOIN keyToPage on keys.keyID = keyToPage.keyID - INNER JOIN paths ON keyToPage.pageID = paths.pageID - WHERE - keys.key = ? - AND - paths.urlPath = ? -` - - row := db.QueryRow(query, key, URLPath) - err := row.Scan(&result) - if err != nil { - return false - } - - return true -} - -func keysAndPaths() ([]keyPath, error) { - var result []keyPath - - query := ` - SELECT - paths.urlPath, keys.key - FROM - keys - INNER JOIN keyToPage on keys.keyID = keyToPage.keyID - INNER JOIN paths ON keyToPage.pageID = paths.pageID - ORDER BY - paths.urlPath ASC -` - // Perform query - rows, err := db.Query(query) - if err != nil { - return nil, err - } - defer rows.Close() - - // Add each page to the slice - for rows.Next() { - var single keyPath - - // Add query data to single - err = rows.Scan(&single.path, &single.key) - if err != nil { - return nil, err - } - - // Add entry to slice - result = append(result, single) - } - - // Check for final errors - err = rows.Err() - if err != nil { - return nil, err - } - - return result, nil -} - -func checkSiteKey(key string) (bool, error) { - query := ` -SELECT - key -FROM - keys -WHERE - keyID = 1 -AND - key = ? -` - - var result string - - row := db.QueryRow(query, key) - - err := row.Scan(&result) - if err != nil { - return false, err - } - - return true, nil -}
A db/db.go

@@ -0,0 +1,459 @@

+package db + +import ( + "database/sql" + "html/template" + "os" + + "goldfinch/logger" + + _ "modernc.org/sqlite" +) + +var db *sql.DB + +type Page struct { + ID int64 + Title string `yaml:"title"` + Published string `yaml:"published"` + Edited string `yaml:"edited"` + Tags []string `yaml:"tags"` + Status string `yaml:"status"` + AudioFile string `yaml:"audioFile"` + PrivateKey string `yaml:"privateKey"` + AppendIndex string `yaml:"appendIndex"` + Content template.HTML +} + +type IndexPage struct { + Title string + Published string + URLPath string +} + +type FeedItem struct { + Title string + Published string + Content string + AudioFile string + URLPath string +} + +type Tag struct { + ID int64 + Description string +} + +type TagPageCount struct { + Description string + Count int +} + +type PagePath struct { + PageID int64 + Filepath string + UrlPath string +} + +type KeyPath struct { + Key string + Path string +} + +// Create the database and build pages +func OpenDB(dbLocation string) error { + // Close database if it's running + if db != nil { + _ = db.Close() + } + + // Remove old database + _ = os.Remove(dbLocation) + + // Create connection pool + DB, err := sql.Open("sqlite", dbLocation+"?_pragma=foreign_keys(1)") + if err != nil { + return err + } + + db = DB + + // Create tables + CreateTables() + + return nil +} + +// Create required tables +func CreateTables() { + _, err := db.Exec(sqlCreate) + if err != nil { + logger.Log.Fatalf("Failed to create tables\n%v", err) + } +} + +func Close() { + db.Close() +} + +// Insert a page entry +func InsertPage(p *Page) (int64, error) { + result, err := db.Exec(sqlInsertPage, p.Title, p.Content, p.AudioFile, p.Published, p.Edited, p.Status, p.AppendIndex) + if err != nil { + return 0, err + } + + id, err := result.LastInsertId() + if err != nil { + return 0, err + } + + return id, nil +} + +// Create a tag entry +func InsertTag(t *Tag) (int64, error) { + // Check if tag exists + existingTag, _ := TagFromDescription(t.Description) + if existingTag.ID != 0 { + return existingTag.ID, nil + } + + // Add tag if it doesn't + result, err := db.Exec(sqlInsertTag, t.Description) + if err != nil { + return 0, err + } + + // Get tag ID to return + id, err := result.LastInsertId() + if err != nil { + return 0, err + } + + return id, nil +} + +// Create a path entry +func InsertPath(p *PagePath) error { + _, err := db.Exec( + sqlInsertPath, + p.PageID, + p.Filepath, + p.UrlPath, + ) + if err != nil { + return err + } + + return nil +} + +// Create tag to page relation +func InsertTagToPage(pageID, tagID int64) error { + _, err := db.Exec(sqlInsertTagToPage, pageID, tagID) + if err != nil { + logger.Log.Printf("Failed to associate tag ID: %v with page ID: %v\n%v\n", tagID, pageID, err) + return err + } + + return nil +} + +// Add key +func InsertKey(key string) (int64, error) { + result, err := db.Exec(sqlInsertKey, key) + if err != nil { + return 0, err + } + + id, err := result.LastInsertId() + if err != nil { + return 0, err + } + + return id, nil +} + +// Add key to page association +func InsertKeyToPage(keyID, pageID int64) error { + _, err := db.Exec(sqlInsertKeyToPage, pageID, keyID) + if err != nil { + logger.Log.Printf("Failed to associate key ID: %v with page ID: %v\n%v\n", keyID, pageID, err) + return err + } + + return nil +} + +// Return a single page based on urlPath given +func PageFromURLPath(urlPath string) (Page, error) { + var p Page + + row := db.QueryRow(sqlPageFromURLPath, urlPath) + + err := row.Scan(&p.ID, &p.Title, &p.Content, &p.AudioFile, &p.Published, &p.Edited, &p.Status, &p.AppendIndex) + if err != nil { + return p, err + } + + return p, nil +} + +// Returns an slice of indexPages that have the given tag +func IndexPagesFromTag(searchTag string) ([]IndexPage, error) { + var searchResults []IndexPage + + // Perform query + rows, err := db.Query(sqlIndexPagesFromTag, searchTag) + if err != nil { + return nil, err + } + defer rows.Close() + + // Add each page to the slice + for rows.Next() { + var single IndexPage + + // Add query data to page + err = rows.Scan(&single.Title, &single.Published, &single.URLPath) + if err != nil { + return nil, err + } + + // Add page to slice + searchResults = append(searchResults, single) + } + + // Check for final errors + err = rows.Err() + if err != nil { + return nil, err + } + + return searchResults, nil +} + +// Returns an slice of pages that start with the given URL path +func IndexPagesFromURLRoot(root string) ([]IndexPage, error) { + var index []IndexPage + + // Perform query + rows, err := db.Query(sqlIndexPagesFromURLRoot, root+"%") + if err != nil { + return nil, err + } + defer rows.Close() + + // Add each page to the slice + for rows.Next() { + var single IndexPage + + // Add query data to page + err = rows.Scan(&single.Title, &single.Published, &single.URLPath) + if err != nil { + return nil, err + } + + // Add page to slice + index = append(index, single) + } + + // Check for final errors + err = rows.Err() + if err != nil { + return nil, err + } + + return index, nil +} + +// Returns an slice of pages that start with the given URL path +func GetFeedItems(feed string) ([]FeedItem, error) { + var feedItems []FeedItem + + // Perform query + rows, err := db.Query(sqlFeedItems, feed+"%") + if err != nil { + return nil, err + } + defer rows.Close() + + // Add each page to the slice + for rows.Next() { + var single FeedItem + + // Add query data to page + err = rows.Scan(&single.Title, &single.Published, &single.Content, &single.AudioFile, &single.URLPath) + if err != nil { + return nil, err + } + + // Add page to slice + feedItems = append(feedItems, single) + } + + // Check for final errors + err = rows.Err() + if err != nil { + return nil, err + } + + return feedItems, nil +} + +// Return pagePath based on ID given +func GetPathFromID(id int64) (PagePath, error) { + var pPath PagePath + + row := db.QueryRow(sqlPathFromID, id) + + err := row.Scan(&pPath.PageID, &pPath.Filepath, &pPath.UrlPath) + if err != nil { + logger.Log.Println(err) + return pPath, err + } + + return pPath, nil +} + +// Return tag from description +func TagFromDescription(desc string) (Tag, error) { + var t Tag + + row := db.QueryRow(sqlTagFromDescription, desc) + + err := row.Scan(&t.ID, &t.Description) + if err != nil { + return t, err + } + + return t, nil +} + +// Return a slice of tag that match a page ID +func TagsFromPageID(pageID int64) ([]Tag, error) { + var result []Tag + + // Perform query + rows, err := db.Query(sqlTagsFromPageID, pageID) + if err != nil { + return nil, err + } + defer rows.Close() + + // Add each page to the slice + for rows.Next() { + var single Tag + + // Add query data to single + err = rows.Scan(&single.Description) + if err != nil { + return nil, err + } + + // Add entry to slice + result = append(result, single) + } + + // Check for final errors + err = rows.Err() + if err != nil { + return nil, err + } + + return result, nil +} + +// Return a slice of tags with the number of pages that use them +func TagsWithPageCount() ([]TagPageCount, error) { + var tagsWithCounts []TagPageCount + + // Perform query + rows, err := db.Query(sqlTagsWithPageCount) + if err != nil { + return nil, err + } + defer rows.Close() + + // Add each page to the slice + for rows.Next() { + var single TagPageCount + + // Add query data to single + err = rows.Scan(&single.Description, &single.Count) + if err != nil { + return nil, err + } + + // Add entry to slice + tagsWithCounts = append(tagsWithCounts, single) + } + + // Check for final errors + err = rows.Err() + if err != nil { + return nil, err + } + + return tagsWithCounts, nil +} + +// Check if provided key is valid +func IsValidKey(key, URLPath string) bool { + var result string + + row := db.QueryRow(sqlIsValidKey, key, URLPath) + err := row.Scan(&result) + if err != nil { + return false + } + + return true +} + +func KeysAndPaths() ([]KeyPath, error) { + var result []KeyPath + + // Perform query + rows, err := db.Query(sqlKeysAndPaths) + if err != nil { + return nil, err + } + defer rows.Close() + + // Add each page to the slice + for rows.Next() { + var single KeyPath + + // Add query data to single + err = rows.Scan(&single.Path, &single.Key) + if err != nil { + return nil, err + } + + // Add entry to slice + result = append(result, single) + } + + // Check for final errors + err = rows.Err() + if err != nil { + return nil, err + } + + return result, nil +} + +func CheckSiteKey(key string) (bool, error) { + var result string + + row := db.QueryRow(sqlSiteKey, key) + + err := row.Scan(&result) + if err != nil { + return false, err + } + + return true, nil +}
A db/sql.go

@@ -0,0 +1,189 @@

+package db + +const ( + sqlCreate = ` + CREATE TABLE IF NOT EXISTS pages ( + pageID INTEGER PRIMARY KEY, + title TEXT NOT NULL, + content TEXT, + audioFile TEXT DEFAULT '', + published TEXT, + edited TEXT, + status TEXT, + appendIndex TEXT + ); + CREATE TABLE IF NOT EXISTS tags ( + tagID INTEGER PRIMARY KEY, + description TEXT NOT NULL UNIQUE + ); + CREATE TABLE IF NOT EXISTS keys ( + keyID INTEGER PRIMARY KEY, + key TEXT NOT NULL + ); + CREATE TABLE IF NOT EXISTS paths ( + pageID INTEGER, + filepath TEXT, + urlPath TEXT, + FOREIGN KEY(pageID) REFERENCES pages(pageID) + ); + CREATE TABLE IF NOT EXISTS tagToPage ( + pageID INTEGER NOT NULL, + tagID INTEGER NOT NULL, + FOREIGN KEY(pageID) REFERENCES pages(pageID), + FOREIGN KEY(tagID) REFERENCES tags(tagID) + ); + CREATE TABLE IF NOT EXISTS keyToPage ( + pageID INTEGER NOT NULL, + keyID INTEGER NOT NULL, + FOREIGN KEY(pageID) REFERENCES pages(pageID), + FOREIGN KEY(keyID) REFERENCES keys(keyID) + );` + + sqlInsertPage = ` + INSERT INTO pages ( + title, content, audioFile, published, edited, status, appendIndex + ) VALUES ( + ?, ?, ?, ?, ?, ?, ? + )` + + sqlInsertTag = `INSERT INTO tags (description) VALUES (?)` + + sqlInsertPath = `INSERT INTO paths (pageID, filepath, urlPath) VALUES (?, ?, ?)` + + sqlInsertTagToPage = `INSERT INTO tagToPage (pageID, tagID) VALUES (?, ?)` + + sqlInsertKey = `INSERT INTO keys (key) VALUES (?)` + + sqlInsertKeyToPage = `INSERT INTO keyToPage (pageID, keyID) VALUES (?, ?)` + + sqlPageFromURLPath = `SELECT * FROM pages WHERE pageID = (SELECT pageID FROM paths WHERE urlPath = ?)` + + sqlIndexPagesFromTag = ` + SELECT + pages.title, pages.published, paths.urlPath + FROM + pages + INNER JOIN + paths ON paths.pageID = pages.pageID + WHERE + pages.pageID IN ( + SELECT + pageID + FROM + tagToPage + WHERE + tagID = ( + SELECT + tagID + FROM + tags + WHERE + description = ? + ) + ) + AND + status = '' + ORDER BY published DESC` + + sqlIndexPagesFromURLRoot = ` + SELECT pages.title, pages.published, paths.urlPath + FROM + pages + INNER JOIN paths ON paths.pageID = pages.pageID + WHERE + pages.pageID IN ( + SELECT + pageID + FROM + paths + WHERE + urlPath LIKE ? + ) + AND + status = '' + ORDER BY published DESC` + + sqlFeedItems = ` + SELECT + pages.title, pages.published, pages.content, pages.audioFile, paths.urlPath + FROM + pages + INNER JOIN + paths ON paths.pageID = pages.pageID + WHERE + pages.pageID IN ( + SELECT + pageID + FROM + paths + WHERE + urlPath LIKE ? + ) + AND + status = '' + ORDER BY + published DESC` + + sqlPathFromID = `SELECT * FROM paths WHERE pageID=?` + + sqlTagFromDescription = `SELECT * FROM tags WHERE description = ?` + + sqlTagsFromPageID = ` + SELECT + description + FROM + tags + WHERE + tagID IN ( + SELECT tagID FROM tagToPage WHERE pageID=? + ) + ORDER BY description ASC` + + sqlTagsWithPageCount = ` + SELECT + tags.description, COUNT(DISTINCT tagToPage.pageID) AS count + FROM + tags + INNER JOIN + tagToPage ON tags.tagID = tagToPage.tagID + INNER JOIN + pages ON tagToPage.pageID = pages.pageID + WHERE + pages.Status = '' + GROUP BY + tags.tagID + ORDER BY + tags.description ASC` + + sqlIsValidKey = ` + SELECT + keys.key + FROM + keys + INNER JOIN keyToPage on keys.keyID = keyToPage.keyID + INNER JOIN paths ON keyToPage.pageID = paths.pageID + WHERE + keys.key = ? + AND + paths.urlPath = ?` + + sqlKeysAndPaths = ` + SELECT + paths.urlPath, keys.key + FROM + keys + INNER JOIN keyToPage on keys.keyID = keyToPage.keyID + INNER JOIN paths ON keyToPage.pageID = paths.pageID + ORDER BY + paths.urlPath ASC` + + sqlSiteKey = ` + SELECT + key + FROM + keys + WHERE + keyID = 1 + AND + key = ?` +)
M example-config.yamlexample-config.yaml

@@ -1,6 +1,9 @@

# The listen address should generally be left as localhost and access via a reverse proxy listenAddr: localhost:2500 +# Location of SQLite database +databaseLocation: goldfinch.db + # startPage will redirect your index to whatever you set here startPage: /

@@ -36,6 +39,6 @@ feeds:

main: /blog # Folder locations -dataFolder: data -templatesFolder: templates -logsFolder: logs +dataDirectory: data +templatesDirectory: templates +logsDirectory: logs
A logger/logger.go

@@ -0,0 +1,23 @@

+package logger + +import ( + "io" + "log" + "os" + "path" +) + +var Log *log.Logger + +func Init(logDir string) { + // Logging + logFilePath := path.Join(logDir, "main.log") + logFile, err := os.OpenFile(logFilePath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644) + if err != nil { + log.Fatal(err) + } + + // Create multi-writer to output to both log file and console + mw := io.MultiWriter(os.Stdout, logFile) + Log = log.New(mw, "", log.Ldate|log.Ltime) +}
M main.gomain.go

@@ -2,16 +2,16 @@ package main

import ( "flag" - "io" "log" "net/http" "os" "os/signal" "path" "syscall" + + "goldfinch/db" + "goldfinch/logger" ) - -var logger *log.Logger func init() { // Flags

@@ -23,10 +23,10 @@ loadConfig(configFile)

// Create required folders if they doesn't exist requiredFolders := []string{ - config.FolderData, - config.FolderContent, - config.FolderStatic, - config.FolderLogs, + config.DirectoryData, + config.DirectoryContent, + config.DirectoryStatic, + config.DirectoryLogs, } for _, folder := range requiredFolders {

@@ -35,28 +35,20 @@

if os.IsNotExist(err) { err = os.MkdirAll(folder, 0750) if err != nil { - log.Fatalf("Failed to create folder at: %v", folder) + log.Fatalf("Failed to create folder at: %v\n%v", folder, err) } log.Printf("Created folder at %v", folder) } } - // Logging - logFilePath := path.Join(config.FolderLogs, "main.log") - logFile, err := os.OpenFile(logFilePath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644) - if err != nil { - log.Fatal(err) - } - - // Create multi-writer to output to both log file and console - mw := io.MultiWriter(os.Stdout, logFile) - logger = log.New(mw, "", log.Ldate|log.Ltime) + // Set up logging + logger.Init(config.DirectoryLogs) // Create database - err = buildDB() + err := db.OpenDB(config.DatabaseLocation) if err != nil { - logger.Fatal("Failed to open database") + logger.Log.Fatal("Failed to open database") } }

@@ -66,7 +58,7 @@ c := make(chan os.Signal)

signal.Notify(c, os.Interrupt, syscall.SIGTERM) go func() { <-c - logger.Println("Stopping server...") + logger.Log.Println("Stopping server...") os.Exit(1) }()

@@ -80,13 +72,13 @@ mux.HandleFunc("/feed/", feeds)

mux.HandleFunc("/rebuild", rebuild) // Handle static files - fileServer := http.FileServer(http.Dir(config.FolderStatic)) - templateAssets := http.FileServer(http.Dir(path.Join(config.FolderTemplates, config.Template, "assets"))) + fileServer := http.FileServer(http.Dir(config.DirectoryStatic)) + templateAssets := http.FileServer(http.Dir(path.Join(config.DirectoryTemplates, config.Template, "assets"))) mux.Handle("/static/", http.StripPrefix("/static", fileServer)) mux.Handle("/assets/", http.StripPrefix("/assets", templateAssets)) // Start the server - logger.Println("Starting server...") - logger.Fatal(http.ListenAndServe(config.ListenAddr, mux)) + logger.Log.Println("Starting server...") + logger.Log.Fatal(http.ListenAndServe(config.ListenAddr, mux)) }
M pages.gopages.go

@@ -12,6 +12,9 @@ "regexp"

"slices" "strings" + "goldfinch/db" + "goldfinch/logger" + "git.sr.ht/~bouncepaw/mycomarkup/v5" "git.sr.ht/~bouncepaw/mycomarkup/v5/mycocontext" "github.com/bouncepaw/mycorrhiza/mycoopts"

@@ -23,7 +26,7 @@

// Return a stripped path, removing filesystem prefixes and clean it up to be URL safe func getURLPath(originalPath string) string { // Get a string that's representitive of the expected URI - urlPath := strings.TrimPrefix(originalPath, config.FolderContent) + urlPath := strings.TrimPrefix(originalPath, config.DirectoryContent) urlPath = strings.TrimSuffix(urlPath, filepath.Ext(originalPath)) urlPath = strings.ToLower(urlPath)

@@ -41,7 +44,7 @@ }

// Extract YAML frontmatter from markdown file, save it to // a page struct then return the markdown from the file -func parseYAML(mdPath string, p *page) ([]byte, error) { +func parseYAML(mdPath string, p *db.Page) ([]byte, error) { // Get file contents mdFile, err := os.ReadFile(mdPath) if err != nil {

@@ -55,7 +58,7 @@

// Parse YAML err = yaml.Unmarshal(frontmatter, p) if err != nil { - logger.Printf("Failed to parse YAML from %s\n", mdPath) + logger.Log.Printf("Failed to parse YAML from %s\n", mdPath) } // Extract markdown from file

@@ -104,39 +107,39 @@ // Create site key

sKey := generateKey(config.SiteKey) // Get keys - keyPaths, err := keysAndPaths() + keyPaths, err := db.KeysAndPaths() if err != nil { - logger.Println(err) + logger.Log.Println(err) return } // Add site key to slice - keyPaths = append(keyPaths, keyPath{path: "siteKey", key: sKey}) + keyPaths = append(keyPaths, db.KeyPath{Path: "siteKey", Key: sKey}) // Add keys to write string for _, kp := range keyPaths { - toWrite = toWrite + fmt.Sprintf("%s: %s\n", kp.key, kp.path) + toWrite = toWrite + fmt.Sprintf("%s: %s\n", kp.Key, kp.Path) } // Write file err = os.WriteFile("keys.txt", []byte(toWrite), 0644) if err != nil { - logger.Println("Failed to write key file") + logger.Log.Println("Failed to write key file") } } // Create array of Page structs func buildPages() error { - indexes := []*pagePath{} + indexes := []*db.PagePath{} // Find all content files - err := filepath.Walk(config.FolderContent, func(originalPath string, info fs.FileInfo, err error) error { + err := filepath.Walk(config.DirectoryContent, func(originalPath string, info fs.FileInfo, err error) error { if err != nil { - logger.Println("Couldn't access path") + logger.Log.Println("Couldn't access path") return err } // Initialise page - pageHolder := new(page) + pageHolder := new(db.Page) // Prevent content folder itself being added to pages if originalPath == "data/content" {

@@ -148,7 +151,7 @@ urlPath := getURLPath(originalPath)

// Check if path is a directory, add it to the indexes then prevent it from being added to pages if info.IsDir() { - indexes = append(indexes, &pagePath{filepath: originalPath, urlPath: urlPath}) + indexes = append(indexes, &db.PagePath{Filepath: originalPath, UrlPath: urlPath}) return nil }

@@ -156,7 +159,7 @@ // Check if filetype is supported

fileExt := filepath.Ext(info.Name()) supportedFile := slices.Contains([]string{".md", ".myco"}, fileExt) if !supportedFile { - logger.Println(originalPath, "uses an unsupported file format") + logger.Log.Println(originalPath, "uses an unsupported file format") return nil }

@@ -168,7 +171,7 @@

// Add YAML to page and return content content, err := parseYAML(originalPath, pageHolder) if err != nil { - logger.Printf("Failed to parse YAML from file: %v\n", originalPath) + logger.Log.Printf("Failed to parse YAML from file: %v\n", originalPath) return nil }

@@ -183,7 +186,7 @@ switch fileExt {

case ".md": convertedMD, err := markdownToHTML(content) if err != nil { - logger.Printf("Failed to parse markdown!\n%v", err) + logger.Log.Printf("Failed to parse markdown!\n%v", err) } convertedHTML = string(convertedMD.Bytes())

@@ -197,34 +200,34 @@ // Add page content to the holder variable

pageHolder.Content = template.HTML(convertedHTML) // Add the page to database - pageID, err := insertPage(pageHolder) + pageID, err := db.InsertPage(pageHolder) if err != nil { - logger.Println("Failed to add page: ", originalPath) + logger.Log.Println("Failed to add page: ", originalPath) } // Add page filepath and URL path to the database - err = insertPath(&pagePath{ - pageID, - originalPath, - urlPath, + err = db.InsertPath(&db.PagePath{ + PageID: pageID, + Filepath: originalPath, + UrlPath: urlPath, }) if err != nil { - logger.Printf("Failed to add %v to paths", originalPath) + logger.Log.Printf("Failed to add %v to paths", originalPath) } // Add key if required if pageHolder.Status == "private" { // Add key key := generateKey(pageHolder.PrivateKey) - keyID, err := insertKey(key) + keyID, err := db.InsertKey(key) if err != nil { - logger.Printf("Failed to add key for: %s\n", urlPath) + logger.Log.Printf("Failed to add key for: %s\n", urlPath) } // Add key to page - err = insertKeyToPage(keyID, pageID) + err = db.InsertKeyToPage(keyID, pageID) if err != nil { - logger.Printf("Failed to add key to association for: %s\n", urlPath) + logger.Log.Printf("Failed to add key to association for: %s\n", urlPath) } }

@@ -235,51 +238,51 @@ return nil

} for _, t := range pageHolder.Tags { - tagID, err := insertTag(&tag{Description: t}) + tagID, err := db.InsertTag(&db.Tag{Description: t}) if err != nil { - logger.Printf("Unable to add tag: %v\n", t) + logger.Log.Printf("Unable to add tag: %v\n", t) } // Add tag to page association - err = insertTagToPage(pageID, tagID) + err = db.InsertTagToPage(pageID, tagID) if err != nil { - logger.Printf("Failed to associate page with tag in %v\n", originalPath) + logger.Log.Printf("Failed to associate page with tag in %v\n", originalPath) } } return nil }) if err != nil { - logger.Printf("Can't read file(s) in data folder.\n%v\n", err) + logger.Log.Printf("Can't read file(s) in data folder.\n%v\n", err) } // Create index pages for _, index := range indexes { // Make a title for the page - pageTitle := strings.TrimPrefix(index.filepath, filepath.Dir(index.filepath))[1:] + " index" + pageTitle := strings.TrimPrefix(index.Filepath, filepath.Dir(index.Filepath))[1:] + " index" // Insert page and get page ID - pageID, err := insertPage(&page{ + pageID, err := db.InsertPage(&db.Page{ Title: pageTitle, Status: "index", - AppendIndex: index.urlPath, + AppendIndex: index.UrlPath, }) if err != nil { - logger.Printf("Failed to add %v\n", index.filepath) + logger.Log.Printf("Failed to add %v\n", index.Filepath) } // Create path entry - err = insertPath(&pagePath{ - pageID: pageID, - filepath: index.filepath, - urlPath: index.urlPath, + err = db.InsertPath(&db.PagePath{ + PageID: pageID, + Filepath: index.Filepath, + UrlPath: index.UrlPath, }) if err != nil { - logger.Printf("Failed to add %v paths\n", err) + logger.Log.Printf("Failed to add %v paths\n", err) } } - logger.Println("Pages loaded") + logger.Log.Println("Pages loaded") return nil }
M routes.goroutes.go

@@ -7,17 +7,20 @@ "os"

"path" "strings" ttemplate "text/template" + + "goldfinch/db" + "goldfinch/logger" ) var templates *template.Template type templateData struct { Cfg Config - P page - Tags []tag - Indexes []indexPage - TagIndexes []tagPageCount - FeedItems []feedItem + P db.Page + Tags []db.Tag + Indexes []db.IndexPage + TagIndexes []db.TagPageCount + FeedItems []db.FeedItem } func init() {

@@ -54,14 +57,14 @@ return

} // Get a page or return a 404 error if the page doesn't exist - p, err := pageFromURLPath(r.URL.Path) + p, err := db.PageFromURLPath(r.URL.Path) if err != nil { http.NotFound(w, r) return } // If page is private, check if key is exists and is valid, error if not - if p.Status == "private" && !isValidKey(r.URL.Query().Get("k"), r.URL.Path) { + if p.Status == "private" && !db.IsValidKey(r.URL.Query().Get("k"), r.URL.Path) { http.NotFound(w, r) return }

@@ -75,9 +78,9 @@ P: p,

} // Retrieve tags - pageTags, err := tagsFromPageID(p.ID) + pageTags, err := db.TagsFromPageID(p.ID) if err != nil { - logger.Printf("Failed to retrieve tags for %v", r.URL.Path) + logger.Log.Printf("Failed to retrieve tags for %v", r.URL.Path) } // Add tags

@@ -85,10 +88,10 @@ tmplData.Tags = pageTags

// Build indexes if required if p.AppendIndex != "" { - indexes, err := indexPagesFromURLRoot(p.AppendIndex) + indexes, err := db.IndexPagesFromURLRoot(p.AppendIndex) if err != nil { // Redirect to start page if index fails - logger.Println("Failed to build index for: ", r.URL.Path) + logger.Log.Println("Failed to build index for: ", r.URL.Path) } tmplData.Indexes = indexes

@@ -98,7 +101,7 @@ // Determine template to use and build index if required

if p.Status == "index" { // Redirect to start page if index failed if tmplData.Indexes == nil { - logger.Println("Failed to build index for: ", r.URL.Path) + logger.Log.Println("Failed to build index for: ", r.URL.Path) http.Redirect(w, r, config.StartPage, 302) return }

@@ -125,17 +128,17 @@

// Check if tag to search exists then perform the lookup, otherwise return a of list of possible tags if r.URL.Query().Get("t") != "" { // Perform the search - searchResults, err := indexPagesFromTag(r.URL.Query().Get("t")) + searchResults, err := db.IndexPagesFromTag(r.URL.Query().Get("t")) if err != nil { - tmplData.P = page{Content: "Search failed"} + tmplData.P = db.Page{Content: "Search failed"} } // Add results to tmplData and return page tmplData.Indexes = searchResults } else { - tagCounts, err := tagsWithPageCount() + tagCounts, err := db.TagsWithPageCount() if err != nil { - tmplData.P = page{Content: "Tag lookup failed"} + tmplData.P = db.Page{Content: "Tag lookup failed"} } tmplData.TagIndexes = tagCounts

@@ -149,7 +152,7 @@ // Return feed based on URL path

func feeds(w http.ResponseWriter, r *http.Request) { xmlTemplate, err := ttemplate.ParseFiles(config.ActiveTemplate + string(os.PathSeparator) + "feed.xml") if err != nil { - logger.Fatal("Failed to parse XML") + logger.Log.Fatal("Failed to parse XML") } // Prepare page data

@@ -166,9 +169,9 @@ return

} // Add items to tmplData - items, err := getFeedItems(feed) + items, err := db.GetFeedItems(config.Feeds[feed]) if err != nil { - logger.Println("Failed to retrieve feed items: ", err) + logger.Log.Println("Failed to retrieve feed items: ", err) } tmplData.FeedItems = items

@@ -181,19 +184,19 @@ func rebuild(w http.ResponseWriter, r *http.Request) {

// Generate site key sKey := r.URL.Query().Get("k") // Check site key - isValid, err := checkSiteKey(sKey) + isValid, err := db.CheckSiteKey(sKey) if err != nil { - logger.Println("Invalid key used in database rebuild attempt") + logger.Log.Println("Invalid key used in database rebuild attempt") } // If key is valid, rebuild datbase if isValid { - err = buildDB() + err = db.OpenDB(config.DatabaseLocation) if err != nil { - logger.Fatal("Database rebuild failed") + logger.Log.Fatal("Database rebuild failed") } - logger.Println("Database rebuilt manually") + logger.Log.Println("Database rebuilt manually") } http.Redirect(w, r, config.StartPage, 302)