github.com/jamiefdhurst/journal@v0.9.2/internal/app/model/journal.go (about)

     1  package model
     2  
     3  import (
     4  	"database/sql"
     5  	"fmt"
     6  	"math"
     7  	"regexp"
     8  	"strconv"
     9  	"strings"
    10  	"time"
    11  
    12  	"github.com/jamiefdhurst/journal/internal/app"
    13  	"github.com/jamiefdhurst/journal/pkg/database"
    14  	"github.com/jamiefdhurst/journal/pkg/database/rows"
    15  )
    16  
    17  const journalTable = "journal"
    18  
    19  // Journal model
    20  type Journal struct {
    21  	ID      int    `json:"id"`
    22  	Slug    string `json:"slug"`
    23  	Title   string `json:"title"`
    24  	Date    string `json:"date"`
    25  	Content string `json:"content"`
    26  }
    27  
    28  // GetDate Get the friendly date for the Journal
    29  func (j Journal) GetDate() string {
    30  	re := regexp.MustCompile("\\d{4}\\-\\d{2}\\-\\d{2}")
    31  	date := re.FindString(j.Date)
    32  	timeObj, err := time.Parse("2006-01-02", date)
    33  	if err != nil {
    34  		return ""
    35  	}
    36  	return timeObj.Format("Monday January 2, 2006")
    37  }
    38  
    39  // GetEditableDate Get the date string for editing
    40  func (j Journal) GetEditableDate() string {
    41  	re := regexp.MustCompile("\\d{4}\\-\\d{2}\\-\\d{2}")
    42  	return re.FindString(j.Date)
    43  }
    44  
    45  // GetExcerpt returns a small extract of the entry
    46  func (j Journal) GetExcerpt() string {
    47  	strip := regexp.MustCompile("\b+")
    48  	text := strings.ReplaceAll(j.Content, "<p>", "")
    49  	text = strings.ReplaceAll(text, "</p>", " ")
    50  	text = strip.ReplaceAllString(text, " ")
    51  	words := strings.Split(text, " ")
    52  
    53  	if len(words) > 50 {
    54  		return strings.Join(words[:50], " ") + "..."
    55  	}
    56  	return strings.TrimSuffix(strings.Join(words, " "), " ")
    57  }
    58  
    59  // Journals Common database resource link for Journal actions
    60  type Journals struct {
    61  	Container *app.Container
    62  	Gs        GiphysExtractor
    63  }
    64  
    65  // CreateTable Create the actual table
    66  func (js *Journals) CreateTable() error {
    67  	_, err := js.Container.Db.Exec("CREATE TABLE IF NOT EXISTS `" + journalTable + "` (" +
    68  		"`id` INTEGER PRIMARY KEY AUTOINCREMENT, " +
    69  		"`slug` VARCHAR(255) NOT NULL, " +
    70  		"`title` VARCHAR(255) NOT NULL, " +
    71  		"`date` DATE NOT NULL, " +
    72  		"`content` TEXT NOT NULL" +
    73  		")")
    74  
    75  	return err
    76  }
    77  
    78  // EnsureUniqueSlug Make sure the current slug is unique
    79  func (js *Journals) EnsureUniqueSlug(slug string, addition int) string {
    80  	newSlug := slug
    81  	if addition > 0 {
    82  		newSlug = strings.Join([]string{slug, "-", strconv.Itoa(addition)}, "")
    83  	}
    84  	exists := js.FindBySlug(newSlug)
    85  	if exists.ID > 0 {
    86  		addition++
    87  		return js.EnsureUniqueSlug(slug, addition)
    88  	}
    89  
    90  	return newSlug
    91  }
    92  
    93  // FetchAll Get all journals
    94  func (js *Journals) FetchAll() []Journal {
    95  	rows, err := js.Container.Db.Query("SELECT * FROM `" + journalTable + "` ORDER BY `date` DESC")
    96  	if err != nil {
    97  		return []Journal{}
    98  	}
    99  
   100  	return js.loadFromRows(rows)
   101  }
   102  
   103  // FetchPaginated returns a set of paginated journal entries
   104  func (js *Journals) FetchPaginated(query database.PaginationQuery) ([]Journal, database.PaginationInformation) {
   105  	pagination := database.PaginationInformation{
   106  		Page:           query.Page,
   107  		ResultsPerPage: query.ResultsPerPage,
   108  	}
   109  
   110  	countResult, err := js.Container.Db.Query("SELECT COUNT(*) AS `total` FROM `" + journalTable + "`")
   111  	if err != nil {
   112  		return []Journal{}, pagination
   113  	}
   114  	countResult.Next()
   115  	countResult.Scan(&pagination.TotalResults)
   116  	countResult.Close()
   117  	pagination.TotalPages = int(math.Ceil(float64(pagination.TotalResults) / float64(query.ResultsPerPage)))
   118  
   119  	if query.Page > pagination.TotalPages {
   120  		return []Journal{}, pagination
   121  	}
   122  
   123  	rows, _ := js.Container.Db.Query(fmt.Sprintf("SELECT * FROM `"+journalTable+"` ORDER BY `date` DESC LIMIT %d OFFSET %d", query.ResultsPerPage, (query.Page-1)*query.ResultsPerPage))
   124  	return js.loadFromRows(rows), pagination
   125  }
   126  
   127  // FindBySlug Find a journal by slug
   128  func (js *Journals) FindBySlug(slug string) Journal {
   129  	return js.loadSingle(js.Container.Db.Query("SELECT * FROM `"+journalTable+"` WHERE `slug` = ? LIMIT 1", slug))
   130  }
   131  
   132  // FindNext returns the next entry after an ID
   133  func (js *Journals) FindNext(id int) Journal {
   134  	return js.loadSingle(js.Container.Db.Query("SELECT * FROM `"+journalTable+"` WHERE `id` > ? ORDER BY `id` LIMIT 1", strconv.Itoa(id)))
   135  }
   136  
   137  // FindNext returns the previous entry before an ID
   138  func (js *Journals) FindPrev(id int) Journal {
   139  	return js.loadSingle(js.Container.Db.Query("SELECT * FROM `"+journalTable+"` WHERE `id` < ? ORDER BY `id` DESC LIMIT 1", strconv.Itoa(id)))
   140  }
   141  
   142  // Save Save a journal entry, either inserting it or updating it in the database
   143  func (js *Journals) Save(j Journal) Journal {
   144  	var res sql.Result
   145  
   146  	// Convert content for saving
   147  	j.Content = js.Gs.ExtractContentsAndSearchAPI(j.Content)
   148  	if j.Slug == "" {
   149  		j.Slug = Slugify(j.Title)
   150  	}
   151  
   152  	if j.ID == 0 {
   153  		j.Slug = js.EnsureUniqueSlug(j.Slug, 0)
   154  		res, _ = js.Container.Db.Exec("INSERT INTO `"+journalTable+"` (`slug`, `title`, `date`, `content`) VALUES(?,?,?,?)", j.Slug, j.Title, j.Date, j.Content)
   155  	} else {
   156  		res, _ = js.Container.Db.Exec("UPDATE `"+journalTable+"` SET `slug` = ?, `title` = ?, `date` = ?, `content` = ? WHERE `id` = ?", j.Slug, j.Title, j.Date, j.Content, strconv.Itoa(j.ID))
   157  	}
   158  
   159  	// Store insert ID
   160  	if j.ID == 0 {
   161  		id, _ := res.LastInsertId()
   162  		j.ID = int(id)
   163  	}
   164  
   165  	return j
   166  }
   167  
   168  func (js Journals) loadFromRows(rows rows.Rows) []Journal {
   169  	defer rows.Close()
   170  	journals := []Journal{}
   171  	for rows.Next() {
   172  		j := Journal{}
   173  		rows.Scan(&j.ID, &j.Slug, &j.Title, &j.Date, &j.Content)
   174  		journals = append(journals, j)
   175  	}
   176  
   177  	return journals
   178  }
   179  
   180  func (js *Journals) loadSingle(rows rows.Rows, err error) Journal {
   181  	if err != nil {
   182  		return Journal{}
   183  	}
   184  	journals := js.loadFromRows(rows)
   185  
   186  	if len(journals) == 1 {
   187  		return journals[0]
   188  	}
   189  
   190  	return Journal{}
   191  }
   192  
   193  // Slugify Utility to convert a string into a slug
   194  func Slugify(s string) string {
   195  	re := regexp.MustCompile("[\\W+]")
   196  
   197  	return strings.ToLower(re.ReplaceAllString(s, "-"))
   198  }