diff --git a/admin/controller.go b/admin/controller.go index ab7cf23b..aa7c4b69 100644 --- a/admin/controller.go +++ b/admin/controller.go @@ -29,6 +29,10 @@ type SubscriptionOperationForm struct { Month int64 `json:"month"` } +type UpdateRootPasswordForm struct { + Password string `json:"password"` +} + func InfoAPI(c *gin.Context) { db := utils.GetDBFromContext(c) cache := utils.GetCacheFromContext(c) @@ -161,3 +165,29 @@ func UserSubscriptionAPI(c *gin.Context) { "status": true, }) } + +func UpdateRootPasswordAPI(c *gin.Context) { + var form UpdateRootPasswordForm + if err := c.ShouldBindJSON(&form); err != nil { + c.JSON(http.StatusOK, gin.H{ + "status": false, + "error": err.Error(), + }) + return + } + + db := utils.GetDBFromContext(c) + cache := utils.GetCacheFromContext(c) + err := UpdateRootPassword(db, cache, form.Password) + if err != nil { + c.JSON(http.StatusOK, gin.H{ + "status": false, + "error": err.Error(), + }) + return + } + + c.JSON(http.StatusOK, gin.H{ + "status": true, + }) +} diff --git a/admin/router.go b/admin/router.go index f8a2b86a..1255a80a 100644 --- a/admin/router.go +++ b/admin/router.go @@ -23,4 +23,5 @@ func Register(app *gin.RouterGroup) { app.GET("/admin/user/list", UserPaginationAPI) app.POST("/admin/user/quota", UserQuotaAPI) app.POST("/admin/user/subscription", UserSubscriptionAPI) + app.POST("/admin/user/root", UpdateRootPasswordAPI) } diff --git a/admin/user.go b/admin/user.go index f5b0810f..51bf4f7e 100644 --- a/admin/user.go +++ b/admin/user.go @@ -2,8 +2,12 @@ package admin import ( "chat/utils" + "context" "database/sql" + "fmt" + "github.com/go-redis/redis/v8" "math" + "strings" "time" ) @@ -109,3 +113,19 @@ func SubscriptionOperation(db *sql.DB, id int64, month int64) error { return err } + +func UpdateRootPassword(db *sql.DB, cache *redis.Client, password string) error { + password = strings.TrimSpace(password) + if len(password) < 6 || len(password) > 36 { + return fmt.Errorf("password length must be between 6 and 36") + } + + if _, err := db.Exec(` + UPDATE auth SET password = ? WHERE username = 'root' + `, utils.Sha2Encrypt(password)); err != nil { + return err + } + + cache.Del(context.Background(), fmt.Sprint("nio:user:root")) + return nil +} diff --git a/app/src/admin/api/info.ts b/app/src/admin/api/info.ts new file mode 100644 index 00000000..0aee8034 --- /dev/null +++ b/app/src/admin/api/info.ts @@ -0,0 +1,24 @@ +import axios from "axios"; +import { setAppLogo, setAppName } from "@/utils/env.ts"; + +export type SiteInfo = { + title: string; + logo: string; +}; + +export async function getSiteInfo(): Promise { + try { + const response = await axios.get("/info"); + return response.data as SiteInfo; + } catch (e) { + console.warn(e); + return { title: "", logo: "" }; + } +} + +export function syncSiteInfo() { + getSiteInfo().then((info) => { + setAppName(info.title); + setAppLogo(info.logo); + }); +} diff --git a/app/src/admin/api/system.ts b/app/src/admin/api/system.ts index 9b0930a1..97fb50d1 100644 --- a/app/src/admin/api/system.ts +++ b/app/src/admin/api/system.ts @@ -3,6 +3,8 @@ import { getErrorMessage } from "@/utils/base.ts"; import axios from "axios"; export type GeneralState = { + title: string; + logo: string; backend: string; }; @@ -47,8 +49,21 @@ export async function setConfig(config: SystemProps): Promise { } } +export async function updateRootPassword( + password: string, +): Promise { + try { + const response = await axios.post(`/admin/user/root`, { password }); + return response.data as CommonResponse; + } catch (e) { + return { status: false, error: getErrorMessage(e) }; + } +} + export const initialSystemState: SystemProps = { general: { + logo: "", + title: "", backend: "", }, mail: { diff --git a/app/src/assets/pages/auth.less b/app/src/assets/pages/auth.less index 997cf187..263ff45e 100644 --- a/app/src/assets/pages/auth.less +++ b/app/src/assets/pages/auth.less @@ -15,6 +15,7 @@ .logo { width: 4rem; height: 4rem; + border-radius: var(--radius); } & > * { diff --git a/app/src/assets/pages/navbar.less b/app/src/assets/pages/navbar.less index 873a71b1..90402f0b 100644 --- a/app/src/assets/pages/navbar.less +++ b/app/src/assets/pages/navbar.less @@ -31,6 +31,7 @@ margin: 2px; width: 40px; height: 40px; + border-radius: var(--radius); cursor: pointer; } diff --git a/app/src/assets/ui.less b/app/src/assets/ui.less index 23403013..15baf7fb 100644 --- a/app/src/assets/ui.less +++ b/app/src/assets/ui.less @@ -234,13 +234,18 @@ input[type="number"] { } } + .paragraph-space { + width: 100%; + height: 0.25rem; + } + .paragraph-footer { display: flex; flex-direction: row; margin-top: 1rem; & > * { - margin-right: 1rem; + margin-right: 0.5rem; &:last-child { margin-right: 0; diff --git a/app/src/components/Paragraph.tsx b/app/src/components/Paragraph.tsx index 05d5ec35..9bd2f83d 100644 --- a/app/src/components/Paragraph.tsx +++ b/app/src/components/Paragraph.tsx @@ -78,6 +78,10 @@ export function ParagraphDescription({ children }: { children: string }) { ); } +export function ParagraphSpace() { + return
; +} + function ParagraphFooter({ children }: { children: React.ReactNode }) { return
{children}
; } diff --git a/app/src/components/app/NavBar.tsx b/app/src/components/app/NavBar.tsx index 390f07ed..23be6374 100644 --- a/app/src/components/app/NavBar.tsx +++ b/app/src/components/app/NavBar.tsx @@ -18,6 +18,7 @@ import MenuBar from "./MenuBar.tsx"; import { getMemory } from "@/utils/memory.ts"; import { goAuth } from "@/utils/app.ts"; import Avatar from "@/components/Avatar.tsx"; +import { appLogo } from "@/utils/env.ts"; function NavMenu() { const username = useSelector(selectUsername); @@ -53,7 +54,7 @@ function NavBar() { router.navigate("/")} /> diff --git a/app/src/conf.ts b/app/src/conf.ts index fa819976..af95b238 100644 --- a/app/src/conf.ts +++ b/app/src/conf.ts @@ -11,6 +11,7 @@ import { import { getMemory } from "@/utils/memory.ts"; import { Compass, Image, Newspaper } from "lucide-react"; import React from "react"; +import { syncSiteInfo } from "@/admin/api/info.ts"; export const version = "3.8.0"; export const dev: boolean = getDev(); @@ -524,3 +525,5 @@ export function login() { axios.defaults.baseURL = rest_api; axios.defaults.headers.post["Content-Type"] = "application/json"; axios.defaults.headers.common["Authorization"] = getMemory(tokenField); + +syncSiteInfo(); diff --git a/app/src/resources/i18n/cn.json b/app/src/resources/i18n/cn.json index 19ed87ea..73e87c66 100644 --- a/app/src/resources/i18n/cn.json +++ b/app/src/resources/i18n/cn.json @@ -464,9 +464,18 @@ "search": "联网搜索", "mail": "SMTP 发件设置", "save": "保存", + "updateRoot": "修改 Root 密码", + "updateRootTip": "请谨慎操作,修改 Root 密码后,您需要重新登录。", + "updateRootPlaceholder": "请输入新的 Root 密码", + "updateRootRepeatPlaceholder": "请再次输入新的 Root 密码", "test": "测试发件", + "title": "网站名称", + "titleTip": "网站名称,用于显示在网站标题,留空默认", + "logo": "网站 Logo", + "logoTip": "网站 Logo 的链接,用于显示在网站标题,留空默认 (如 {{logo}})", "backend": "后端域名", "backendTip": "后端回调域名(docker 安装默认路径为 /api),接收回调参数。", + "backendPlaceholder": "后端回调域名,默认为空,接受回调必填(如 {{backend}})", "mailHost": "发件域名", "mailPort": "SMTP 端口", "mailUser": "用户名", @@ -474,7 +483,7 @@ "mailFrom": "发件人", "searchEndpoint": "搜索接入点", "searchQuery": "最大搜索结果数", - "searchTip": "DuckDuckGo 搜索接入点,如不填写自动使用 WebPilot 和 New Bing 逆向进行搜索功能。\nDuckDuckGo API 项目搭建:[duckduckgo-api](https://github.com/binjie09/duckduckgo-api)。" + "searchTip": "DuckDuckGo 搜索接入点,如不填写自动使用 WebPilot 和 New Bing 逆向进行搜索功能(速度较慢)。\nDuckDuckGo API 项目搭建:[duckduckgo-api](https://github.com/binjie09/duckduckgo-api)。" } }, "mask": { diff --git a/app/src/resources/i18n/en.json b/app/src/resources/i18n/en.json index 414da44d..f9f07d79 100644 --- a/app/src/resources/i18n/en.json +++ b/app/src/resources/i18n/en.json @@ -425,7 +425,16 @@ "searchQuery": "Max Search Results", "searchTip": "DuckDuckGo search endpoint, if not filled in, use WebPilot and New Bing reverse search function by default.\nDuckDuckGo API project build: [duckduckgo-api](https://github.com/binjie09/duckduckgo-api).", "mailFrom": "Sender", - "test": "Test outgoing" + "test": "Test outgoing", + "updateRoot": "Change Root Password", + "updateRootTip": "Please proceed with caution, after changing the root password, you will need to log in again.", + "updateRootPlaceholder": "Please enter a new root password", + "updateRootRepeatPlaceholder": "Please enter the new root password again", + "title": "Site name", + "titleTip": "Site name to display in the site title, leave blank for default", + "logo": "Site Logo", + "logoTip": "Link to the site logo to display in the site title, leave blank for default (e.g. {{logo}})", + "backendPlaceholder": "Backend callback domain name, empty by default, required for accepting callbacks (e.g. {{backend}})" }, "user": "User Management", "invitation-code": "Invitation Code", diff --git a/app/src/resources/i18n/ja.json b/app/src/resources/i18n/ja.json index fcfea81b..f3a88b98 100644 --- a/app/src/resources/i18n/ja.json +++ b/app/src/resources/i18n/ja.json @@ -425,7 +425,16 @@ "searchQuery": "検索結果の最大数", "searchTip": "DuckDuckGoは、入力せずにWebPilotやNew Bing Reverse Searchなどのアクセスポイントを自動的に検索します。\\ nDuckDuckGo APIプロジェクトビルド:[ duckduckgo - api ]( https://github.com/binjie09/duckduckgo-api )。", "mailFrom": "発信元", - "test": "テスト送信" + "test": "テスト送信", + "updateRoot": "ルートパスワードの変更", + "updateRootTip": "ルートパスワードを変更した後、再度ログインする必要がありますので、慎重に進んでください。", + "updateRootPlaceholder": "新しいrootパスワードを入力してください", + "updateRootRepeatPlaceholder": "新しいrootパスワードをもう一度入力してください", + "title": "サイト名", + "titleTip": "サイトタイトルに表示するサイト名、デフォルトの場合は空白のままにします", + "logo": "サイトロゴ", + "logoTip": "サイトタイトルに表示するサイトロゴへのリンク、デフォルトの場合は空白のままにします(例:{{ logo }})", + "backendPlaceholder": "バックエンドコールバックドメイン名、デフォルトで空、コールバックを受け入れるために必要(例:{{ backend }})" }, "user": "ユーザー管理", "invitation-code": "招待コード", diff --git a/app/src/resources/i18n/ru.json b/app/src/resources/i18n/ru.json index a00da593..e6e401eb 100644 --- a/app/src/resources/i18n/ru.json +++ b/app/src/resources/i18n/ru.json @@ -425,7 +425,16 @@ "searchQuery": "Максимальное количество результатов поиска", "searchTip": "Конечная точка поиска DuckDuckGo, если она не заполнена, по умолчанию используется функция обратного поиска WebPilot и New Bing.\nСборка проекта DuckDuckGo API: [duckduckgo-api](https://github.com/binjie09/duckduckgo-api).", "mailFrom": "От", - "test": "Тест исходящий" + "test": "Тест исходящий", + "updateRoot": "Изменить корневой пароль", + "updateRootTip": "Пожалуйста, соблюдайте осторожность, после смены пароля root вам нужно будет снова войти в систему.", + "updateRootPlaceholder": "Введите новый пароль root", + "updateRootRepeatPlaceholder": "Введите новый пароль root еще раз", + "title": "Название сайта", + "titleTip": "Название сайта для отображения в заголовке сайта, оставьте пустым по умолчанию", + "logo": "Логотип сайта", + "logoTip": "Ссылка на логотип сайта для отображения в заголовке сайта, оставьте поле пустым по умолчанию (например, {{logo}})", + "backendPlaceholder": "Имя домена обратного вызова Backend, пустое по умолчанию, требуется для приема обратных вызовов (например, {{backend}})" }, "user": "Управление пользователями", "invitation-code": "Код приглашения", diff --git a/app/src/routes/Auth.tsx b/app/src/routes/Auth.tsx index 58261f4c..572be9e9 100644 --- a/app/src/routes/Auth.tsx +++ b/app/src/routes/Auth.tsx @@ -10,7 +10,7 @@ import router from "@/router.tsx"; import { useTranslation } from "react-i18next"; import { getQueryParam } from "@/utils/path.ts"; import { setMemory } from "@/utils/memory.ts"; -import { useDeeptrain } from "@/utils/env.ts"; +import { appLogo, appName, useDeeptrain } from "@/utils/env.ts"; import { Card, CardContent } from "@/components/ui/card.tsx"; import { goAuth } from "@/utils/app.ts"; import { Label } from "@/components/ui/label.tsx"; @@ -132,8 +132,10 @@ function Login() { return (
- -
{t("login")} Chat Nio
+ +
+ {t("login")} {appName} +
diff --git a/app/src/routes/Forgot.tsx b/app/src/routes/Forgot.tsx index 16ae12c1..b4d19f94 100644 --- a/app/src/routes/Forgot.tsx +++ b/app/src/routes/Forgot.tsx @@ -14,6 +14,7 @@ import Require, { import { Input } from "@/components/ui/input.tsx"; import { Button } from "@/components/ui/button.tsx"; import TickButton from "@/components/TickButton.tsx"; +import { appLogo } from "@/utils/env.ts"; function Forgot() { const { t } = useTranslation(); @@ -57,7 +58,7 @@ function Forgot() { return (
- +
{t("auth.reset-password")}
diff --git a/app/src/routes/Generation.tsx b/app/src/routes/Generation.tsx index 805cb49d..21933df0 100644 --- a/app/src/routes/Generation.tsx +++ b/app/src/routes/Generation.tsx @@ -12,6 +12,7 @@ import { useToast } from "@/components/ui/use-toast.ts"; import { handleGenerationData } from "@/utils/processor.ts"; import { selectModel } from "@/store/chat.ts"; import ModelFinder from "@/components/home/ModelFinder.tsx"; +import { appLogo } from "@/utils/env.ts"; type WrapperProps = { onSend?: (value: string, model: string) => boolean; @@ -127,7 +128,7 @@ function Wrapper({ onSend }: WrapperProps) {
) : (
- {""} + {""} AI Code Generator
)} diff --git a/app/src/routes/Register.tsx b/app/src/routes/Register.tsx index 5889f9d6..be18b412 100644 --- a/app/src/routes/Register.tsx +++ b/app/src/routes/Register.tsx @@ -16,6 +16,7 @@ import { useToast } from "@/components/ui/use-toast.ts"; import TickButton from "@/components/TickButton.tsx"; import { validateToken } from "@/store/auth.ts"; import { useDispatch } from "react-redux"; +import { appLogo, appName } from "@/utils/env.ts"; type CompProps = { form: RegisterForm; @@ -225,8 +226,10 @@ function Register() { return (
- -
{t("register")} Chat Nio
+ +
+ {t("register")} {appName} +
{!next ? ( diff --git a/app/src/routes/admin/System.tsx b/app/src/routes/admin/System.tsx index 76c18020..7fc8588b 100644 --- a/app/src/routes/admin/System.tsx +++ b/app/src/routes/admin/System.tsx @@ -24,6 +24,7 @@ import { SearchState, setConfig, SystemProps, + updateRootPassword, } from "@/admin/api/system.ts"; import { useEffectAsync } from "@/utils/hook.ts"; import { toastState } from "@/admin/utils.ts"; @@ -38,6 +39,7 @@ import { DialogTrigger, } from "@/components/ui/dialog.tsx"; import { DialogTitle } from "@radix-ui/react-dialog"; +import Require from "@/components/Require.tsx"; type CompProps = { data: T; @@ -45,6 +47,83 @@ type CompProps = { onChange: (doToast?: boolean) => Promise; }; +function RootDialog() { + const { t } = useTranslation(); + const { toast } = useToast(); + const [open, setOpen] = useState(false); + const [password, setPassword] = useState(""); + const [repeat, setRepeat] = useState(""); + + const onPost = async () => { + const res = await updateRootPassword(password); + toastState(toast, t, res, true); + if (res.status) { + setPassword(""); + setRepeat(""); + setOpen(false); + + setTimeout(() => { + window.location.reload(); + }, 1000); + } + }; + + return ( + + + + + + + {t("admin.system.updateRoot")} + +
+ {t("admin.system.updateRootTip")} +
+ setPassword(e.target.value)} + /> + setRepeat(e.target.value)} + /> +
+
+ + + + +
+
+ ); +} + function General({ data, dispatch, onChange }: CompProps) { const { t } = useTranslation(); @@ -55,7 +134,37 @@ function General({ data, dispatch, onChange }: CompProps) { isCollapsed={true} > - + + + dispatch({ + type: "update:general.title", + value: e.target.value, + }) + } + placeholder={t("admin.system.titleTip")} + /> + + + + + dispatch({ + type: "update:general.logo", + value: e.target.value, + }) + } + placeholder={t("admin.system.logoTip", { + logo: `${window.location.protocol}//${window.location.host}/favicon.ico`, + })} + /> + + + @@ -64,7 +173,9 @@ function General({ data, dispatch, onChange }: CompProps) { value: e.target.value, }) } - placeholder={`${window.location.protocol}//${window.location.host}/api`} + placeholder={t("admin.system.backendPlaceholder", { + backend: `${window.location.protocol}//${window.location.host}/api`, + })} /> @@ -72,7 +183,12 @@ function General({ data, dispatch, onChange }: CompProps) {
- @@ -102,7 +218,9 @@ function Mail({ data, dispatch, onChange }: CompProps) { isCollapsed={true} > - + @@ -115,7 +233,9 @@ function Mail({ data, dispatch, onChange }: CompProps) { /> - + @@ -127,7 +247,9 @@ function Mail({ data, dispatch, onChange }: CompProps) { /> - + @@ -140,7 +262,9 @@ function Mail({ data, dispatch, onChange }: CompProps) { /> - + @@ -153,7 +277,9 @@ function Mail({ data, dispatch, onChange }: CompProps) { /> - + @@ -200,7 +326,11 @@ function Mail({ data, dispatch, onChange }: CompProps) { - @@ -245,7 +375,11 @@ function Search({ data, dispatch, onChange }: CompProps) { {t("admin.system.searchTip")}
- diff --git a/app/src/utils/dom.ts b/app/src/utils/dom.ts index bc85290a..26891907 100644 --- a/app/src/utils/dom.ts +++ b/app/src/utils/dom.ts @@ -220,3 +220,15 @@ export function scrollDown(el: HTMLElement | null) { behavior: "smooth", }); } + +export function updateFavicon(url: string) { + /** + * Update favicon in the link element from head + * @param url Favicon url + * @example + * updateFavicon("https://example.com/favicon.ico"); + */ + + const link = document.querySelector("link[rel*='icon']"); + return link && link.setAttribute("href", url); +} diff --git a/app/src/utils/env.ts b/app/src/utils/env.ts index d87c3f15..8d94d198 100644 --- a/app/src/utils/env.ts +++ b/app/src/utils/env.ts @@ -1,3 +1,13 @@ +import { updateFavicon } from "@/utils/dom.ts"; + +export let appName = + localStorage.getItem("app_name") || + import.meta.env.VITE_APP_NAME || + "Chat Nio"; +export let appLogo = + localStorage.getItem("app_logo") || + import.meta.env.VITE_APP_LOGO || + "/favicon.ico"; export const useDeeptrain = !!import.meta.env.VITE_USE_DEEPTRAIN; export const backendEndpoint = import.meta.env.VITE_BACKEND_ENDPOINT || "/api"; export const blobEndpoint = @@ -10,6 +20,9 @@ export const deeptrainAppName = import.meta.env.VITE_DEEPTRAIN_APP || "chatnio"; export const deeptrainApiEndpoint = import.meta.env.VITE_DEEPTRAIN_API_ENDPOINT || "https://api.deeptrain.net"; +document.title = appName; +updateFavicon(appLogo); + export function getDev(): boolean { /** * return if the current environment is development @@ -47,3 +60,29 @@ export function getTokenField(deploy: boolean): string { */ return deploy ? "token" : "token-dev"; } + +export function setAppName(name: string): void { + /** + * set the app name in localStorage + */ + name = name.trim(); + if (name.length === 0) return; + + localStorage.setItem("app_name", name); + appName = name; + + document.title = name; +} + +export function setAppLogo(logo: string): void { + /** + * set the app logo in localStorage + */ + logo = logo.trim(); + if (logo.length === 0) return; + + localStorage.setItem("app_logo", logo); + appLogo = logo; + + updateFavicon(logo); +} diff --git a/auth/auth.go b/auth/auth.go index fcf53b79..796c6bc7 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -4,6 +4,7 @@ import ( "chat/channel" "chat/globals" "chat/utils" + "context" "database/sql" "errors" "fmt" @@ -244,14 +245,31 @@ func Reset(c *gin.Context, form ResetForm) error { return errors.New("invalid email verification code") } + user := GetUserByEmail(db, email) + if user == nil { + return errors.New("cannot find user by email") + } + + if err := user.UpdatePassword(db, cache, password); err != nil { + return err + } + + cache.Del(c, fmt.Sprintf("nio:otp:%s", email)) + + return nil +} + +func (u *User) UpdatePassword(db *sql.DB, cache *redis.Client, password string) error { hash := utils.Sha2Encrypt(password) if _, err := db.Exec(` - UPDATE auth SET password = ? WHERE email = ? - `, hash, email); err != nil { + UPDATE auth SET password = ? WHERE id = ? + `, hash, u.ID); err != nil { return err } + cache.Del(context.Background(), fmt.Sprintf("nio:user:%s", u.Username)) + return nil } diff --git a/auth/struct.go b/auth/struct.go index ec21ced7..33b0560b 100644 --- a/auth/struct.go +++ b/auth/struct.go @@ -26,6 +26,22 @@ func GetUserById(db *sql.DB, id int64) *User { return &user } +func GetUserByName(db *sql.DB, username string) *User { + var user User + if err := db.QueryRow("SELECT id, username FROM auth WHERE username = ?", username).Scan(&user.ID, &user.Username); err != nil { + return nil + } + return &user +} + +func GetUserByEmail(db *sql.DB, email string) *User { + var user User + if err := db.QueryRow("SELECT id, username FROM auth WHERE email = ?", email).Scan(&user.ID, &user.Username); err != nil { + return nil + } + return &user +} + func GetId(db *sql.DB, user *User) int64 { if user == nil { return -1 diff --git a/channel/controller.go b/channel/controller.go index 981d9dae..a4c78b83 100644 --- a/channel/controller.go +++ b/channel/controller.go @@ -6,6 +6,10 @@ import ( "net/http" ) +func GetInfo(c *gin.Context) { + c.JSON(http.StatusOK, SystemInstance.AsInfo()) +} + func DeleteChannel(c *gin.Context) { id := c.Param("id") state := ConduitInstance.DeleteChannel(utils.ParseInt(id)) diff --git a/channel/router.go b/channel/router.go index 051a457b..68bee990 100644 --- a/channel/router.go +++ b/channel/router.go @@ -3,6 +3,8 @@ package channel import "github.com/gin-gonic/gin" func Register(app *gin.RouterGroup) { + app.GET("/info", GetInfo) + app.GET("/admin/channel/list", GetChannelList) app.POST("/admin/channel/create", CreateChannel) app.GET("/admin/channel/get/:id", GetChannel) diff --git a/channel/system.go b/channel/system.go index 9765c064..9379769d 100644 --- a/channel/system.go +++ b/channel/system.go @@ -5,7 +5,14 @@ import ( "github.com/spf13/viper" ) +type ApiInfo struct { + Title string `json:"title"` + Logo string `json:"logo"` +} + type generalState struct { + Title string `json:"title" mapstructure:"title"` + Logo string `json:"logo" mapstructure:"logo"` Backend string `json:"backend" mapstructure:"backend"` } @@ -47,6 +54,13 @@ func (c *SystemConfig) SaveConfig() error { return viper.WriteConfig() } +func (c *SystemConfig) AsInfo() ApiInfo { + return ApiInfo{ + Title: c.General.Title, + Logo: c.General.Logo, + } +} + func (c *SystemConfig) UpdateConfig(data *SystemConfig) error { c.General = data.General c.Mail = data.Mail diff --git a/cli/admin.go b/cli/admin.go new file mode 100644 index 00000000..f19859ba --- /dev/null +++ b/cli/admin.go @@ -0,0 +1,25 @@ +package cli + +import ( + "chat/admin" + "chat/connection" + "errors" +) + +func UpdateRootCommand(args []string) { + db := connection.ConnectMySQL() + cache := connection.ConnectRedis() + + if len(args) == 0 { + outputError(errors.New("invalid arguments, please provide a new root password")) + return + } + + password := args[0] + if err := admin.UpdateRootPassword(db, cache, password); err != nil { + outputError(err) + return + } + + outputInfo("root", "root password updated") +} diff --git a/cli/exec.go b/cli/exec.go index 523ff62b..1b4d6b50 100644 --- a/cli/exec.go +++ b/cli/exec.go @@ -10,17 +10,17 @@ func Run() bool { switch args[0] { case "help": Help() - return true case "invite": CreateInvitationCommand(param) - return true case "filter": FilterApiKeyCommand(param) - return true case "token": CreateTokenCommand(param) - return true + case "root": + UpdateRootCommand(param) default: return false } + + return true } diff --git a/cli/help.go b/cli/help.go index 307c3196..debd1420 100644 --- a/cli/help.go +++ b/cli/help.go @@ -7,6 +7,7 @@ Commands: - help - invite - token + - root ` func Help() { diff --git a/cli/parser.go b/cli/parser.go index 19833390..dfb073f6 100644 --- a/cli/parser.go +++ b/cli/parser.go @@ -63,7 +63,7 @@ func GetArgString(args []string, idx int) string { } func outputError(err error) { - fmt.Println(fmt.Sprintf("[cli] error: %s", err.Error())) + fmt.Println(fmt.Sprintf("\033[31m[cli] error: %s\033[0m", err.Error())) } func outputInfo(t, msg string) {