Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion document/content/self-host/config/env.en.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,6 @@ These variables are mainly read by `pro/admin`. Admin also uses the shared App/A
| `BING_OAUTH_CLIENT_SECRET` | Empty | Bing OAuth client secret. |
| `BING_OAUTH_REFRESH_TOKEN` | Empty | Bing OAuth refresh token. |
| `SHOW_WECOM_CONFIG` | `false` | Whether WeCom configuration is shown. |
| `WECOM_DEV` | `false` | Development mode switch for WeCom Pay. |

## Code Sandbox Variables

Expand Down
1 change: 0 additions & 1 deletion document/content/self-host/config/env.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,6 @@ DOC2X_KEY=your-api-key
| `BING_OAUTH_CLIENT_SECRET` | 空 | Bing OAuth Client Secret。 |
| `BING_OAUTH_REFRESH_TOKEN` | 空 | Bing OAuth Refresh Token。 |
| `SHOW_WECOM_CONFIG` | `false` | 是否展示企业微信相关配置。 |
| `WECOM_DEV` | `false` | 企业微信支付相关开发模式开关。 |

## Code Sandbox 变量

Expand Down
7 changes: 2 additions & 5 deletions packages/global/support/user/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@ export const UserTagsSchema = z.enum(['wecom']);
export const UserTagsEnum = UserTagsSchema.enum;
export type UserTagsType = z.infer<typeof UserTagsSchema>;

export type UserMetaType = {
isActivatedWecomLicense?: boolean;
};
export type UserMetaType = Record<string, unknown>;

export type UserModelSchema = {
_id: string;
Expand Down Expand Up @@ -58,8 +56,7 @@ export const TeamMetaSchema = z.object({
wecom: z
.object({
permanentCode: z.string(),
corpId: z.string(),
licenseCapacity: z.int().min(0).default(0)
corpId: z.string()
})
.optional()
});
Expand Down
16 changes: 1 addition & 15 deletions packages/global/support/wallet/sub/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,6 @@ export const TeamStandardSubPlanItemSchema = z.object({
// Active
annualBonusPoints: z.int().optional(), // 年度赠送积分

/** 企微设置 */
wecom: z
.object({
price: z.number().describe('企微价格'),
points: z.number().describe('企微积分')
})
.nullish(),

/** @deprecated */
pointPrice: z.number().optional()
});
Expand Down Expand Up @@ -103,13 +95,7 @@ const TeamStandardSubPlanItemInputSchema = TeamStandardSubPlanItemSchema.extend(
maxUploadFileSize: configOptionalIntegerInputSchema,
maxUploadFileCount: configOptionalIntegerInputSchema,
annualBonusPoints: configOptionalIntegerInputSchema,
pointPrice: configOptionalNumberInputSchema,
wecom: z
.object({
price: NumSchema.nonnegative(),
points: NumSchema.nonnegative()
})
.nullish()
pointPrice: configOptionalNumberInputSchema
});

