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 }