Skip to content

Commit 09ab69a

Browse files
committed
remove vacuum
1 parent 2d5f95a commit 09ab69a

File tree

9 files changed

+462
-246
lines changed

9 files changed

+462
-246
lines changed

aiplan.go/cmd/aiplan/main.go

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -190,12 +190,6 @@ func main() {
190190
os.Exit(1)
191191
}
192192
slog.Info("UUID migration completed successfully")
193-
194-
// VACUUM FULL для освобождения места на диске после миграции
195-
if err := dao.VacuumFull(db); err != nil {
196-
slog.Error("VACUUM FULL failed", "err", err)
197-
os.Exit(1)
198-
}
199193
}
200194

201195
if err := CreateTriggers(db); err != nil {
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
package edtypes
2+
3+
import (
4+
"bytes"
5+
"encoding/hex"
6+
"errors"
7+
"fmt"
8+
"image/color"
9+
"io"
10+
"net/url"
11+
"regexp"
12+
"strconv"
13+
"strings"
14+
)
15+
16+
type TextAlign int
17+
18+
const (
19+
LeftAlign TextAlign = iota
20+
CenterAlign
21+
RightAlign
22+
)
23+
24+
var (
25+
colorReg = regexp.MustCompile(`[rgb()#\s"]`)
26+
)
27+
28+
// TipTapParser - функция для парсинга TipTap JSON, устанавливается из tiptap пакета
29+
var TipTapParser func(io.Reader) (*Document, error)
30+
31+
type Document struct {
32+
Elements []any
33+
}
34+
35+
// UnmarshalJSON реализует кастомную десериализацию TipTap JSON в Document.
36+
// Автоматически вызывает зарегистрированный TipTapParser.
37+
func (d *Document) UnmarshalJSON(data []byte) error {
38+
if TipTapParser == nil {
39+
return errors.New("TipTapParser not registered, import tiptap package to enable TipTap JSON parsing")
40+
}
41+
42+
// Вызываем парсер TipTap для получения документа
43+
doc, err := TipTapParser(bytes.NewReader(data))
44+
if err != nil {
45+
return err
46+
}
47+
48+
d.Elements = doc.Elements
49+
return nil
50+
}
51+
52+
type Paragraph struct {
53+
Content []any
54+
Indent int // для атрибута indent (0 = нет отступа)
55+
Align TextAlign // для атрибута textAlign
56+
}
57+
58+
type Text struct {
59+
Content string
60+
Size int
61+
62+
Strong bool
63+
Italic bool
64+
Underlined bool
65+
Strikethrough bool
66+
Sup bool
67+
Sub bool
68+
69+
Color *Color
70+
BgColor *Color
71+
Align TextAlign
72+
73+
URL *url.URL
74+
}
75+
76+
type ListElement struct {
77+
Content []Paragraph
78+
Checked bool
79+
}
80+
81+
type List struct {
82+
Elements []ListElement
83+
Numbered bool
84+
TaskList bool
85+
}
86+
87+
type Quote struct {
88+
Content []Paragraph
89+
}
90+
91+
type Code struct {
92+
Content string
93+
}
94+
95+
type Image struct {
96+
Src *url.URL
97+
Width int
98+
Align TextAlign
99+
}
100+
101+
type Table struct {
102+
MinWidth int
103+
ColWidth []int
104+
Rows [][]TableCell
105+
}
106+
107+
type TableCell struct {
108+
Content []Paragraph
109+
ColSpan int
110+
RowSpan int
111+
Header bool
112+
}
113+
114+
type Spoiler struct {
115+
Title string
116+
Collapsed bool
117+
BgColor Color
118+
Color Color
119+
120+
Content []Paragraph
121+
}
122+
123+
type InfoBlock struct {
124+
Title string
125+
Color Color
126+
127+
Content []Paragraph
128+
}
129+
130+
type Color color.RGBA
131+
132+
func ParseColor(raw string) (Color, error) {
133+
isDecRGB := strings.Contains(raw, "rgb(")
134+
isHex := raw[0] == '#' || raw[1] == '#'
135+
raw = colorReg.ReplaceAllString(raw, "")
136+
if isDecRGB {
137+
c := Color{}
138+
for i, n := range strings.Split(raw, ",") {
139+
nn, err := strconv.ParseUint(n, 10, 8)
140+
if err != nil {
141+
return c, err
142+
}
143+
144+
switch i {
145+
case 0:
146+
c.R = uint8(nn)
147+
case 1:
148+
c.G = uint8(nn)
149+
case 2:
150+
c.B = uint8(nn)
151+
case 3:
152+
c.A = uint8(nn)
153+
}
154+
}
155+
return c, nil
156+
} else if isHex {
157+
// HEX
158+
b, err := hex.DecodeString(raw)
159+
if err != nil {
160+
return Color{}, err
161+
}
162+
if len(b) < 3 {
163+
return Color{}, errors.New("unsupported color format")
164+
}
165+
c := Color{
166+
R: b[0],
167+
G: b[1],
168+
B: b[2],
169+
}
170+
if len(b) > 3 {
171+
c.A = b[3]
172+
}
173+
return c, nil
174+
}
175+
return Color{}, errors.New("unsupported color format")
176+
}
177+
178+
func (c Color) MarshalJSON() ([]byte, error) {
179+
return fmt.Appendf(nil, "\"#%s\"", hex.EncodeToString([]byte{c.R, c.G, c.B, c.A})), nil
180+
}
181+
182+
func (c *Color) UnmarshalJSON(data []byte) error {
183+
if string(data) == "null" || string(data) == `""` {
184+
return nil
185+
}
186+
187+
cc, err := ParseColor(string(data))
188+
*c = cc
189+
190+
return err
191+
}
192+
193+
type DateNode struct {
194+
Date string
195+
}
196+
197+
type IssueLinkMention struct {
198+
Slug string
199+
ProjectIdentifier string
200+
CurrentIssueId string
201+
OriginalUrl string
202+
}
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
package edtypes_test
2+
3+
import (
4+
"encoding/json"
5+
"testing"
6+
7+
"github.com/aisa-it/aiplan/aiplan.go/internal/aiplan/editor/edtypes"
8+
_ "github.com/aisa-it/aiplan/aiplan.go/internal/aiplan/editor/tiptap" // Регистрация парсера
9+
)
10+
11+
func TestDocument_UnmarshalJSON(t *testing.T) {
12+
tests := []struct {
13+
name string
14+
json string
15+
wantElemCount int
16+
wantErr bool
17+
}{
18+
{
19+
name: "simple paragraph",
20+
json: `{
21+
"type": "doc",
22+
"content": [
23+
{
24+
"type": "paragraph",
25+
"content": [
26+
{
27+
"type": "text",
28+
"text": "Hello World"
29+
}
30+
]
31+
}
32+
]
33+
}`,
34+
wantElemCount: 1,
35+
wantErr: false,
36+
},
37+
{
38+
name: "multiple paragraphs",
39+
json: `{
40+
"type": "doc",
41+
"content": [
42+
{
43+
"type": "paragraph",
44+
"content": [
45+
{
46+
"type": "text",
47+
"text": "First"
48+
}
49+
]
50+
},
51+
{
52+
"type": "paragraph",
53+
"content": [
54+
{
55+
"type": "text",
56+
"text": "Second"
57+
}
58+
]
59+
}
60+
]
61+
}`,
62+
wantElemCount: 2,
63+
wantErr: false,
64+
},
65+
{
66+
name: "empty document",
67+
json: `{
68+
"type": "doc",
69+
"content": []
70+
}`,
71+
wantElemCount: 0,
72+
wantErr: false,
73+
},
74+
{
75+
name: "invalid json",
76+
json: `{"type": "doc", "content": [}`,
77+
wantElemCount: 0,
78+
wantErr: true,
79+
},
80+
}
81+
82+
for _, tt := range tests {
83+
t.Run(tt.name, func(t *testing.T) {
84+
var doc edtypes.Document
85+
err := json.Unmarshal([]byte(tt.json), &doc)
86+
87+
if (err != nil) != tt.wantErr {
88+
t.Errorf("UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
89+
return
90+
}
91+
92+
if tt.wantErr {
93+
return
94+
}
95+
96+
if len(doc.Elements) != tt.wantElemCount {
97+
t.Errorf("Elements count = %v, want %v", len(doc.Elements), tt.wantElemCount)
98+
}
99+
})
100+
}
101+
}
102+
103+
func TestDocument_UnmarshalJSON_Integration(t *testing.T) {
104+
// Тест для проверки что UnmarshalJSON работает в составе DTO структуры
105+
type IssueDTO struct {
106+
Title string `json:"title"`
107+
Description edtypes.Document `json:"description"`
108+
}
109+
110+
jsonData := `{
111+
"title": "Test Issue",
112+
"description": {
113+
"type": "doc",
114+
"content": [
115+
{
116+
"type": "paragraph",
117+
"content": [
118+
{
119+
"type": "text",
120+
"text": "This is a test description"
121+
}
122+
]
123+
}
124+
]
125+
}
126+
}`
127+
128+
var issue IssueDTO
129+
err := json.Unmarshal([]byte(jsonData), &issue)
130+
if err != nil {
131+
t.Fatalf("UnmarshalJSON() error = %v", err)
132+
}
133+
134+
if issue.Title != "Test Issue" {
135+
t.Errorf("Title = %v, want %v", issue.Title, "Test Issue")
136+
}
137+
138+
if len(issue.Description.Elements) != 1 {
139+
t.Errorf("Description.Elements count = %v, want %v", len(issue.Description.Elements), 1)
140+
}
141+
142+
// Проверяем что первый элемент - это Paragraph
143+
if para, ok := issue.Description.Elements[0].(*edtypes.Paragraph); ok {
144+
if len(para.Content) != 1 {
145+
t.Errorf("Paragraph content count = %v, want %v", len(para.Content), 1)
146+
}
147+
if text, ok := para.Content[0].(edtypes.Text); ok {
148+
if text.Content != "This is a test description" {
149+
t.Errorf("Text content = %v, want %v", text.Content, "This is a test description")
150+
}
151+
} else {
152+
t.Errorf("First content element is not Text, got %T", para.Content[0])
153+
}
154+
} else {
155+
t.Error("First element is not *Paragraph")
156+
}
157+
}

0 commit comments

Comments
 (0)