BrainMinder/models/item.go
2024-08-22 10:13:16 +02:00

471 lines
12 KiB
Go

package models
import (
"bytes"
"database/sql"
"errors"
"strconv"
"strings"
"brainminder.speedtech.it/internal/database"
)
type Item struct {
Id int64 `db:"id"`
Title string `db:"title"`
Summary string `db:"summary"`
Summary_rendered string `db:"summary_rendered"`
Description string `db:"description"`
Description_rendered string `db:"description_rendered"`
Active int `db:"active"`
Tags string `db:"tags"`
Type_id int64 `db:"type_id"`
Categories string `db:"categories"`
Notebooks string `db:"notebooks"`
Crypted int `db:"crypted"`
Hidden int `db:"hidden"`
On_dashboard int `db:"on_dashboard"`
On_dashboard_position int `db:"on_dashboard_position"`
Type_title string `db:"type_title"`
Type_icon string `db:"type_icon"`
Type_show_summary int `db:"type_show_summary"`
Type_show_description int `db:"type_show_description"`
FieldsOnList []Field
FieldsValues []FieldValue
FieldsValuesMap map[int64]map[int]string
}
type ItemRelation struct {
Item_id int64 `db:"item_id"`
Related_item_id int64 `db:"related_item_id"`
Relation_type string `db:"relation_type"`
Title string `db:"title"`
Categories string `db:"categories"`
Tags string `db:"tags"`
Type_title string `db:"type_title"`
Type_icon string `db:"type_icon"`
}
type ItemModel struct {
*BaseModel
}
func (model *ItemModel) One(id int64) (*Item, bool, error) {
ctx, cancel := database.GetContext()
defer cancel()
var row Item
query := `SELECT bmi.*, bmt.title AS type_title, bmt.icon AS type_icon, bmt.show_summary AS type_show_summary,
bmt.show_description AS type_show_description FROM bm_item bmi
INNER JOIN bm_type bmt ON bmi.type_id=bmt.id WHERE bmi.id = $1`
err := model.DB.GetContext(ctx, &row, query, id)
if errors.Is(err, sql.ErrNoRows) {
return nil, false, nil
}
return &row, true, err
}
func (model *ItemModel) Delete(id int) (bool, error) {
ctx, cancel := database.GetContext()
defer cancel()
_, err := model.DB.ExecContext(ctx, `DELETE FROM bm_item WHERE id = $1`, id)
if errors.Is(err, sql.ErrNoRows) {
return false, nil
}
_, err = model.DB.ExecContext(ctx, `DELETE FROM bm_item_relations WHERE id = $1 OR related_item_id=$1`, id)
if errors.Is(err, sql.ErrNoRows) {
return false, nil
}
_, err = model.DB.ExecContext(ctx, `DELETE FROM bm_item_fields WHERE item_id = $1`, id)
if errors.Is(err, sql.ErrNoRows) {
return false, nil
}
_, err = model.DB.ExecContext(ctx, `DELETE FROM bm_item_keywords WHERE item_id = $1`, id)
if errors.Is(err, sql.ErrNoRows) {
return false, nil
}
return true, err
}
func (model *ItemModel) Search(searchText string, criteria map[string]any) ([]Item, bool, error) {
ctx, cancel := database.GetContext()
defer cancel()
var params []interface{}
var conditions []string
var cond string
var conditions_criteria []string
var cond_criteria string
query := `SELECT DISTINCT bmi.*, bmt.title AS type_title, bmt.icon AS type_icon FROM bm_item bmi
INNER JOIN bm_type bmt ON bmi.type_id=bmt.id
INNER JOIN bm_item_keywords bit ON bit.item_id=bmi.id `
for field, value := range criteria {
switch field {
case "notebook_id":
if value != nil {
valint := value.(int64)
if valint > 0 {
valstr := "|" + strconv.FormatInt(valint, 10) + "|"
params = append(params, valstr)
conditions_criteria = append(conditions_criteria, "INSTR(bmi.notebooks, ?) > 0 ")
}
}
}
}
if len(searchText) > 0 {
params = append(params, strings.ToLower(searchText))
conditions = append(conditions, "bit.keyword = ? ")
}
for _, s := range strings.Split(searchText, " ") {
s = strings.ToLower(s)
if len(s) > 0 {
params = append(params, s)
conditions = append(conditions, "bit.keyword = ? ")
}
}
for _, condition := range conditions {
if len(cond) > 0 {
cond = cond + " OR "
}
cond = cond + condition
}
for _, condition := range conditions_criteria {
if len(cond_criteria) > 0 {
cond_criteria = cond_criteria + " AND "
}
cond_criteria = cond_criteria + condition
}
len_cond := len(cond)
len_cond_criteria := len(cond_criteria)
if len_cond_criteria > 0 || len_cond > 0 {
query = query + "WHERE "
}
if len_cond_criteria > 0 {
query = query + cond_criteria
if len_cond > 0 {
query = query + " AND (" + cond + ")"
}
} else {
if len_cond > 0 {
query = query + " (" + cond + ")"
}
}
query = query + ` ORDER BY bmi.title`
var rows []Item
err := model.DB.SelectContext(ctx, &rows, query, params...)
if errors.Is(err, sql.ErrNoRows) {
return nil, false, nil
}
return rows, true, err
}
func (model *ItemModel) Find(criteria map[string]any, offset int64) ([]Item, bool, error) {
ctx, cancel := database.GetContext()
defer cancel()
var params []interface{}
var conditions []string
var cond string
query := `SELECT DISTINCT bmi.*, bmt.title AS type_title, bmt.icon AS type_icon FROM bm_item bmi
INNER JOIN bm_type bmt ON bmi.type_id=bmt.id `
for field, value := range criteria {
switch field {
case "Title":
valstr := value.(string)
if len(valstr) > 0 {
params = append(params, valstr)
conditions = append(conditions, "bmi.title LIKE '%' || ? || '%'")
}
case "Tags":
valstr := value.(string)
if len(valstr) > 0 {
params = append(params, valstr)
conditions = append(conditions, "bmi.tags LIKE '%' || ? || '%'")
}
case "type_id":
valint := value.(int64)
if valint > 0 {
params = append(params, valint)
conditions = append(conditions, "bmi.type_id=?")
}
case "notebook_id":
if value != nil {
valint := value.(int64)
if valint > 0 {
valstr := "|" + strconv.FormatInt(valint, 10) + "|"
params = append(params, valstr)
conditions = append(conditions, "INSTR(bmi.notebooks, ?) > 0")
}
}
case "category_id":
if value != nil {
valint := value.(int64)
if valint > 0 {
valstr := "|" + strconv.FormatInt(valint, 10) + "|"
params = append(params, valstr)
conditions = append(conditions, "INSTR(bmi.categories, ?) > 0")
}
}
case "On_dashboard":
valint := value.(int)
if valint > 0 {
params = append(params, valint)
conditions = append(conditions, "bmi.on_dashboard=?")
}
}
}
for _, condition := range conditions {
if len(cond) > 0 {
cond = cond + " AND "
}
cond = cond + condition
}
if len(cond) > 0 {
query = query + "WHERE " + cond + " "
}
query = query + ` ORDER BY bmi.title`
params = append(params, offset)
query = query + ` LIMIT 10 OFFSET ?`
var rows []Item
err := model.DB.SelectContext(ctx, &rows, query, params...)
if errors.Is(err, sql.ErrNoRows) {
return nil, false, nil
}
return rows, true, err
}
func (model *ItemModel) Create(Item *Item) (int64, error) {
ctx, cancel := database.GetContext()
defer cancel()
markdown := *model.GetMarkdown()
var bufSummary bytes.Buffer
markdown.Convert([]byte(Item.Summary), &bufSummary)
Item.Summary_rendered = bufSummary.String()
var bufDescription bytes.Buffer
markdown.Convert([]byte(Item.Description), &bufDescription)
Item.Description_rendered = bufDescription.String()
query := `INSERT INTO bm_item (type_id, title, summary, summary_rendered, description, description_rendered, on_dashboard, tags, notebooks, categories)
VALUES (:type_id, :title, :summary, :summary_rendered, :description, :description_rendered, :on_dashboard, :tags, :notebooks, :categories)`
result, err := model.DB.NamedExecContext(ctx, query, Item)
if err != nil {
return 0, err
}
id, err := result.LastInsertId()
if err != nil {
return 0, err
}
return id, err
}
func (model *ItemModel) Update(Item *Item) error {
ctx, cancel := database.GetContext()
defer cancel()
markdown := *model.GetMarkdown()
var bufSummary bytes.Buffer
markdown.Convert([]byte(Item.Summary), &bufSummary)
Item.Summary_rendered = bufSummary.String()
var bufDescription bytes.Buffer
markdown.Convert([]byte(Item.Description), &bufDescription)
Item.Description_rendered = bufDescription.String()
query := `UPDATE bm_item SET title=:title, type_id=:type_id, summary=:summary, summary_rendered=:summary_rendered,
description=:description, description_rendered=:description_rendered, tags=:tags, on_dashboard=:on_dashboard,
categories=:categories, notebooks =:notebooks WHERE id = :id`
_, err := model.DB.NamedExecContext(ctx, query, Item)
if err != nil {
return err
}
return err
}
func (model *ItemModel) AddToDashboard(id int64) error {
ctx, cancel := database.GetContext()
defer cancel()
query := `UPDATE bm_item SET on_dashboard=1 WHERE id = :id`
_, err := model.DB.ExecContext(ctx, query, id)
if err != nil {
return err
}
return err
}
func (model *ItemModel) RemoveFromDashboard(id int64) error {
ctx, cancel := database.GetContext()
defer cancel()
query := `UPDATE bm_item SET on_dashboard=0 WHERE id = :id`
_, err := model.DB.ExecContext(ctx, query, id)
if err != nil {
return err
}
return err
}
func (model *ItemModel) SaveKeywords(Item *Item, fields *[]Field, fieldsValues map[int64]map[int]string) error {
ctx, cancel := database.GetContext()
defer cancel()
var keywords []string
keywords = append(keywords, Item.Type_title)
var categories_int []int64
categories_str := strings.Split(strings.Trim(Item.Categories, "|"), "|")
for _, category_str := range categories_str {
category_int, _ := strconv.ParseInt(category_str, 10, 64)
categories_int = append(categories_int, category_int)
}
categoryModel := &CategoryModel{DB: model.DB}
categories, _, _ := categoryModel.Find(categories_int)
for _, category := range categories {
keywords = append(keywords, category.Name)
}
keywords = append(keywords, strings.Split(Item.Tags, ",")...)
keywords = append(keywords, strings.Split(Item.Title, " ")...)
for _, field := range *fields {
if field.Widget != "url" {
values, found := fieldsValues[field.Type_field_id]
if found {
for _, value := range values {
keywords = append(keywords, value)
}
}
}
}
query := `DELETE FROM bm_item_keywords WHERE item_id = $1`
_, err := model.DB.ExecContext(ctx, query, Item.Id)
if err != nil {
return err
}
stmt, err := model.DB.Prepare(`INSERT INTO bm_item_keywords (item_id, keyword) VALUES($1, $2)`)
for _, keyword := range keywords {
keyword = strings.ToLower(strings.TrimSpace(keyword))
if len(keyword) > 1 {
_, err = stmt.ExecContext(ctx, Item.Id, keyword)
}
if err != nil {
return err
}
}
return nil
}
func (model *ItemModel) AddRelation(id int64, related_id int64, relation_type string) error {
ctx, cancel := database.GetContext()
defer cancel()
query := `INSERT INTO bm_item_relations (item_id, related_item_id, relation_type) VALUES($1, $2, $3)`
_, err := model.DB.ExecContext(ctx, query, id, related_id, relation_type)
if err != nil {
return err
}
return nil
}
func (model *ItemModel) UpdateRelation(id int64, related_id int64, relation_type string) error {
ctx, cancel := database.GetContext()
defer cancel()
query := `UPDATE bm_item_relations SET relation_type=$1 WHERE item_id=$2 AND related_item_id=$3`
_, err := model.DB.ExecContext(ctx, query, relation_type, id, related_id)
if err != nil {
return err
}
return nil
}
func (model *ItemModel) DeleteRelation(id int64, related_id int64) error {
ctx, cancel := database.GetContext()
defer cancel()
query := `DELETE FROM bm_item_relations WHERE item_id=$1 AND related_item_id=$2`
_, err := model.DB.ExecContext(ctx, query, id, related_id)
if err != nil {
return err
}
return nil
}
func (model *ItemModel) GetRelations(id int64) ([]ItemRelation, bool, error) {
ctx, cancel := database.GetContext()
defer cancel()
query := `SELECT bir.item_id, bir.related_item_id, bmi.title, bmi.categories, bmi.tags, bmt.title AS type_title,
bmt.icon AS type_icon, bir.relation_type FROM bm_item bmi
INNER JOIN bm_type bmt ON bmi.type_id=bmt.id
INNER JOIN bm_item_relations bir ON bir.related_item_id=bmi.id WHERE bir.item_id=$1
UNION
SELECT bir.item_id, bir.related_item_id, bmi.title, bmi.categories, bmi.tags, bmt.title AS type_title,
bmt.icon AS type_icon, bir.relation_type FROM bm_item bmi
INNER JOIN bm_type bmt ON bmi.type_id=bmt.id
INNER JOIN bm_item_relations bir ON bir.item_id=bmi.id WHERE bir.related_item_id=$1
`
var rows []ItemRelation
err := model.DB.SelectContext(ctx, &rows, query, id)
if errors.Is(err, sql.ErrNoRows) {
return nil, false, nil
}
return rows, true, err
}