const CustomStandardSubPlanItemInputSchema = z.preprocess((value) => {
Expand Down
9 changes: 9 additions & 0 deletions packages/service/support/user/team/teamMemberSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,15 @@ defineIndex(TeamMemberSchema, {
key: { userId: 1, teamId: 1 },
options: { unique: true, background: true }
});
defineIndex(TeamMemberSchema, {
key: { teamId: 1, wecomUserId: 1 },
options: {
unique: true,
partialFilterExpression: { wecomUserId: { $exists: true } },
background: true
},
deprecated: true
});

export const MongoTeamMember = getMongoModel<TeamMemberType>(
TeamMemberCollectionName,
Expand Down
55 changes: 12 additions & 43 deletions packages/service/support/wallet/sub/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import type {
} from '@fastgpt/global/support/wallet/sub/type';
import dayjs from 'dayjs';
import { type ClientSession } from '../../../common/mongo';
import { addMonths, addDays } from 'date-fns';
import { addMonths } from 'date-fns';
import { readFromSecondary } from '../../../common/mongo/utils';
import { TeamPointCache, teamQpmCache } from '@fastgpt/dal/redis/caches';
import { getLogger, LogCategories } from '../../../common/logger';
Expand Down Expand Up @@ -65,7 +65,6 @@ export const buildStandardPlan = (
priceDescription: standardConstants.priceDescription,
customFormUrl: standardConstants.customFormUrl,
customDescriptions: standardConstants.customDescriptions,
wecom: standardConstants.wecom,
maxTeamMember: standard?.maxTeamMember ?? standardConstants.maxTeamMember,
maxAppAmount: standard?.maxApp ?? standardConstants.maxAppAmount,
maxDatasetAmount: standard?.maxDataset ?? standardConstants.maxDatasetAmount,
Expand All @@ -85,44 +84,19 @@ export const buildStandardPlan = (

export const initTeamFreePlan = async ({
teamId,
isWecomTeam = false,
session
}: {
teamId: string;
isWecomTeam?: boolean;
session?: ClientSession;
}) => {
const freePoints = isWecomTeam
? Math.round((global.subPlans?.standard?.basic?.totalPoints ?? 4000) / 2)
: global?.subPlans?.standard?.[StandardSubLevelEnum.free]?.totalPoints || 100;
const freePoints = global?.subPlans?.standard?.[StandardSubLevelEnum.free]?.totalPoints || 100;

const freePlan = await MongoTeamSub.findOne({
teamId,
type: SubTypeEnum.standard,
currentSubLevel: StandardSubLevelEnum.free
});

// Get basic plan config for wecom mode
const specialConfig: Record<string, any> | null = (() => {
const config = global?.subPlans?.standard?.[StandardSubLevelEnum.basic];
if (isWecomTeam && config) {
return {
maxTeamMember: config.maxTeamMember,
maxApp: config.maxAppAmount,
maxDataset: config.maxDatasetAmount,
requestsPerMinute: config.requestsPerMinute,
chatHistoryStoreDuration: config.chatHistoryStoreDuration,
maxDatasetSize: config.maxDatasetSize,
websiteSyncPerDataset: config.websiteSyncPerDataset,
appRegistrationCount: config.appRegistrationCount,
auditLogStoreDuration: config.auditLogStoreDuration,
ticketResponseTime: config.ticketResponseTime,
customDomain: config.customDomain
} as TeamSubSchemaType;
}
return null;
})();

// Reset one month free plan
if (freePlan) {
freePlan.currentMode = SubModeEnum.month;
Expand All @@ -139,13 +113,6 @@ export const initTeamFreePlan = async ({
? freePlan.surplusPoints + freePoints
: freePoints;

// Apply basic plan config for wecom, but with limited points and dataset size
if (specialConfig) {
for (const key in specialConfig) {
(freePlan as any)[key] = specialConfig[key];
}
}

return freePlan.save({ session });
}

Expand All @@ -157,32 +124,34 @@ export const initTeamFreePlan = async ({
currentMode: SubModeEnum.month,
nextMode: SubModeEnum.month,
startTime: new Date(),
expiredTime: isWecomTeam ? addDays(new Date(), 15) : addMonths(new Date(), 1),
expiredTime: addMonths(new Date(), 1),

currentSubLevel: StandardSubLevelEnum.free,
nextSubLevel: StandardSubLevelEnum.free,

totalPoints: freePoints,
surplusPoints: freePoints,
...(specialConfig && specialConfig)
surplusPoints: freePoints
}
],
{ session, ordered: true }
);
};

// 获取团队标准套餐
export const getTeamStandPlan = async ({ teamId }: { teamId: string }) => {
const standardPlans = global.subPlans?.standard;
export const getTeamStandPlan = async ({
teamId,
readFromPrimary = false
}: {
teamId: string;
readFromPrimary?: boolean;
}) => {
const plans = await MongoTeamSub.find(
{
teamId,
type: SubTypeEnum.standard
},
undefined,
{
...readFromSecondary
}
readFromPrimary ? { readPreference: 'primary' } : { ...readFromSecondary }
).lean();
sortStandPlans(plans);

Expand Down
40 changes: 40 additions & 0 deletions packages/service/test/common/mongo/indexManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,46 @@ describe('MongoIndexManager.syncModelIndexes', () => {
});
});

it('removes deprecated partial compound indexes', async () => {
const schema = new Schema(
{
teamId: String,
wecomUserId: String
},
{ autoIndex: false }
);
defineDeprecatedTestIndexes(schema, [
{
indexName: 'teamId_1_wecomUserId_1',
key: { teamId: 1, wecomUserId: 1 },
options: {
unique: true,
partialFilterExpression: { wecomUserId: { $exists: true } }
}
}
]);
const model = createModel({ schema });
await model.collection.createIndex(
{ teamId: 1, wecomUserId: 1 },
{
name: 'teamId_1_wecomUserId_1',
unique: true,
partialFilterExpression: { wecomUserId: { $exists: true } }
}
);

const result = await MongoIndexManager.syncModelIndexes({ model, logger });

expect(await getIndexNames(model)).not.toContain('teamId_1_wecomUserId_1');
expect(result.cleanupReport.items).toEqual([
expect.objectContaining({
action: 'drop',
applied: true,
indexName: 'teamId_1_wecomUserId_1'
})
]);
});

it('does not delete schema-external indexes when the Schema has no deprecated declarations', async () => {
const schema = new Schema(
{ currentField: String, customerField: String },
Expand Down
24 changes: 24 additions & 0 deletions packages/service/test/support/user/team/teamMemberSchema.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { describe, expect, it } from 'vitest';
import { MongoTeamMember } from '@fastgpt/service/support/user/team/teamMemberSchema';
import { getDeprecatedIndexes } from '@fastgpt/service/common/mongo/schemaIndexes';

describe('TeamMember schema', () => {
it('does not declare a duplicated WeCom user identity or active index', () => {
expect(MongoTeamMember.schema.path('wecomUserId')).toBeUndefined();
expect(MongoTeamMember.schema.indexes()).not.toContainEqual([
{ teamId: 1, wecomUserId: 1 },
expect.any(Object)
]);
});

it('registers the removed WeCom index for safe cleanup', () => {
expect(getDeprecatedIndexes(MongoTeamMember.schema)).toContainEqual({
indexName: 'teamId_1_wecomUserId_1',
key: { teamId: 1, wecomUserId: 1 },
options: {
unique: true,
partialFilterExpression: { wecomUserId: { $exists: true } }
}
});
});
});
32 changes: 22 additions & 10 deletions packages/service/test/support/wallet/sub/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
} from '@fastgpt/service/support/wallet/sub/utils';
import { MongoTeamSub } from '@fastgpt/service/support/wallet/sub/schema';
import { redisCacheAdapter } from '@fastgpt/dal/redis/adapter';
import { addMonths } from 'date-fns';

// Valid ObjectId for testing
const mockTeamId = '507f1f77bcf86cd799439011';
Expand Down Expand Up @@ -444,21 +445,11 @@ describe('buildStandardPlan', () => {
expect(result.customDescriptions).toEqual(['特性A', '特性B']);
});

it('wecom 取自 standardConstants', () => {
const constants: TeamStandardSubPlanItemType = {
...baseConstants,
wecom: { price: 9.9, points: 500 }
};
const result = buildStandardPlan(baseStandard, constants);
expect(result.wecom).toEqual({ price: 9.9, points: 500 });
});

it('standardConstants 展示字段为 undefined 时结果也为 undefined', () => {
const result = buildStandardPlan(baseStandard, baseConstants);
expect(result.priceDescription).toBeUndefined();
expect(result.customFormUrl).toBeUndefined();
expect(result.customDescriptions).toBeUndefined();
expect(result.wecom).toBeUndefined();
});
});

Expand Down Expand Up @@ -691,6 +682,9 @@ describe('initTeamFreePlan', () => {

expect(MongoTeamSub.findOne).toHaveBeenCalled();
expect(MongoTeamSub.create).toHaveBeenCalled();
const [createdPlan] = (MongoTeamSub.create as any).mock.calls[0][0];
expect(createdPlan.expiredTime).toEqual(addMonths(createdPlan.startTime, 1));
expect(createdPlan.maxTeamMember).toBeUndefined();
expect(result).toEqual([mockCreatedPlan]);
});

Expand Down Expand Up @@ -790,6 +784,24 @@ describe('getTeamStandPlan', () => {
expect(result[SubTypeEnum.standard]?.name).toBe('Basic Plan');
});

it('支持强制从主库读取团队标准套餐', async () => {
const mockQuery = {
lean: vi.fn().mockResolvedValue([])
};
vi.spyOn(MongoTeamSub, 'find').mockReturnValue(mockQuery as any);

await getTeamStandPlan({ teamId: mockTeamId, readFromPrimary: true });

expect(MongoTeamSub.find).toHaveBeenCalledWith(
{
teamId: mockTeamId,
type: SubTypeEnum.standard
},
undefined,
{ readPreference: 'primary' }
);
});

it('开启标准套餐配置但没有套餐记录时返回 undefined', async () => {
vi.spyOn(MongoTeamSub, 'find').mockReturnValue({
lean: vi.fn().mockResolvedValue([])
Expand Down
2 changes: 1 addition & 1 deletion pro
Submodule pro updated from cfb127 to febb0c
9 changes: 1 addition & 8 deletions projects/app/src/components/CommunityModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,10 @@ import MyModal from '@fastgpt/web/components/common/MyModal';
import { useClientTranslation } from '@fastgpt/web/i18n/useClientTranslation';
import Markdown from '../Markdown';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useUserStore } from '@/web/support/user/useUserStore';

const CommunityModal = ({ onClose }: { onClose: () => void }) => {
const { t } = useClientTranslation();
const { feConfigs } = useSystemStore();
const { userInfo } = useUserStore();
const isWecomTeam = !!userInfo?.team.isWecomTeam;

return (
<MyModal
Expand All @@ -20,11 +17,7 @@ const CommunityModal = ({ onClose }: { onClose: () => void }) => {
title={t('common:system.Concat us')}
>
<ModalBody textAlign={'center'}>
{isWecomTeam ? (
t('common:contact_email', { email: 'archer@fastgpt.io' })
) : (
<Markdown source={feConfigs?.concatMd || ''} />
)}
<Markdown source={feConfigs?.concatMd || ''} />
</ModalBody>

<ModalFooter>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ const EnterpriseAuthNoticeModal = () => {
router.pathname === '/dashboard/agent' &&
!!feConfigs?.show_enterprise_auth &&
!!teamId &&
!userInfo?.team?.isWecomTeam &&
canCheckEnterpriseAuthNotice &&
!isAccountCancellationPending &&
!enterpriseAuthNoticeReadTeamIds?.includes(teamId);
Expand Down
Loading
Loading