A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
at upstream/main 674 lines 14 kB view raw
1// SiYuan - Refactor your thinking 2// Copyright (c) 2020-present, b3log.org 3// 4// This program is free software: you can redistribute it and/or modify 5// it under the terms of the GNU Affero General Public License as published by 6// the Free Software Foundation, either version 3 of the License, or 7// (at your option) any later version. 8// 9// This program is distributed in the hope that it will be useful, 10// but WITHOUT ANY WARRANTY; without even the implied warranty of 11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12// GNU Affero General Public License for more details. 13// 14// You should have received a copy of the GNU Affero General Public License 15// along with this program. If not, see <https://www.gnu.org/licenses/>. 16 17package api 18 19import ( 20 "fmt" 21 "net/http" 22 "strings" 23 24 "github.com/88250/gulu" 25 "github.com/gin-gonic/gin" 26 "github.com/siyuan-note/siyuan/kernel/bazaar" 27 "github.com/siyuan-note/siyuan/kernel/conf" 28 "github.com/siyuan-note/siyuan/kernel/model" 29 "github.com/siyuan-note/siyuan/kernel/server/proxy" 30 "github.com/siyuan-note/siyuan/kernel/sql" 31 "github.com/siyuan-note/siyuan/kernel/util" 32) 33 34func setEditorReadOnly(c *gin.Context) { 35 ret := gulu.Ret.NewResult() 36 defer c.JSON(http.StatusOK, ret) 37 38 arg, ok := util.JsonArg(c, ret) 39 if !ok { 40 return 41 } 42 43 readOnly := arg["readonly"].(bool) 44 45 oldReadOnly := model.Conf.Editor.ReadOnly 46 model.Conf.Editor.ReadOnly = readOnly 47 model.Conf.Save() 48 49 if oldReadOnly != model.Conf.Editor.ReadOnly { 50 util.BroadcastByType("protyle", "readonly", 0, "", model.Conf.Editor.ReadOnly) 51 util.BroadcastByType("main", "readonly", 0, "", model.Conf.Editor.ReadOnly) 52 } 53} 54 55func setConfSnippet(c *gin.Context) { 56 ret := gulu.Ret.NewResult() 57 defer c.JSON(http.StatusOK, ret) 58 59 arg, ok := util.JsonArg(c, ret) 60 if !ok { 61 return 62 } 63 64 param, err := gulu.JSON.MarshalJSON(arg) 65 if err != nil { 66 ret.Code = -1 67 ret.Msg = err.Error() 68 return 69 } 70 71 snippet := &conf.Snpt{} 72 if err = gulu.JSON.UnmarshalJSON(param, snippet); err != nil { 73 ret.Code = -1 74 ret.Msg = err.Error() 75 return 76 } 77 78 model.Conf.Snippet = snippet 79 model.Conf.Save() 80 81 ret.Data = snippet 82} 83 84func addVirtualBlockRefExclude(c *gin.Context) { 85 // Add internal kernel API `/api/setting/addVirtualBlockRefExclude` https://github.com/siyuan-note/siyuan/issues/9909 86 87 ret := gulu.Ret.NewResult() 88 defer c.JSON(http.StatusOK, ret) 89 90 arg, ok := util.JsonArg(c, ret) 91 if !ok { 92 return 93 } 94 95 keywordsArg := arg["keywords"] 96 var keywords []string 97 for _, k := range keywordsArg.([]interface{}) { 98 keywords = append(keywords, k.(string)) 99 } 100 101 model.AddVirtualBlockRefExclude(keywords) 102 util.BroadcastByType("main", "setConf", 0, "", model.Conf) 103} 104 105func addVirtualBlockRefInclude(c *gin.Context) { 106 // Add internal kernel API `/api/setting/addVirtualBlockRefInclude` https://github.com/siyuan-note/siyuan/issues/9909 107 108 ret := gulu.Ret.NewResult() 109 defer c.JSON(http.StatusOK, ret) 110 111 arg, ok := util.JsonArg(c, ret) 112 if !ok { 113 return 114 } 115 116 keywordsArg := arg["keywords"] 117 var keywords []string 118 for _, k := range keywordsArg.([]interface{}) { 119 keywords = append(keywords, k.(string)) 120 } 121 122 model.AddVirtualBlockRefInclude(keywords) 123 util.BroadcastByType("main", "setConf", 0, "", model.Conf) 124} 125 126func refreshVirtualBlockRef(c *gin.Context) { 127 // Add internal kernel API `/api/setting/refreshVirtualBlockRef` https://github.com/siyuan-note/siyuan/issues/9829 128 129 ret := gulu.Ret.NewResult() 130 defer c.JSON(http.StatusOK, ret) 131 132 model.ResetVirtualBlockRefCache() 133 util.BroadcastByType("main", "setConf", 0, "", model.Conf) 134} 135 136func setBazaar(c *gin.Context) { 137 ret := gulu.Ret.NewResult() 138 defer c.JSON(http.StatusOK, ret) 139 140 arg, ok := util.JsonArg(c, ret) 141 if !ok { 142 return 143 } 144 145 param, err := gulu.JSON.MarshalJSON(arg) 146 if err != nil { 147 ret.Code = -1 148 ret.Msg = err.Error() 149 return 150 } 151 152 bazaar := &conf.Bazaar{} 153 if err = gulu.JSON.UnmarshalJSON(param, bazaar); err != nil { 154 ret.Code = -1 155 ret.Msg = err.Error() 156 return 157 } 158 159 model.Conf.Bazaar = bazaar 160 model.Conf.Save() 161 162 ret.Data = bazaar 163} 164 165func setAI(c *gin.Context) { 166 ret := gulu.Ret.NewResult() 167 defer c.JSON(http.StatusOK, ret) 168 169 arg, ok := util.JsonArg(c, ret) 170 if !ok { 171 return 172 } 173 174 param, err := gulu.JSON.MarshalJSON(arg) 175 if err != nil { 176 ret.Code = -1 177 ret.Msg = err.Error() 178 return 179 } 180 181 ai := &conf.AI{} 182 if err = gulu.JSON.UnmarshalJSON(param, ai); err != nil { 183 ret.Code = -1 184 ret.Msg = err.Error() 185 return 186 } 187 188 if 5 > ai.OpenAI.APITimeout { 189 ai.OpenAI.APITimeout = 5 190 } 191 if 600 < ai.OpenAI.APITimeout { 192 ai.OpenAI.APITimeout = 600 193 } 194 195 if 0 > ai.OpenAI.APIMaxTokens { 196 ai.OpenAI.APIMaxTokens = 0 197 } 198 199 if 0 >= ai.OpenAI.APITemperature || 2 < ai.OpenAI.APITemperature { 200 ai.OpenAI.APITemperature = 1.0 201 } 202 203 if 1 > ai.OpenAI.APIMaxContexts || 64 < ai.OpenAI.APIMaxContexts { 204 ai.OpenAI.APIMaxContexts = 7 205 } 206 207 model.Conf.AI = ai 208 model.Conf.Save() 209 210 ret.Data = ai 211} 212 213func setFlashcard(c *gin.Context) { 214 ret := gulu.Ret.NewResult() 215 defer c.JSON(http.StatusOK, ret) 216 217 arg, ok := util.JsonArg(c, ret) 218 if !ok { 219 return 220 } 221 222 param, err := gulu.JSON.MarshalJSON(arg) 223 if err != nil { 224 ret.Code = -1 225 ret.Msg = err.Error() 226 return 227 } 228 229 flashcard := &conf.Flashcard{} 230 if err = gulu.JSON.UnmarshalJSON(param, flashcard); err != nil { 231 ret.Code = -1 232 ret.Msg = err.Error() 233 return 234 } 235 236 if 0 > flashcard.NewCardLimit { 237 flashcard.NewCardLimit = 20 238 } 239 240 if 0 > flashcard.ReviewCardLimit { 241 flashcard.ReviewCardLimit = 200 242 } 243 244 model.Conf.Flashcard = flashcard 245 model.Conf.Save() 246 247 ret.Data = flashcard 248} 249 250func setAccount(c *gin.Context) { 251 ret := gulu.Ret.NewResult() 252 defer c.JSON(http.StatusOK, ret) 253 254 arg, ok := util.JsonArg(c, ret) 255 if !ok { 256 return 257 } 258 259 param, err := gulu.JSON.MarshalJSON(arg) 260 if err != nil { 261 ret.Code = -1 262 ret.Msg = err.Error() 263 return 264 } 265 266 account := &conf.Account{} 267 if err = gulu.JSON.UnmarshalJSON(param, account); err != nil { 268 ret.Code = -1 269 ret.Msg = err.Error() 270 return 271 } 272 273 model.Conf.Account = account 274 model.Conf.Save() 275 276 ret.Data = model.Conf.Account 277} 278 279func setEditor(c *gin.Context) { 280 ret := gulu.Ret.NewResult() 281 defer c.JSON(http.StatusOK, ret) 282 283 arg, ok := util.JsonArg(c, ret) 284 if !ok { 285 return 286 } 287 288 param, err := gulu.JSON.MarshalJSON(arg) 289 if err != nil { 290 ret.Code = -1 291 ret.Msg = err.Error() 292 return 293 } 294 295 oldGenerateHistoryInterval := model.Conf.Editor.GenerateHistoryInterval 296 297 editor := conf.NewEditor() 298 if err = gulu.JSON.UnmarshalJSON(param, editor); err != nil { 299 ret.Code = -1 300 ret.Msg = err.Error() 301 return 302 } 303 304 if "" == editor.PlantUMLServePath { 305 editor.PlantUMLServePath = "https://www.plantuml.com/plantuml/svg/~1" 306 } 307 308 if "" == editor.KaTexMacros { 309 editor.KaTexMacros = "{}" 310 } 311 312 if 1 > editor.HistoryRetentionDays { 313 editor.HistoryRetentionDays = 30 314 } 315 if 3650 < editor.HistoryRetentionDays { 316 editor.HistoryRetentionDays = 3650 317 } 318 319 oldVirtualBlockRef := model.Conf.Editor.VirtualBlockRef 320 oldVirtualBlockRefInclude := model.Conf.Editor.VirtualBlockRefInclude 321 oldVirtualBlockRefExclude := model.Conf.Editor.VirtualBlockRefExclude 322 oldReadOnly := model.Conf.Editor.ReadOnly 323 324 model.Conf.Editor = editor 325 model.Conf.Save() 326 327 if oldGenerateHistoryInterval != model.Conf.Editor.GenerateHistoryInterval { 328 model.ChangeHistoryTick(editor.GenerateHistoryInterval) 329 } 330 331 if oldVirtualBlockRef != model.Conf.Editor.VirtualBlockRef || 332 oldVirtualBlockRefInclude != model.Conf.Editor.VirtualBlockRefInclude || 333 oldVirtualBlockRefExclude != model.Conf.Editor.VirtualBlockRefExclude { 334 model.ResetVirtualBlockRefCache() 335 } 336 337 if oldReadOnly != model.Conf.Editor.ReadOnly { 338 util.BroadcastByType("protyle", "readonly", 0, "", model.Conf.Editor.ReadOnly) 339 util.BroadcastByType("main", "readonly", 0, "", model.Conf.Editor.ReadOnly) 340 } 341 342 util.MarkdownSettings = model.Conf.Editor.Markdown 343 344 ret.Data = model.Conf.Editor 345} 346 347func setExport(c *gin.Context) { 348 ret := gulu.Ret.NewResult() 349 defer c.JSON(http.StatusOK, ret) 350 351 arg, ok := util.JsonArg(c, ret) 352 if !ok { 353 return 354 } 355 356 param, err := gulu.JSON.MarshalJSON(arg) 357 if err != nil { 358 ret.Code = -1 359 ret.Msg = err.Error() 360 return 361 } 362 363 export := &conf.Export{} 364 if err = gulu.JSON.UnmarshalJSON(param, export); err != nil { 365 ret.Code = -1 366 ret.Msg = err.Error() 367 ret.Data = map[string]interface{}{"closeTimeout": 5000} 368 return 369 } 370 371 if "" != export.PandocBin { 372 if !util.IsValidPandocBin(export.PandocBin) { 373 util.PushErrMsg(fmt.Sprintf(model.Conf.Language(117), export.PandocBin), 5000) 374 export.PandocBin = util.PandocBinPath 375 } else { 376 util.PandocBinPath = export.PandocBin 377 } 378 } 379 380 model.Conf.Export = export 381 model.Conf.Save() 382 383 ret.Data = model.Conf.Export 384} 385 386func setFiletree(c *gin.Context) { 387 ret := gulu.Ret.NewResult() 388 defer c.JSON(http.StatusOK, ret) 389 390 arg, ok := util.JsonArg(c, ret) 391 if !ok { 392 return 393 } 394 395 param, err := gulu.JSON.MarshalJSON(arg) 396 if err != nil { 397 ret.Code = -1 398 ret.Msg = err.Error() 399 return 400 } 401 402 fileTree := conf.NewFileTree() 403 if err = gulu.JSON.UnmarshalJSON(param, fileTree); err != nil { 404 ret.Code = -1 405 ret.Msg = err.Error() 406 return 407 } 408 409 fileTree.RefCreateSavePath = util.TrimSpaceInPath(fileTree.RefCreateSavePath) 410 if "" != fileTree.RefCreateSavePath { 411 if !strings.HasSuffix(fileTree.RefCreateSavePath, "/") { 412 fileTree.RefCreateSavePath += "/" 413 } 414 } 415 416 fileTree.DocCreateSavePath = util.TrimSpaceInPath(fileTree.DocCreateSavePath) 417 418 if 1 > fileTree.MaxOpenTabCount { 419 fileTree.MaxOpenTabCount = 8 420 } 421 if 32 < fileTree.MaxOpenTabCount { 422 fileTree.MaxOpenTabCount = 32 423 } 424 model.Conf.FileTree = fileTree 425 model.Conf.Save() 426 427 util.UseSingleLineSave = model.Conf.FileTree.UseSingleLineSave 428 util.LargeFileWarningSize = model.Conf.FileTree.LargeFileWarningSize 429 430 ret.Data = model.Conf.FileTree 431} 432 433func setSearch(c *gin.Context) { 434 ret := gulu.Ret.NewResult() 435 defer c.JSON(http.StatusOK, ret) 436 437 arg, ok := util.JsonArg(c, ret) 438 if !ok { 439 return 440 } 441 442 param, err := gulu.JSON.MarshalJSON(arg) 443 if err != nil { 444 ret.Code = -1 445 ret.Msg = err.Error() 446 return 447 } 448 449 s := &conf.Search{} 450 if err = gulu.JSON.UnmarshalJSON(param, s); err != nil { 451 ret.Code = -1 452 ret.Msg = err.Error() 453 return 454 } 455 456 if 32 > s.Limit { 457 s.Limit = 32 458 } 459 460 oldCaseSensitive := model.Conf.Search.CaseSensitive 461 oldIndexAssetPath := model.Conf.Search.IndexAssetPath 462 463 oldVirtualRefName := model.Conf.Search.VirtualRefName 464 oldVirtualRefAlias := model.Conf.Search.VirtualRefAlias 465 oldVirtualRefAnchor := model.Conf.Search.VirtualRefAnchor 466 oldVirtualRefDoc := model.Conf.Search.VirtualRefDoc 467 468 model.Conf.Search = s 469 model.Conf.Save() 470 471 sql.SetCaseSensitive(s.CaseSensitive) 472 sql.SetIndexAssetPath(s.IndexAssetPath) 473 474 if needFullReindex := s.CaseSensitive != oldCaseSensitive || s.IndexAssetPath != oldIndexAssetPath; needFullReindex { 475 model.FullReindex() 476 } 477 478 if oldVirtualRefName != s.VirtualRefName || 479 oldVirtualRefAlias != s.VirtualRefAlias || 480 oldVirtualRefAnchor != s.VirtualRefAnchor || 481 oldVirtualRefDoc != s.VirtualRefDoc { 482 model.ResetVirtualBlockRefCache() 483 } 484 ret.Data = s 485} 486 487func setKeymap(c *gin.Context) { 488 ret := gulu.Ret.NewResult() 489 defer c.JSON(http.StatusOK, ret) 490 491 arg, ok := util.JsonArg(c, ret) 492 if !ok { 493 return 494 } 495 496 param, err := gulu.JSON.MarshalJSON(arg["data"]) 497 if err != nil { 498 ret.Code = -1 499 ret.Msg = err.Error() 500 return 501 } 502 503 keymap := &conf.Keymap{} 504 if err = gulu.JSON.UnmarshalJSON(param, keymap); err != nil { 505 ret.Code = -1 506 ret.Msg = err.Error() 507 return 508 } 509 510 model.Conf.Keymap = keymap 511 model.Conf.Save() 512} 513 514func setAppearance(c *gin.Context) { 515 ret := gulu.Ret.NewResult() 516 defer c.JSON(http.StatusOK, ret) 517 518 arg, ok := util.JsonArg(c, ret) 519 if !ok { 520 return 521 } 522 523 param, err := gulu.JSON.MarshalJSON(arg) 524 if err != nil { 525 ret.Code = -1 526 ret.Msg = err.Error() 527 return 528 } 529 530 appearance := &conf.Appearance{} 531 if err = gulu.JSON.UnmarshalJSON(param, appearance); err != nil { 532 ret.Code = -1 533 ret.Msg = err.Error() 534 return 535 } 536 537 model.Conf.Appearance = appearance 538 model.Conf.Lang = appearance.Lang 539 oldLang := util.Lang 540 util.Lang = model.Conf.Lang 541 model.Conf.Save() 542 model.InitAppearance() 543 544 if oldLang != util.Lang { 545 // The marketplace language does not change after switching the appearance language https://github.com/siyuan-note/siyuan/issues/12892 546 bazaar.CleanBazaarPackageCache() 547 } 548 549 ret.Data = model.Conf.Appearance 550} 551 552func setPublish(c *gin.Context) { 553 ret := gulu.Ret.NewResult() 554 defer c.JSON(http.StatusOK, ret) 555 556 arg, ok := util.JsonArg(c, ret) 557 if !ok { 558 return 559 } 560 561 param, err := gulu.JSON.MarshalJSON(arg) 562 if err != nil { 563 ret.Code = -1 564 ret.Msg = err.Error() 565 return 566 } 567 568 publish := &conf.Publish{} 569 if err = gulu.JSON.UnmarshalJSON(param, publish); err != nil { 570 ret.Code = -1 571 ret.Msg = err.Error() 572 return 573 } 574 575 model.Conf.Publish = publish 576 model.Conf.Save() 577 578 if port, err := proxy.InitPublishService(); err != nil { 579 ret.Code = -1 580 ret.Msg = err.Error() 581 } else { 582 ret.Data = map[string]any{ 583 "port": port, 584 "publish": model.Conf.Publish, 585 } 586 } 587} 588 589func getPublish(c *gin.Context) { 590 ret := gulu.Ret.NewResult() 591 defer c.JSON(http.StatusOK, ret) 592 593 if port, err := proxy.InitPublishService(); err != nil { 594 ret.Code = -1 595 ret.Msg = err.Error() 596 } else { 597 ret.Data = map[string]any{ 598 "port": port, 599 "publish": model.Conf.Publish, 600 } 601 } 602} 603 604func getCloudUser(c *gin.Context) { 605 ret := gulu.Ret.NewResult() 606 defer c.JSON(http.StatusOK, ret) 607 608 if !model.IsAdminRoleContext(c) { 609 return 610 } 611 612 arg, ok := util.JsonArg(c, ret) 613 if !ok { 614 return 615 } 616 617 t := arg["token"] 618 var token string 619 if nil != t { 620 token = t.(string) 621 } 622 model.RefreshUser(token) 623 ret.Data = model.Conf.GetUser() 624} 625 626func logoutCloudUser(c *gin.Context) { 627 ret := gulu.Ret.NewResult() 628 defer c.JSON(http.StatusOK, ret) 629 630 model.LogoutUser() 631} 632 633func login2faCloudUser(c *gin.Context) { 634 ret := gulu.Ret.NewResult() 635 defer c.JSON(http.StatusOK, ret) 636 637 arg, ok := util.JsonArg(c, ret) 638 if !ok { 639 return 640 } 641 642 token := arg["token"].(string) 643 code := arg["code"].(string) 644 data, err := model.Login2fa(token, code) 645 if err != nil { 646 ret.Code = -1 647 ret.Msg = err.Error() 648 return 649 } 650 ret.Data = data 651} 652 653func setEmoji(c *gin.Context) { 654 ret := gulu.Ret.NewResult() 655 defer c.JSON(http.StatusOK, ret) 656 657 arg, ok := util.JsonArg(c, ret) 658 if !ok { 659 return 660 } 661 662 argEmoji := arg["emoji"].([]interface{}) 663 var emoji []string 664 for _, ae := range argEmoji { 665 e := ae.(string) 666 if strings.Contains(e, ".") { 667 // XSS through emoji name https://github.com/siyuan-note/siyuan/issues/15034 668 e = util.FilterUploadEmojiFileName(e) 669 } 670 emoji = append(emoji, e) 671 } 672 673 model.Conf.Editor.Emoji = emoji 674}