mirror of
https://github.com/coaidev/coai.git
synced 2025-05-30 02:10:25 +09:00
feat: support custom article/generation permission groups
This commit is contained in:
parent
c13c65ccbf
commit
831a20b13a
@ -2,6 +2,7 @@ package article
|
||||
|
||||
import (
|
||||
"chat/auth"
|
||||
"chat/globals"
|
||||
"chat/utils"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
@ -48,7 +49,7 @@ func GenerateAPI(c *gin.Context) {
|
||||
user := auth.ParseToken(c, form.Token)
|
||||
db := utils.GetDBFromContext(c)
|
||||
|
||||
if !(user != nil && user.IsSubscribe(db)) {
|
||||
if !auth.HitGroups(db, user, globals.ArticlePermissionGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,6 @@ import (
|
||||
"chat/utils"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@ -41,22 +40,15 @@ func GenerateAPI(c *gin.Context) {
|
||||
}
|
||||
|
||||
user := auth.ParseToken(c, form.Token)
|
||||
authenticated := user != nil
|
||||
|
||||
db := utils.GetDBFromContext(c)
|
||||
cache := utils.GetCacheFromContext(c)
|
||||
|
||||
id := auth.GetId(db, user)
|
||||
|
||||
if !utils.IncrWithLimit(cache,
|
||||
fmt.Sprintf(":generation:%s", utils.Multi[string](authenticated, strconv.FormatInt(id, 10), c.ClientIP())),
|
||||
1,
|
||||
30,
|
||||
3600,
|
||||
) {
|
||||
if !auth.HitGroups(db, user, globals.GenerationPermissionGroup) {
|
||||
conn.Send(globals.GenerationSegmentResponse{
|
||||
End: true,
|
||||
Error: "generation rate limit exceeded, the max generation rate is 30 per hour.",
|
||||
Message: "permission denied",
|
||||
Quota: 0,
|
||||
End: true,
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -76,7 +68,6 @@ func GenerateAPI(c *gin.Context) {
|
||||
auth.GetGroup(db, user),
|
||||
form.Model,
|
||||
form.Prompt,
|
||||
plan,
|
||||
func(buffer *utils.Buffer, data string) {
|
||||
instance = buffer
|
||||
conn.Send(globals.GenerationSegmentResponse{
|
||||
|
@ -6,10 +6,10 @@ import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func CreateGenerationWithCache(group, model, prompt string, enableReverse bool, hook func(buffer *utils.Buffer, data string)) (string, error) {
|
||||
func CreateGenerationWithCache(group, model, prompt string, hook func(buffer *utils.Buffer, data string)) (string, error) {
|
||||
hash, path := GetFolderByHash(model, prompt)
|
||||
if !utils.Exists(path) {
|
||||
if err := CreateGeneration(group, model, prompt, path, enableReverse, hook); err != nil {
|
||||
if err := CreateGeneration(group, model, prompt, path, hook); err != nil {
|
||||
globals.Info(fmt.Sprintf("[project] error during generation %s (model %s): %s", prompt, model, err.Error()))
|
||||
return "", fmt.Errorf("error during generate project: %s", err.Error())
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ type ProjectResult struct {
|
||||
Result map[string]interface{} `json:"result"`
|
||||
}
|
||||
|
||||
func CreateGeneration(group, model, prompt, path string, plan bool, hook func(buffer *utils.Buffer, data string)) error {
|
||||
func CreateGeneration(group, model, prompt, path string, hook func(buffer *utils.Buffer, data string)) error {
|
||||
message := GenerateMessage(prompt)
|
||||
buffer := utils.NewBuffer(model, message, channel.ChargeInstance.GetCharge(model))
|
||||
|
||||
|
@ -18,6 +18,8 @@ export type SiteInfo = {
|
||||
buy_link: string;
|
||||
mail: boolean;
|
||||
contact: string;
|
||||
article: string[];
|
||||
generation: string[];
|
||||
};
|
||||
|
||||
export async function getSiteInfo(): Promise<SiteInfo> {
|
||||
@ -35,6 +37,8 @@ export async function getSiteInfo(): Promise<SiteInfo> {
|
||||
buy_link: "",
|
||||
contact: "",
|
||||
mail: false,
|
||||
article: [],
|
||||
generation: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -50,10 +54,6 @@ export function syncSiteInfo() {
|
||||
setAnnouncement(info.announcement);
|
||||
setBuyLink(info.buy_link);
|
||||
|
||||
infoEvent.emit({
|
||||
mail: info.mail,
|
||||
contact: info.contact,
|
||||
} as InfoForm);
|
||||
console.log(info);
|
||||
infoEvent.emit(info as InfoForm);
|
||||
}, 25);
|
||||
}
|
||||
|
@ -37,11 +37,17 @@ export type SiteState = {
|
||||
contact: string;
|
||||
};
|
||||
|
||||
export type CommonState = {
|
||||
article: string[];
|
||||
generation: string[];
|
||||
};
|
||||
|
||||
export type SystemProps = {
|
||||
general: GeneralState;
|
||||
site: SiteState;
|
||||
mail: MailState;
|
||||
search: SearchState;
|
||||
common: CommonState;
|
||||
};
|
||||
|
||||
export type SystemResponse = CommonResponse & {
|
||||
@ -126,4 +132,8 @@ export const initialSystemState: SystemProps = {
|
||||
endpoint: "https://duckduckgo-api.vercel.app",
|
||||
query: 5,
|
||||
},
|
||||
common: {
|
||||
article: [],
|
||||
generation: [],
|
||||
},
|
||||
};
|
||||
|
@ -1,4 +1,11 @@
|
||||
import { getUniqueList } from "@/utils/base.ts";
|
||||
import {
|
||||
AnonymousType,
|
||||
BasicType,
|
||||
NormalType,
|
||||
ProType,
|
||||
StandardType,
|
||||
} from "@/utils/groups.ts";
|
||||
|
||||
export type Channel = {
|
||||
id: number;
|
||||
@ -213,11 +220,11 @@ export const channelModels: string[] = getUniqueList(
|
||||
);
|
||||
|
||||
export const channelGroups: string[] = [
|
||||
"anonymous",
|
||||
"normal",
|
||||
"basic",
|
||||
"standard",
|
||||
"pro",
|
||||
AnonymousType,
|
||||
NormalType,
|
||||
BasicType,
|
||||
StandardType,
|
||||
ProType,
|
||||
];
|
||||
|
||||
export function getChannelInfo(type?: string): ChannelInfo {
|
||||
|
@ -40,12 +40,7 @@ function FileViewer({ filename, content, children, asChild }: FileViewerProps) {
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className={`file-viewer-action`}>
|
||||
<ToggleGroup
|
||||
variant={`outline`}
|
||||
type={`single`}
|
||||
value={renderedType}
|
||||
onValueChange={console.log}
|
||||
>
|
||||
<ToggleGroup variant={`outline`} type={`single`} value={renderedType}>
|
||||
<ToggleGroupItem
|
||||
value={viewerType.Text}
|
||||
onClick={() => setRenderedType(viewerType.Text)}
|
||||
|
@ -15,8 +15,13 @@ import {
|
||||
import { getLanguage } from "@/i18n.ts";
|
||||
import { selectAuthenticated } from "@/store/auth.ts";
|
||||
import { appLogo } from "@/conf/env.ts";
|
||||
import { infoContactSelector } from "@/store/info.ts";
|
||||
import {
|
||||
infoArticleSelector,
|
||||
infoContactSelector,
|
||||
infoGenerationSelector,
|
||||
} from "@/store/info.ts";
|
||||
import Markdown from "@/components/Markdown.tsx";
|
||||
import { hitGroup } from "@/utils/groups.ts";
|
||||
|
||||
function ChatSpace() {
|
||||
const [open, setOpen] = useState(false);
|
||||
@ -27,6 +32,12 @@ function ChatSpace() {
|
||||
const cn = getLanguage() === "cn";
|
||||
const auth = useSelector(selectAuthenticated);
|
||||
|
||||
const generationGroup = useSelector(infoGenerationSelector);
|
||||
const generation = hitGroup(generationGroup);
|
||||
|
||||
const articleGroup = useSelector(infoArticleSelector);
|
||||
const article = hitGroup(articleGroup);
|
||||
|
||||
return (
|
||||
<div className={`chat-product`}>
|
||||
<img
|
||||
@ -42,18 +53,25 @@ function ChatSpace() {
|
||||
<ChevronRight className={`h-4 w-4 ml-2`} />
|
||||
</Button>
|
||||
)}
|
||||
{subscription && (
|
||||
|
||||
{article && (
|
||||
<Button variant={`outline`} onClick={() => router.navigate("/article")}>
|
||||
<Newspaper className={`h-4 w-4 mr-1.5`} />
|
||||
{t("article.title")}
|
||||
<ChevronRight className={`h-4 w-4 ml-2`} />
|
||||
</Button>
|
||||
)}
|
||||
<Button variant={`outline`} onClick={() => router.navigate("/generate")}>
|
||||
<FolderKanban className={`h-4 w-4 mr-1.5`} />
|
||||
{t("generate.title")}
|
||||
<ChevronRight className={`h-4 w-4 ml-2`} />
|
||||
</Button>
|
||||
|
||||
{generation && (
|
||||
<Button
|
||||
variant={`outline`}
|
||||
onClick={() => router.navigate("/generate")}
|
||||
>
|
||||
<FolderKanban className={`h-4 w-4 mr-1.5`} />
|
||||
{t("generate.title")}
|
||||
<ChevronRight className={`h-4 w-4 ml-2`} />
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogContent className={`flex-dialog`}>
|
||||
|
@ -50,6 +50,8 @@ export function MultiCombobox({
|
||||
return [...set];
|
||||
}, [list]);
|
||||
|
||||
const v = value ?? [];
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
@ -61,7 +63,7 @@ export function MultiCombobox({
|
||||
disabled={disabled}
|
||||
>
|
||||
<Check className="mr-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
{placeholder ?? `${value.length} Items Selected`}
|
||||
{placeholder ?? `${v.length} Items Selected`}
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
@ -75,17 +77,17 @@ export function MultiCombobox({
|
||||
key={key}
|
||||
value={key}
|
||||
onSelect={(current) => {
|
||||
if (value.includes(current)) {
|
||||
onChange(value.filter((item) => item !== current));
|
||||
if (v.includes(current)) {
|
||||
onChange(v.filter((item) => item !== current));
|
||||
} else {
|
||||
onChange([...value, current]);
|
||||
onChange([...v, current]);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4",
|
||||
value.includes(key) ? "opacity-100" : "opacity-0",
|
||||
v.includes(key) ? "opacity-100" : "opacity-0",
|
||||
)}
|
||||
/>
|
||||
{listTranslate ? t(`${listTranslate}.${key}`) : key}
|
||||
|
@ -3,6 +3,8 @@ import { EventCommitter } from "@/events/struct.ts";
|
||||
export type InfoForm = {
|
||||
mail: boolean;
|
||||
contact: string;
|
||||
article: string[];
|
||||
generation: string[];
|
||||
};
|
||||
|
||||
export const infoEvent = new EventCommitter<InfoForm>({
|
||||
|
@ -583,7 +583,8 @@
|
||||
"normal": "普通用户",
|
||||
"basic": "基础版订阅用户",
|
||||
"standard": "标准版订阅用户",
|
||||
"pro": "专业版订阅用户"
|
||||
"pro": "专业版订阅用户",
|
||||
"admin": "管理员用户"
|
||||
}
|
||||
},
|
||||
"charge": {
|
||||
@ -622,6 +623,7 @@
|
||||
"search": "联网搜索",
|
||||
"site": "站点设置",
|
||||
"mail": "SMTP 发件设置",
|
||||
"common": "通用设置",
|
||||
"save": "保存",
|
||||
"updateRoot": "修改 Root 密码",
|
||||
"updateRootTip": "请谨慎操作,修改 Root 密码后,您需要重新登录。",
|
||||
@ -661,7 +663,12 @@
|
||||
"announcement": "站点公告",
|
||||
"announcementPlaceholder": "请输入站点公告 (支持 Markdown / HTML 格式)",
|
||||
"contact": "联系信息",
|
||||
"contactPlaceholder": "请输入联系信息 (支持 Markdown / HTML 格式)"
|
||||
"contactPlaceholder": "请输入联系信息 (支持 Markdown / HTML 格式)",
|
||||
"article": "批量文章生成功能分组",
|
||||
"articleTip": "批量文章生成功能分组,勾选后当前用户组可使用批量文章生成功能",
|
||||
"generate": "AI 项目生成器分组",
|
||||
"generateTip": "AI 项目生成器分组,勾选后当前用户组可使用 AI 项目生成器",
|
||||
"groupPlaceholder": "已选 {{length}} 个分组"
|
||||
},
|
||||
"logger": {
|
||||
"title": "服务日志",
|
||||
|
@ -422,7 +422,8 @@
|
||||
"normal": "Normal",
|
||||
"basic": "Basic Subscribers",
|
||||
"standard": "Standard Subscribers",
|
||||
"pro": "Pro Subscribers"
|
||||
"pro": "Pro Subscribers",
|
||||
"admin": "Admin user"
|
||||
},
|
||||
"joint": "Dock upstream",
|
||||
"joint-endpoint": "Upstream address",
|
||||
@ -509,7 +510,13 @@
|
||||
"buyLinkPlaceholder": "Please enter the card secret purchase link, leave blank to not show the purchase button",
|
||||
"mailConfNotValid": "SMTP send parameters are not configured correctly, mailbox verification is disabled",
|
||||
"contact": "Contact Information",
|
||||
"contactPlaceholder": "Please enter contact information (Markdown/HTML supported)"
|
||||
"contactPlaceholder": "Please enter contact information (Markdown/HTML supported)",
|
||||
"common": "General Settings",
|
||||
"article": "Batch Post Generation Feature Grouping",
|
||||
"articleTip": "Batch post generation function grouping, after checking the current user group can use batch post generation function",
|
||||
"generate": "AI Project Builder Grouping",
|
||||
"generateTip": "AI project generator grouping, after checking the current user group can use AI project generator",
|
||||
"groupPlaceholder": "{{length}} groups selected"
|
||||
},
|
||||
"user": "User Management",
|
||||
"invitation-code": "Invitation Code",
|
||||
|
@ -422,7 +422,8 @@
|
||||
"normal": "一般ユーザー",
|
||||
"basic": "ベーシックサブスクライバー",
|
||||
"standard": "標準サブスクライバー",
|
||||
"pro": "Pro Subscribers"
|
||||
"pro": "Pro Subscribers",
|
||||
"admin": "管理者ユーザー"
|
||||
},
|
||||
"joint": "上流にドッキング",
|
||||
"joint-endpoint": "アップストリームアドレス",
|
||||
@ -509,7 +510,13 @@
|
||||
"buyLinkPlaceholder": "カードシークレット購入リンクを入力してください。購入ボタンを表示しない場合は空白のままにしてください",
|
||||
"mailConfNotValid": "SMTP送信パラメータが正しく設定されていません。メールボックスの検証が無効になっています",
|
||||
"contact": "コンタクト&インフォメーション",
|
||||
"contactPlaceholder": "連絡先情報を入力してください( Markdown/HTML対応)"
|
||||
"contactPlaceholder": "連絡先情報を入力してください( Markdown/HTML対応)",
|
||||
"common": "基本設定",
|
||||
"article": "一括ポストジェネレーションフィーチャーグループ",
|
||||
"articleTip": "バッチポストジェネレーション機能のグループ化、現在のユーザーグループを確認した後、バッチポストジェネレーション機能を使用することができます",
|
||||
"generate": "AIプロジェクトビルダーグループ",
|
||||
"generateTip": "AIプロジェクトジェネレータグループ、現在のユーザーグループを確認した後、AIプロジェクトジェネレータを使用することができます",
|
||||
"groupPlaceholder": "{{length}}グループが選択されました"
|
||||
},
|
||||
"user": "ユーザー管理",
|
||||
"invitation-code": "招待コード",
|
||||
|
@ -422,7 +422,8 @@
|
||||
"normal": "обычный пользователь",
|
||||
"basic": "Базовые подписчики",
|
||||
"standard": "Стандартные подписчики",
|
||||
"pro": "Подписчики Pro"
|
||||
"pro": "Подписчики Pro",
|
||||
"admin": "Пользователь-администратор"
|
||||
},
|
||||
"joint": "Док-станция выше по течению",
|
||||
"joint-endpoint": "Адрес выше по потоку",
|
||||
@ -509,7 +510,13 @@
|
||||
"buyLinkPlaceholder": "Введите ссылку на секретную покупку карты, оставьте поле пустым, чтобы не показывать кнопку покупки",
|
||||
"mailConfNotValid": "Параметры отправки SMTP настроены неправильно, проверка почтового ящика отключена",
|
||||
"contact": "shops|Контактные данные",
|
||||
"contactPlaceholder": "Введите контактную информацию (поддерживается Markdown/HTML)"
|
||||
"contactPlaceholder": "Введите контактную информацию (поддерживается Markdown/HTML)",
|
||||
"common": "Основные параметры",
|
||||
"article": "Группировка функций генерации пакетной записи",
|
||||
"articleTip": "Группировка функций пакетного пост-генерации, после проверки текущей группы пользователей можно использовать функцию пакетного пост-генерации",
|
||||
"generate": "Группировка конструкторов ИИ-проектов",
|
||||
"generateTip": "Группировка генераторов ИИ-проектов, после проверки текущей группы пользователей можно использовать генератор ИИ-проектов",
|
||||
"groupPlaceholder": "Выбрано групп: {{length}}"
|
||||
},
|
||||
"user": "Управление пользователями",
|
||||
"invitation-code": "Код приглашения",
|
||||
|
@ -18,6 +18,7 @@ import { useMemo, useReducer, useState } from "react";
|
||||
import { formReducer } from "@/utils/form.ts";
|
||||
import { NumberInput } from "@/components/ui/number-input.tsx";
|
||||
import {
|
||||
CommonState,
|
||||
commonWhiteList,
|
||||
GeneralState,
|
||||
getConfig,
|
||||
@ -49,6 +50,7 @@ import Tips from "@/components/Tips.tsx";
|
||||
import { cn } from "@/components/ui/lib/utils.ts";
|
||||
import { Switch } from "@/components/ui/switch.tsx";
|
||||
import { MultiCombobox } from "@/components/ui/multi-combobox.tsx";
|
||||
import { allGroups } from "@/utils/groups.ts";
|
||||
|
||||
type CompProps<T> = {
|
||||
data: T;
|
||||
@ -464,12 +466,9 @@ function Site({ data, dispatch, onChange }: CompProps<SiteState>) {
|
||||
isCollapsed={true}
|
||||
>
|
||||
<ParagraphItem>
|
||||
<Label>
|
||||
<Label className={`flex flex-row items-center`}>
|
||||
{t("admin.system.quota")}
|
||||
<Tips
|
||||
className={`inline-block`}
|
||||
content={t("admin.system.quotaTip")}
|
||||
/>
|
||||
<Tips content={t("admin.system.quotaTip")} />
|
||||
</Label>
|
||||
<NumberInput
|
||||
value={data.quota}
|
||||
@ -535,6 +534,63 @@ function Site({ data, dispatch, onChange }: CompProps<SiteState>) {
|
||||
);
|
||||
}
|
||||
|
||||
function Common({ data, dispatch, onChange }: CompProps<CommonState>) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Paragraph
|
||||
title={t("admin.system.common")}
|
||||
configParagraph={true}
|
||||
isCollapsed={true}
|
||||
>
|
||||
<ParagraphItem>
|
||||
<Label className={`flex flex-row items-center`}>
|
||||
{t("admin.system.article")}
|
||||
<Tips content={t("admin.system.articleTip")} />
|
||||
</Label>
|
||||
<MultiCombobox
|
||||
value={data.article}
|
||||
onChange={(value) => {
|
||||
dispatch({ type: "update:common.article", value });
|
||||
}}
|
||||
list={allGroups}
|
||||
listTranslate={`admin.channels.groups`}
|
||||
placeholder={t("admin.system.groupPlaceholder", {
|
||||
length: (data.article ?? []).length,
|
||||
})}
|
||||
/>
|
||||
</ParagraphItem>
|
||||
<ParagraphItem>
|
||||
<Label className={`flex flex-row items-center`}>
|
||||
{t("admin.system.generate")}
|
||||
<Tips content={t("admin.system.generateTip")} />
|
||||
</Label>
|
||||
<MultiCombobox
|
||||
value={data.generation}
|
||||
onChange={(value) => {
|
||||
dispatch({ type: "update:common.generation", value });
|
||||
}}
|
||||
list={allGroups}
|
||||
listTranslate={`admin.channels.groups`}
|
||||
placeholder={t("admin.system.groupPlaceholder", {
|
||||
length: (data.generation ?? []).length,
|
||||
})}
|
||||
/>
|
||||
</ParagraphItem>
|
||||
<ParagraphFooter>
|
||||
<div className={`grow`} />
|
||||
<Button
|
||||
size={`sm`}
|
||||
loading={true}
|
||||
onClick={async () => await onChange()}
|
||||
>
|
||||
{t("admin.system.save")}
|
||||
</Button>
|
||||
</ParagraphFooter>
|
||||
</Paragraph>
|
||||
);
|
||||
}
|
||||
|
||||
function Search({ data, dispatch, onChange }: CompProps<SearchState>) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@ -624,6 +680,7 @@ function System() {
|
||||
<Site data={data.site} dispatch={setData} onChange={doSaving} />
|
||||
<Mail data={data.mail} dispatch={setData} onChange={doSaving} />
|
||||
<Search data={data.search} dispatch={setData} onChange={doSaving} />
|
||||
<Common data={data.common} dispatch={setData} onChange={doSaving} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
@ -2,8 +2,10 @@ import { createSlice } from "@reduxjs/toolkit";
|
||||
import { InfoForm } from "@/events/info.ts";
|
||||
import { RootState } from "@/store/index.ts";
|
||||
import {
|
||||
getArrayMemory,
|
||||
getBooleanMemory,
|
||||
getMemory,
|
||||
setArrayMemory,
|
||||
setBooleanMemory,
|
||||
setMemory,
|
||||
} from "@/utils/memory.ts";
|
||||
@ -13,15 +15,21 @@ export const infoSlice = createSlice({
|
||||
initialState: {
|
||||
mail: getBooleanMemory("mail", false),
|
||||
contact: getMemory("contact"),
|
||||
article: getArrayMemory("article"),
|
||||
generation: getArrayMemory("generation"),
|
||||
} as InfoForm,
|
||||
reducers: {
|
||||
setForm: (state, action) => {
|
||||
const form = action.payload as InfoForm;
|
||||
state.mail = form.mail ?? false;
|
||||
state.contact = form.contact ?? "";
|
||||
state.article = form.article ?? [];
|
||||
state.generation = form.generation ?? [];
|
||||
|
||||
setBooleanMemory("mail", state.mail);
|
||||
setMemory("contact", state.contact);
|
||||
setArrayMemory("article", state.article);
|
||||
setArrayMemory("generation", state.generation);
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -34,3 +42,7 @@ export const infoDataSelector = (state: RootState): InfoForm => state.info;
|
||||
export const infoMailSelector = (state: RootState): boolean => state.info.mail;
|
||||
export const infoContactSelector = (state: RootState): string =>
|
||||
state.info.contact;
|
||||
export const infoArticleSelector = (state: RootState): string[] =>
|
||||
state.info.article;
|
||||
export const infoGenerationSelector = (state: RootState): string[] =>
|
||||
state.info.generation;
|
||||
|
49
app/src/utils/groups.ts
Normal file
49
app/src/utils/groups.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import { useSelector } from "react-redux";
|
||||
import { selectAdmin, selectAuthenticated } from "@/store/auth.ts";
|
||||
import { levelSelector } from "@/store/subscription.ts";
|
||||
import { useMemo } from "react";
|
||||
|
||||
export const AnonymousType = "anonymous";
|
||||
export const NormalType = "normal";
|
||||
export const BasicType = "basic";
|
||||
export const StandardType = "standard";
|
||||
export const ProType = "pro";
|
||||
export const AdminType = "admin";
|
||||
|
||||
export const allGroups: string[] = [
|
||||
AnonymousType,
|
||||
NormalType,
|
||||
BasicType,
|
||||
StandardType,
|
||||
ProType,
|
||||
AdminType,
|
||||
];
|
||||
|
||||
export function useGroup(): string {
|
||||
const auth = useSelector(selectAuthenticated);
|
||||
const level = useSelector(levelSelector);
|
||||
|
||||
return useMemo(() => {
|
||||
if (!auth) return AnonymousType;
|
||||
switch (level) {
|
||||
case 1:
|
||||
return BasicType;
|
||||
case 2:
|
||||
return StandardType;
|
||||
case 3:
|
||||
return ProType;
|
||||
default:
|
||||
return NormalType;
|
||||
}
|
||||
}, [auth, level]);
|
||||
}
|
||||
|
||||
export function hitGroup(group: string[]): boolean {
|
||||
const current = useGroup();
|
||||
const admin = useSelector(selectAdmin);
|
||||
|
||||
return useMemo(() => {
|
||||
if (group.includes(AdminType) && admin) return true;
|
||||
return group.includes(current);
|
||||
}, [group, current, admin]);
|
||||
}
|
@ -7,7 +7,7 @@ export function useEffectAsync<T>(effect: () => Promise<T>, deps?: any[]) {
|
||||
* @example
|
||||
* useEffectAsync(async () => {
|
||||
* const result = await fetch("https://api.example.com");
|
||||
* console.log(result);
|
||||
* console.debug(result);
|
||||
* }, []);
|
||||
*/
|
||||
|
||||
|
@ -2,6 +2,7 @@ package auth
|
||||
|
||||
import (
|
||||
"chat/globals"
|
||||
"chat/utils"
|
||||
"database/sql"
|
||||
"time"
|
||||
)
|
||||
@ -149,3 +150,22 @@ func GetGroup(db *sql.DB, user *User) string {
|
||||
return globals.NormalType
|
||||
}
|
||||
}
|
||||
|
||||
func HitGroup(db *sql.DB, user *User, group string) bool {
|
||||
if group == globals.AdminType {
|
||||
return user != nil && user.IsAdmin(db)
|
||||
}
|
||||
|
||||
return GetGroup(db, user) == group
|
||||
}
|
||||
|
||||
func HitGroups(db *sql.DB, user *User, groups []string) bool {
|
||||
if utils.Contains(globals.AdminType, groups) {
|
||||
if user != nil && user.IsAdmin(db) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
group := GetGroup(db, user)
|
||||
return utils.Contains(group, groups)
|
||||
}
|
||||
|
@ -9,14 +9,16 @@ import (
|
||||
)
|
||||
|
||||
type ApiInfo struct {
|
||||
Title string `json:"title"`
|
||||
Logo string `json:"logo"`
|
||||
File string `json:"file"`
|
||||
Docs string `json:"docs"`
|
||||
Announcement string `json:"announcement"`
|
||||
BuyLink string `json:"buy_link"`
|
||||
Contact string `json:"contact"`
|
||||
Mail bool `json:"mail"`
|
||||
Title string `json:"title"`
|
||||
Logo string `json:"logo"`
|
||||
File string `json:"file"`
|
||||
Docs string `json:"docs"`
|
||||
Announcement string `json:"announcement"`
|
||||
BuyLink string `json:"buy_link"`
|
||||
Contact string `json:"contact"`
|
||||
Mail bool `json:"mail"`
|
||||
Article []string `json:"article"`
|
||||
Generation []string `json:"generation"`
|
||||
}
|
||||
|
||||
type generalState struct {
|
||||
@ -54,11 +56,18 @@ type searchState struct {
|
||||
Query int `json:"query" mapstructure:"query"`
|
||||
}
|
||||
|
||||
type commonState struct {
|
||||
Cache []string `json:"cache" mapstructure:"cache"`
|
||||
Article []string `json:"article" mapstructure:"article"`
|
||||
Generation []string `json:"generation" mapstructure:"generation"`
|
||||
}
|
||||
|
||||
type SystemConfig struct {
|
||||
General generalState `json:"general" mapstructure:"general"`
|
||||
Site siteState `json:"site" mapstructure:"site"`
|
||||
Mail mailState `json:"mail" mapstructure:"mail"`
|
||||
Search searchState `json:"search" mapstructure:"search"`
|
||||
Common commonState `json:"common" mapstructure:"common"`
|
||||
}
|
||||
|
||||
func NewSystemConfig() *SystemConfig {
|
||||
@ -73,6 +82,9 @@ func NewSystemConfig() *SystemConfig {
|
||||
|
||||
func (c *SystemConfig) Load() {
|
||||
globals.NotifyUrl = c.GetBackend()
|
||||
|
||||
globals.ArticlePermissionGroup = c.Common.Article
|
||||
globals.GenerationPermissionGroup = c.Common.Generation
|
||||
}
|
||||
|
||||
func (c *SystemConfig) SaveConfig() error {
|
||||
@ -92,6 +104,8 @@ func (c *SystemConfig) AsInfo() ApiInfo {
|
||||
Contact: c.Site.Contact,
|
||||
BuyLink: c.Site.BuyLink,
|
||||
Mail: c.IsMailValid(),
|
||||
Article: c.Common.Article,
|
||||
Generation: c.Common.Generation,
|
||||
}
|
||||
}
|
||||
|
||||
@ -100,6 +114,7 @@ func (c *SystemConfig) UpdateConfig(data *SystemConfig) error {
|
||||
c.Site = data.Site
|
||||
c.Mail = data.Mail
|
||||
c.Search = data.Search
|
||||
c.Common = data.Common
|
||||
|
||||
return c.SaveConfig()
|
||||
}
|
||||
|
@ -37,4 +37,5 @@ const (
|
||||
BasicType = "basic" // basic subscription
|
||||
StandardType = "standard" // standard subscription
|
||||
ProType = "pro" // pro subscription
|
||||
AdminType = "admin"
|
||||
)
|
||||
|
@ -17,6 +17,8 @@ var AllowedOrigins = []string{
|
||||
}
|
||||
|
||||
var NotifyUrl = ""
|
||||
var ArticlePermissionGroup []string
|
||||
var GenerationPermissionGroup []string
|
||||
|
||||
func OriginIsAllowed(uri string) bool {
|
||||
instance, _ := url.Parse(uri)
|
||||
|
Loading…
Reference in New Issue
Block a user