2023-09-28 22:09:52 +08:00
package mysql
import (
"context"
"fmt"
"strings"
"github.com/pkg/errors"
2024-05-13 20:24:11 +08:00
"google.golang.org/protobuf/encoding/protojson"
2023-09-28 22:09:52 +08:00
2024-05-13 20:24:11 +08:00
storepb "github.com/usememos/memos/proto/gen/store"
2023-09-28 22:09:52 +08:00
"github.com/usememos/memos/store"
)
2023-10-05 23:11:29 +08:00
func ( d * DB ) CreateMemo ( ctx context . Context , create * store . Memo ) ( * store . Memo , error ) {
2024-05-10 23:02:57 +08:00
fields := [ ] string { "`uid`" , "`creator_id`" , "`content`" , "`visibility`" , "`tags`" , "`payload`" }
placeholder := [ ] string { "?" , "?" , "?" , "?" , "?" , "?" }
2024-05-13 20:24:11 +08:00
payload := "{}"
if create . Payload != nil {
payloadBytes , err := protojson . Marshal ( create . Payload )
if err != nil {
return nil , err
}
payload = string ( payloadBytes )
}
2024-05-13 22:12:56 +08:00
args := [ ] any { create . UID , create . CreatorID , create . Content , create . Visibility , "[]" , payload }
2023-10-08 18:29:12 +08:00
2024-01-06 16:55:13 +08:00
stmt := "INSERT INTO `memo` (" + strings . Join ( fields , ", " ) + ") VALUES (" + strings . Join ( placeholder , ", " ) + ")"
2023-10-08 18:29:12 +08:00
result , err := d . db . ExecContext ( ctx , stmt , args ... )
2023-09-28 22:09:52 +08:00
if err != nil {
return nil , err
}
2023-09-29 09:15:54 +08:00
rawID , err := result . LastInsertId ( )
2023-09-28 22:09:52 +08:00
if err != nil {
return nil , err
}
2023-09-29 09:15:54 +08:00
id := int32 ( rawID )
memo , err := d . GetMemo ( ctx , & store . FindMemo { ID : & id } )
if err != nil {
2023-09-28 22:09:52 +08:00
return nil , err
}
2023-09-29 09:15:54 +08:00
if memo == nil {
return nil , errors . Errorf ( "failed to create memo" )
}
return memo , nil
2023-09-28 22:09:52 +08:00
}
2023-10-05 23:11:29 +08:00
func ( d * DB ) ListMemos ( ctx context . Context , find * store . FindMemo ) ( [ ] * store . Memo , error ) {
2024-01-06 13:22:02 +08:00
where , having , args := [ ] string { "1 = 1" } , [ ] string { "1 = 1" } , [ ] any { }
2023-09-28 22:09:52 +08:00
if v := find . ID ; v != nil {
2023-10-07 22:56:12 +08:00
where , args = append ( where , "`memo`.`id` = ?" ) , append ( args , * v )
2023-09-28 22:09:52 +08:00
}
2024-03-20 20:39:16 +08:00
if v := find . UID ; v != nil {
where , args = append ( where , "`memo`.`uid` = ?" ) , append ( args , * v )
2024-01-21 01:23:55 +08:00
}
2023-09-28 22:09:52 +08:00
if v := find . CreatorID ; v != nil {
2023-10-07 22:56:12 +08:00
where , args = append ( where , "`memo`.`creator_id` = ?" ) , append ( args , * v )
2023-09-28 22:09:52 +08:00
}
if v := find . RowStatus ; v != nil {
2023-10-07 22:56:12 +08:00
where , args = append ( where , "`memo`.`row_status` = ?" ) , append ( args , * v )
2023-09-28 22:09:52 +08:00
}
if v := find . CreatedTsBefore ; v != nil {
2023-10-07 22:56:12 +08:00
where , args = append ( where , "UNIX_TIMESTAMP(`memo`.`created_ts`) < ?" ) , append ( args , * v )
2023-09-28 22:09:52 +08:00
}
if v := find . CreatedTsAfter ; v != nil {
2023-10-07 22:56:12 +08:00
where , args = append ( where , "UNIX_TIMESTAMP(`memo`.`created_ts`) > ?" ) , append ( args , * v )
2023-09-28 22:09:52 +08:00
}
2024-01-18 14:30:20 +08:00
if v := find . UpdatedTsBefore ; v != nil {
where , args = append ( where , "UNIX_TIMESTAMP(`memo`.`updated_ts`) < ?" ) , append ( args , * v )
}
if v := find . UpdatedTsAfter ; v != nil {
where , args = append ( where , "UNIX_TIMESTAMP(`memo`.`updated_ts`) > ?" ) , append ( args , * v )
}
2023-09-28 22:09:52 +08:00
if v := find . ContentSearch ; len ( v ) != 0 {
for _ , s := range v {
2023-10-07 22:56:12 +08:00
where , args = append ( where , "`memo`.`content` LIKE ?" ) , append ( args , "%" + s + "%" )
2023-09-28 22:09:52 +08:00
}
}
if v := find . VisibilityList ; len ( v ) != 0 {
2023-12-18 20:47:29 +08:00
placeholder := [ ] string { }
2023-09-28 22:09:52 +08:00
for _ , visibility := range v {
2023-12-18 20:47:29 +08:00
placeholder = append ( placeholder , "?" )
args = append ( args , visibility . String ( ) )
2023-09-28 22:09:52 +08:00
}
2023-12-18 20:47:29 +08:00
where = append ( where , fmt . Sprintf ( "`memo`.`visibility` in (%s)" , strings . Join ( placeholder , "," ) ) )
2023-09-28 22:09:52 +08:00
}
2024-05-13 22:04:37 +08:00
if v := find . PayloadFind ; v != nil {
if v . Raw != nil {
where , args = append ( where , "`memo`.`payload` = ?" ) , append ( args , * v . Raw )
}
2024-07-26 08:40:40 +08:00
if len ( v . TagSearch ) != 0 {
for _ , tag := range v . TagSearch {
where , args = append ( where , "JSON_CONTAINS(JSON_EXTRACT(`memo`.`payload`, '$.property.tags'), ?)" ) , append ( args , fmt . Sprintf ( ` %%"%s"%% ` , tag ) )
}
2024-05-13 22:04:37 +08:00
}
2024-05-27 23:25:25 +08:00
if v . HasLink {
where = append ( where , "JSON_EXTRACT(`memo`.`payload`, '$.property.hasLink') IS TRUE" )
}
if v . HasTaskList {
where = append ( where , "JSON_EXTRACT(`memo`.`payload`, '$.property.hasTaskList') IS TRUE" )
}
if v . HasCode {
where = append ( where , "JSON_EXTRACT(`memo`.`payload`, '$.property.hasCode') IS TRUE" )
}
2024-06-05 08:39:56 +08:00
if v . HasIncompleteTasks {
where = append ( where , "JSON_EXTRACT(`memo`.`payload`, '$.property.hasIncompleteTasks') IS TRUE" )
}
2024-05-08 20:03:01 +08:00
}
2024-01-06 09:48:11 +08:00
if find . ExcludeComments {
2024-01-06 13:22:02 +08:00
having = append ( having , "`parent_id` IS NULL" )
2024-01-06 09:48:11 +08:00
}
2023-11-19 09:42:59 +08:00
orders := [ ] string { }
if find . OrderByPinned {
orders = append ( orders , "`pinned` DESC" )
}
2024-07-31 23:34:00 +08:00
order := "DESC"
if find . OrderByTimeAsc {
order = "ASC"
}
2023-09-28 22:09:52 +08:00
if find . OrderByUpdatedTs {
2024-07-31 23:34:00 +08:00
orders = append ( orders , "`updated_ts` " + order )
2023-09-28 22:09:52 +08:00
} else {
2024-07-31 23:34:00 +08:00
orders = append ( orders , "`created_ts` " + order )
2023-09-28 22:09:52 +08:00
}
2024-07-31 23:34:00 +08:00
orders = append ( orders , "`id` " + order )
2024-03-30 13:50:18 +08:00
if find . Random {
orders = append ( orders , "RAND()" )
}
2023-09-28 22:09:52 +08:00
2023-12-06 22:44:49 +08:00
fields := [ ] string {
"`memo`.`id` AS `id`" ,
2024-03-20 20:39:16 +08:00
"`memo`.`uid` AS `uid`" ,
2023-12-06 22:44:49 +08:00
"`memo`.`creator_id` AS `creator_id`" ,
"UNIX_TIMESTAMP(`memo`.`created_ts`) AS `created_ts`" ,
"UNIX_TIMESTAMP(`memo`.`updated_ts`) AS `updated_ts`" ,
"`memo`.`row_status` AS `row_status`" ,
"`memo`.`visibility` AS `visibility`" ,
2024-05-13 20:24:11 +08:00
"`memo`.`payload` AS `payload`" ,
2024-01-14 20:51:52 +08:00
"IFNULL(`memo_organizer`.`pinned`, 0) AS `pinned`" ,
2024-01-06 13:22:02 +08:00
"`memo_relation`.`related_memo_id` AS `parent_id`" ,
2023-12-06 22:44:49 +08:00
}
2024-01-06 16:55:13 +08:00
if ! find . ExcludeContent {
fields = append ( fields , "`memo`.`content` AS `content`" )
}
query := "SELECT " + strings . Join ( fields , ", " ) + " FROM `memo` LEFT JOIN `memo_organizer` ON `memo`.`id` = `memo_organizer`.`memo_id` AND `memo`.`creator_id` = `memo_organizer`.`user_id` LEFT JOIN `memo_relation` ON `memo`.`id` = `memo_relation`.`memo_id` AND `memo_relation`.`type` = \"COMMENT\" WHERE " + strings . Join ( where , " AND " ) + " HAVING " + strings . Join ( having , " AND " ) + " ORDER BY " + strings . Join ( orders , ", " )
2023-09-28 22:09:52 +08:00
if find . Limit != nil {
query = fmt . Sprintf ( "%s LIMIT %d" , query , * find . Limit )
if find . Offset != nil {
query = fmt . Sprintf ( "%s OFFSET %d" , query , * find . Offset )
}
}
rows , err := d . db . QueryContext ( ctx , query , args ... )
if err != nil {
return nil , err
}
defer rows . Close ( )
list := make ( [ ] * store . Memo , 0 )
for rows . Next ( ) {
var memo store . Memo
2024-05-13 22:12:56 +08:00
var payloadBytes [ ] byte
2024-01-06 16:55:13 +08:00
dests := [ ] any {
2023-09-28 22:09:52 +08:00
& memo . ID ,
2024-03-20 20:39:16 +08:00
& memo . UID ,
2023-09-28 22:09:52 +08:00
& memo . CreatorID ,
& memo . CreatedTs ,
& memo . UpdatedTs ,
& memo . RowStatus ,
& memo . Visibility ,
2024-05-13 20:24:11 +08:00
& payloadBytes ,
2024-01-14 20:51:52 +08:00
& memo . Pinned ,
2024-01-06 09:48:11 +08:00
& memo . ParentID ,
2024-01-06 16:55:13 +08:00
}
if ! find . ExcludeContent {
dests = append ( dests , & memo . Content )
}
if err := rows . Scan ( dests ... ) ; err != nil {
2023-09-28 22:09:52 +08:00
return nil , err
}
2024-05-13 20:24:11 +08:00
payload := & storepb . MemoPayload { }
if err := protojsonUnmarshaler . Unmarshal ( payloadBytes , payload ) ; err != nil {
return nil , errors . Wrap ( err , "failed to unmarshal payload" )
}
memo . Payload = payload
2023-09-28 22:09:52 +08:00
list = append ( list , & memo )
}
if err := rows . Err ( ) ; err != nil {
return nil , err
}
return list , nil
}
2023-10-05 23:11:29 +08:00
func ( d * DB ) GetMemo ( ctx context . Context , find * store . FindMemo ) ( * store . Memo , error ) {
2023-09-29 09:15:54 +08:00
list , err := d . ListMemos ( ctx , find )
if err != nil {
return nil , err
}
if len ( list ) == 0 {
return nil , nil
}
memo := list [ 0 ]
return memo , nil
}
2023-10-05 23:11:29 +08:00
func ( d * DB ) UpdateMemo ( ctx context . Context , update * store . UpdateMemo ) error {
2023-09-28 22:09:52 +08:00
set , args := [ ] string { } , [ ] any { }
2024-03-20 20:39:16 +08:00
if v := update . UID ; v != nil {
set , args = append ( set , "`uid` = ?" ) , append ( args , * v )
2024-01-21 01:23:55 +08:00
}
2023-09-28 22:09:52 +08:00
if v := update . CreatedTs ; v != nil {
2023-10-07 22:56:12 +08:00
set , args = append ( set , "`created_ts` = FROM_UNIXTIME(?)" ) , append ( args , * v )
2023-09-28 22:09:52 +08:00
}
if v := update . UpdatedTs ; v != nil {
2023-10-07 22:56:12 +08:00
set , args = append ( set , "`updated_ts` = FROM_UNIXTIME(?)" ) , append ( args , * v )
2023-09-28 22:09:52 +08:00
}
if v := update . RowStatus ; v != nil {
2023-10-07 22:56:12 +08:00
set , args = append ( set , "`row_status` = ?" ) , append ( args , * v )
2023-09-28 22:09:52 +08:00
}
if v := update . Content ; v != nil {
2023-10-07 22:56:12 +08:00
set , args = append ( set , "`content` = ?" ) , append ( args , * v )
2023-09-28 22:09:52 +08:00
}
if v := update . Visibility ; v != nil {
2023-10-07 22:56:12 +08:00
set , args = append ( set , "`visibility` = ?" ) , append ( args , * v )
2023-09-28 22:09:52 +08:00
}
2024-05-13 20:24:11 +08:00
if v := update . Payload ; v != nil {
payloadBytes , err := protojson . Marshal ( v )
if err != nil {
return err
}
set , args = append ( set , "`payload` = ?" ) , append ( args , string ( payloadBytes ) )
}
2023-09-28 22:09:52 +08:00
args = append ( args , update . ID )
2023-10-07 22:56:12 +08:00
stmt := "UPDATE `memo` SET " + strings . Join ( set , ", " ) + " WHERE `id` = ?"
2023-09-28 22:09:52 +08:00
if _ , err := d . db . ExecContext ( ctx , stmt , args ... ) ; err != nil {
return err
}
return nil
}
2023-10-05 23:11:29 +08:00
func ( d * DB ) DeleteMemo ( ctx context . Context , delete * store . DeleteMemo ) error {
2023-10-07 22:56:12 +08:00
where , args := [ ] string { "`id` = ?" } , [ ] any { delete . ID }
stmt := "DELETE FROM `memo` WHERE " + strings . Join ( where , " AND " )
2023-09-28 22:09:52 +08:00
result , err := d . db . ExecContext ( ctx , stmt , args ... )
if err != nil {
return err
}
if _ , err := result . RowsAffected ( ) ; err != nil {
return err
}
return nil
}