storage.go 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. package helpers
  2. import (
  3. "database/sql"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "os"
  8. "strings"
  9. "time"
  10. "git.aetherial.dev/aeth/keiji/pkg/env"
  11. "github.com/google/uuid"
  12. "github.com/mattn/go-sqlite3"
  13. "github.com/redis/go-redis/v9"
  14. )
  15. type DatabaseSchema struct {
  16. // Gotta figure out what this looks like
  17. // so that the ExtractAll() function gets
  18. // all of the data from the database
  19. }
  20. type MenuLinkPair struct {
  21. MenuLink string `json:"menu_link"`
  22. LinkText string `json:"link_text"`
  23. }
  24. type NavBarItem struct {
  25. Png string `json:"png"`
  26. Link string `json:"link"`
  27. }
  28. type Document struct {
  29. PostId string
  30. Title string `json:"title"`
  31. Created string `json:"created"`
  32. Body string `json:"body"`
  33. Category string `json:"category"`
  34. Sample string
  35. }
  36. /*
  37. Truncates a text post into a 256 character long 'sample' for displaying posts
  38. */
  39. func (d *Document) MakeSample() string {
  40. t := strings.Split(d.Body, "")
  41. var sample []string
  42. if len(d.Body) < 256 {
  43. return d.Body
  44. }
  45. for i := 0; i < 256; i++ {
  46. sample = append(sample, t[i])
  47. }
  48. sample = append(sample, " ...")
  49. return strings.Join(sample, "")
  50. }
  51. type Image struct {
  52. Location string
  53. Title string
  54. Desc string
  55. }
  56. type DocumentIO interface {
  57. GetDocument(id string) (Document, error)
  58. GetImage(id string) (Image, error)
  59. UpdateDocument(doc Document) error
  60. DeleteDocument(id string) error
  61. AddDocument(doc Document) error
  62. AddImage(img ImageStoreItem) error
  63. GetByCategory(category string) []string
  64. AllDocuments() []Document
  65. GetDropdownElements() []MenuLinkPair
  66. GetNavBarLinks() []NavBarItem
  67. ExportAll()
  68. }
  69. var (
  70. ErrDuplicate = errors.New("record already exists")
  71. ErrNotExists = errors.New("row not exists")
  72. ErrUpdateFailed = errors.New("update failed")
  73. ErrDeleteFailed = errors.New("delete failed")
  74. )
  75. type SQLiteRepo struct {
  76. db *sql.DB
  77. }
  78. // Instantiate a new SQLiteRepo struct
  79. func NewSQLiteRepo(db *sql.DB) *SQLiteRepo {
  80. return &SQLiteRepo{
  81. db: db,
  82. }
  83. }
  84. // Creates a new SQL table for text posts
  85. func (r *SQLiteRepo) Migrate() error {
  86. query := `
  87. CREATE TABLE IF NOT EXISTS posts(
  88. id INTEGER PRIMARY KEY AUTOINCREMENT,
  89. postid TEXT NOT NULL,
  90. title TEXT NOT NULL,
  91. created TEXT NOT NULL,
  92. body TEXT NOT NULL UNIQUE,
  93. category TEXT NOT NULL,
  94. sample TEXT NOT NULL
  95. );
  96. `
  97. _, err := r.db.Exec(query)
  98. return err
  99. }
  100. /*
  101. Create an entry in the hosts table
  102. :param host: a Host entry from a port scan
  103. */
  104. func (r *SQLiteRepo) Create(post Document) error {
  105. _, err := r.db.Exec("INSERT INTO posts(postid, title, created, body, category, sample) values(?,?,?,?,?)", uuid.New().String(), post.Title, post.Created, post.Body, post.Category, post.MakeSample())
  106. if err != nil {
  107. var sqliteErr sqlite3.Error
  108. if errors.As(err, &sqliteErr) {
  109. if errors.Is(sqliteErr.ExtendedCode, sqlite3.ErrConstraintUnique) {
  110. return ErrDuplicate
  111. }
  112. }
  113. return err
  114. }
  115. return nil
  116. }
  117. // Get all Hosts from the host table
  118. func (r *SQLiteRepo) AllDocuments() []Document {
  119. rows, err := r.db.Query("SELECT * FROM posts")
  120. if err != nil {
  121. fmt.Printf("There was an issue getting all posts. %s", err.Error())
  122. return nil
  123. }
  124. defer rows.Close()
  125. var all []Document
  126. for rows.Next() {
  127. var post Document
  128. if err := rows.Scan(&post.PostId, &post.Title, &post.Created, &post.Body, &post.Sample); err != nil {
  129. fmt.Printf("There was an error getting all documents. %s", err.Error())
  130. return nil
  131. }
  132. all = append(all, post)
  133. }
  134. return all
  135. }
  136. // Get a blogpost by its postid
  137. func (r *SQLiteRepo) GetByIP(postId string) (Document, error) {
  138. row := r.db.QueryRow("SELECT * FROM posts WHERE postid = ?", postId)
  139. var post Document
  140. if err := row.Scan(&post); err != nil {
  141. if errors.Is(err, sql.ErrNoRows) {
  142. return post, ErrNotExists
  143. }
  144. return post, err
  145. }
  146. return post, nil
  147. }
  148. // Update a record by its ID
  149. func (r *SQLiteRepo) Update(id int64, updated Document) error {
  150. if id == 0 {
  151. return errors.New("invalid updated ID")
  152. }
  153. res, err := r.db.Exec("UPDATE posts SET title = ?, body = ?, desc = ? WHERE id = ?", updated.Title, updated.Body, updated.MakeSample(), id)
  154. if err != nil {
  155. return err
  156. }
  157. rowsAffected, err := res.RowsAffected()
  158. if err != nil {
  159. return err
  160. }
  161. if rowsAffected == 0 {
  162. return ErrUpdateFailed
  163. }
  164. return nil
  165. }
  166. // Delete a record by its ID
  167. func (r *SQLiteRepo) Delete(id int64) error {
  168. res, err := r.db.Exec("DELETE FROM posts WHERE id = ?", id)
  169. if err != nil {
  170. return err
  171. }
  172. rowsAffected, err := res.RowsAffected()
  173. if err != nil {
  174. return err
  175. }
  176. if rowsAffected == 0 {
  177. return ErrDeleteFailed
  178. }
  179. return err
  180. }
  181. type InvalidSkipArg struct{ Skip int }
  182. func (i *InvalidSkipArg) Error() string {
  183. return fmt.Sprintf("Invalid skip amount was passed: %v", i.Skip)
  184. }
  185. type ImageStoreItem struct {
  186. Identifier string `json:"identifier"`
  187. Filename string `json:"filename"`
  188. AbsolutePath string `json:"absolute_path"`
  189. Title string `json:"title" form:"title"`
  190. Created string `json:"created"`
  191. Desc string `json:"description" form:"description"`
  192. Category string `json:"category"`
  193. ApiPath string
  194. }
  195. /*
  196. Create a new ImageStoreItem
  197. :param fname: the name of the file to be saved
  198. :param title: the canonical title to give the image
  199. :param desc: the description to associate to the image
  200. */
  201. func NewImageStoreItem(fname string, title string, desc string) *ImageStoreItem {
  202. id := uuid.New()
  203. img := ImageStoreItem{
  204. Identifier: id.String(),
  205. Filename: fname,
  206. Title: title,
  207. Category: DIGITAL_ART,
  208. AbsolutePath: fmt.Sprintf("%s/%s", GetImageStore(), fname),
  209. Created: time.Now().UTC().String(),
  210. Desc: desc,
  211. }
  212. return &img
  213. }
  214. /*
  215. Function to return the location of the image store. Wrapping the env call in
  216. a function so that refactoring is easier
  217. */
  218. func GetImageStore() string {
  219. return os.Getenv(env.IMAGE_STORE)
  220. }
  221. /*
  222. Return database entries of the images that exist in the imagestore
  223. :param rds: pointer to a RedisCaller to perform the lookups with
  224. */
  225. func GetImageData(rds *RedisCaller) ([]*ImageStoreItem, error) {
  226. ids, err := rds.GetByCategory(DIGITAL_ART)
  227. if err != nil {
  228. return nil, err
  229. }
  230. var imageEntries []*ImageStoreItem
  231. for i := range ids {
  232. val, err := rds.Client.Get(rds.ctx, ids[i]).Result()
  233. if err == redis.Nil {
  234. return nil, err
  235. } else if err != nil {
  236. return nil, err
  237. }
  238. data := []byte(val)
  239. var imageEntry ImageStoreItem
  240. err = json.Unmarshal(data, &imageEntry)
  241. if err != nil {
  242. return nil, err
  243. }
  244. imageEntry.ApiPath = fmt.Sprintf("/api/v1/images/%s", imageEntry.Filename)
  245. imageEntries = append(imageEntries, &imageEntry)
  246. }
  247. return imageEntries, err
  248. }