mirror of
https://github.com/usememos/memos.git
synced 2025-01-31 09:37:51 +08:00
feat: tag table (#811)
* feat: tag table * chore: update * chore: update
This commit is contained in:
parent
ab07c91d42
commit
e4a8a4d708
4 changed files with 203 additions and 0 deletions
20
api/tag.go
Normal file
20
api/tag.go
Normal file
|
@ -0,0 +1,20 @@
|
|||
package api
|
||||
|
||||
type Tag struct {
|
||||
Name string
|
||||
CreatorID int
|
||||
}
|
||||
|
||||
type TagUpsert struct {
|
||||
Name string
|
||||
CreatorID int
|
||||
}
|
||||
|
||||
type TagFind struct {
|
||||
CreatorID int
|
||||
}
|
||||
|
||||
type TagDelete struct {
|
||||
Name string
|
||||
CreatorID int
|
||||
}
|
|
@ -86,3 +86,10 @@ CREATE TABLE memo_resource (
|
|||
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
UNIQUE(memo_id, resource_id)
|
||||
);
|
||||
|
||||
-- tag
|
||||
CREATE TABLE tag (
|
||||
name TEXT NOT NULL,
|
||||
creator_id INTEGER NOT NULL,
|
||||
UNIQUE(name, creator_id)
|
||||
);
|
||||
|
|
|
@ -67,6 +67,9 @@ func vacuum(ctx context.Context, tx *sql.Tx) error {
|
|||
return err
|
||||
}
|
||||
if err := vacuumMemoResource(ctx, tx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := vacuumTag(ctx, tx); err != nil {
|
||||
// Prevent revive warning.
|
||||
return err
|
||||
}
|
||||
|
|
173
store/tag.go
Normal file
173
store/tag.go
Normal file
|
@ -0,0 +1,173 @@
|
|||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/usememos/memos/api"
|
||||
"github.com/usememos/memos/common"
|
||||
)
|
||||
|
||||
type tagRaw struct {
|
||||
Name string
|
||||
CreatorID int
|
||||
}
|
||||
|
||||
func (raw *tagRaw) toTag() *api.Tag {
|
||||
return &api.Tag{
|
||||
Name: raw.Name,
|
||||
CreatorID: raw.CreatorID,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Store) UpsertTag(ctx context.Context, upsert *api.TagUpsert) (*api.Tag, error) {
|
||||
tx, err := s.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, FormatError(err)
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
tagRaw, err := upsertTag(ctx, tx, upsert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tag := tagRaw.toTag()
|
||||
|
||||
return tag, nil
|
||||
}
|
||||
|
||||
func (s *Store) FindTagList(ctx context.Context, find *api.TagFind) ([]*api.Tag, error) {
|
||||
tx, err := s.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, FormatError(err)
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
tagRawList, err := findTagList(ctx, tx, find)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
list := []*api.Tag{}
|
||||
for _, raw := range tagRawList {
|
||||
list = append(list, raw.toTag())
|
||||
}
|
||||
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func (s *Store) DeleteTag(ctx context.Context, delete *api.TagDelete) error {
|
||||
tx, err := s.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return FormatError(err)
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
if err := deleteTag(ctx, tx, delete); err != nil {
|
||||
return FormatError(err)
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return FormatError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func upsertTag(ctx context.Context, tx *sql.Tx, upsert *api.TagUpsert) (*tagRaw, error) {
|
||||
query := `
|
||||
INSERT INTO tag (
|
||||
name, creator_id
|
||||
)
|
||||
VALUES (?, ?)
|
||||
ON CONFLICT(name, creator_id) DO NOTHING
|
||||
RETURNING name, creator_id
|
||||
`
|
||||
var tagRaw tagRaw
|
||||
if err := tx.QueryRowContext(ctx, query, upsert.Name, upsert.CreatorID).Scan(
|
||||
&tagRaw.Name,
|
||||
&tagRaw.CreatorID,
|
||||
); err != nil {
|
||||
return nil, FormatError(err)
|
||||
}
|
||||
|
||||
return &tagRaw, nil
|
||||
}
|
||||
|
||||
func findTagList(ctx context.Context, tx *sql.Tx, find *api.TagFind) ([]*tagRaw, error) {
|
||||
where, args := []string{"creator_id = ?"}, []interface{}{find.CreatorID}
|
||||
|
||||
query := `
|
||||
SELECT
|
||||
name,
|
||||
creator_id
|
||||
FROM tag
|
||||
WHERE ` + strings.Join(where, " AND ")
|
||||
rows, err := tx.QueryContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, FormatError(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
tagRawList := make([]*tagRaw, 0)
|
||||
for rows.Next() {
|
||||
var tagRaw tagRaw
|
||||
if err := rows.Scan(
|
||||
&tagRaw.Name,
|
||||
&tagRaw.CreatorID,
|
||||
); err != nil {
|
||||
return nil, FormatError(err)
|
||||
}
|
||||
|
||||
tagRawList = append(tagRawList, &tagRaw)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, FormatError(err)
|
||||
}
|
||||
|
||||
return tagRawList, nil
|
||||
}
|
||||
|
||||
func deleteTag(ctx context.Context, tx *sql.Tx, delete *api.TagDelete) error {
|
||||
where, args := []string{"name = ?", "creator_id = ?"}, []interface{}{delete.Name, delete.CreatorID}
|
||||
|
||||
stmt := `DELETE FROM tag WHERE ` + strings.Join(where, " AND ")
|
||||
result, err := tx.ExecContext(ctx, stmt, args...)
|
||||
if err != nil {
|
||||
return FormatError(err)
|
||||
}
|
||||
|
||||
rows, _ := result.RowsAffected()
|
||||
if rows == 0 {
|
||||
return &common.Error{Code: common.NotFound, Err: fmt.Errorf("tag not found")}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func vacuumTag(ctx context.Context, tx *sql.Tx) error {
|
||||
stmt := `
|
||||
DELETE FROM
|
||||
tag
|
||||
WHERE
|
||||
creator_id NOT IN (
|
||||
SELECT
|
||||
id
|
||||
FROM
|
||||
user
|
||||
)`
|
||||
_, err := tx.ExecContext(ctx, stmt)
|
||||
if err != nil {
|
||||
return FormatError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
Loading…
Reference in a new issue