Compare commits

...

6 Commits

Author SHA1 Message Date
CatsJuice
1623f5d82f fix(core): cannot view pricing page if not logged in (#8907)
Due to lifetime plan card show `Upgrade` button that require accout, should show `Login` instead
2024-11-27 15:01:31 +08:00
JimmFly
2abf40b465 fix(core): image block size limits were not enforced as expected (#8908) 2024-11-27 15:00:06 +08:00
EYHN
dea0574a89 fix(core): improve doc meta performance (#8913) 2024-11-27 14:59:53 +08:00
liuyi
a73c08ff24 fix(core): wrong app scheme fallback (#8914) 2024-11-25 16:50:09 +08:00
liuyi
c4410751e4 fix(server): ignore invalid subscription variant for subscriptinos query as well (#8894) 2024-11-22 13:51:03 +08:00
liuyi
47899a7eaf fix(server): ignore invalid subscription variant (#8892) 2024-11-22 12:37:15 +08:00
7 changed files with 96 additions and 29 deletions

View File

@@ -370,7 +370,7 @@ export class UserSubscriptionResolver {
}; };
} }
return this.db.userSubscription.findUnique({ const subscription = await this.db.userSubscription.findUnique({
where: { where: {
userId_plan: { userId_plan: {
userId: user.id, userId: user.id,
@@ -379,6 +379,18 @@ export class UserSubscriptionResolver {
status: SubscriptionStatus.Active, status: SubscriptionStatus.Active,
}, },
}); });
if (
subscription &&
subscription.variant &&
![SubscriptionVariant.EA, SubscriptionVariant.Onetime].includes(
subscription.variant as SubscriptionVariant
)
) {
subscription.variant = null;
}
return subscription;
} }
@ResolveField(() => [UserSubscriptionType]) @ResolveField(() => [UserSubscriptionType])
@@ -390,12 +402,25 @@ export class UserSubscriptionResolver {
throw new AccessDenied(); throw new AccessDenied();
} }
return this.db.userSubscription.findMany({ const subscriptions = await this.db.userSubscription.findMany({
where: { where: {
userId: user.id, userId: user.id,
status: SubscriptionStatus.Active, status: SubscriptionStatus.Active,
}, },
}); });
subscriptions.forEach(subscription => {
if (
subscription.variant &&
![SubscriptionVariant.EA, SubscriptionVariant.Onetime].includes(
subscription.variant as SubscriptionVariant
)
) {
subscription.variant = null;
}
});
return subscriptions;
} }
@ResolveField(() => [UserInvoiceType]) @ResolveField(() => [UserInvoiceType])

View File

@@ -13,23 +13,28 @@ export class DocRecordList extends Entity {
private readonly pool = new Map<string, DocRecord>(); private readonly pool = new Map<string, DocRecord>();
public readonly docs$ = LiveData.from<DocRecord[]>( public readonly docsMap$ = LiveData.from<Map<string, DocRecord>>(
this.store.watchDocIds().pipe( this.store.watchDocIds().pipe(
map(ids => map(
ids.map(id => { ids =>
const exists = this.pool.get(id); new Map(
if (exists) { ids.map(id => {
return exists; const exists = this.pool.get(id);
} if (exists) {
const record = this.framework.createEntity(DocRecord, { id }); return [id, exists];
this.pool.set(id, record); }
return record; const record = this.framework.createEntity(DocRecord, { id });
}) this.pool.set(id, record);
return [id, record];
})
)
) )
), ),
[] new Map()
); );
public readonly docs$ = this.docsMap$.selector(d => Array.from(d.values()));
public readonly trashDocs$ = LiveData.from<DocRecord[]>( public readonly trashDocs$ = LiveData.from<DocRecord[]>(
this.store.watchTrashDocIds().pipe( this.store.watchTrashDocIds().pipe(
map(ids => map(ids =>
@@ -53,7 +58,7 @@ export class DocRecordList extends Entity {
); );
public doc$(id: string) { public doc$(id: string) {
return this.docs$.map(record => record.find(record => record.id === id)); return this.docsMap$.selector(map => map.get(id));
} }
public setPrimaryMode(id: string, mode: DocMode) { public setPrimaryMode(id: string, mode: DocMode) {

View File

@@ -32,7 +32,7 @@ export class DocsStore extends Store {
switchMap(yjsObserve), switchMap(yjsObserve),
map(meta => { map(meta => {
if (meta instanceof YArray) { if (meta instanceof YArray) {
return meta.map(v => v.get('id')); return meta.map(v => v.get('id') as string);
} else { } else {
return []; return [];
} }
@@ -59,6 +59,7 @@ export class DocsStore extends Store {
} }
watchDocMeta(id: string) { watchDocMeta(id: string) {
let docMetaIndexCache = -1;
return yjsObserveByPath( return yjsObserveByPath(
this.workspaceService.workspace.rootYDoc.getMap('meta'), this.workspaceService.workspace.rootYDoc.getMap('meta'),
'pages' 'pages'
@@ -66,13 +67,23 @@ export class DocsStore extends Store {
switchMap(yjsObserve), switchMap(yjsObserve),
map(meta => { map(meta => {
if (meta instanceof YArray) { if (meta instanceof YArray) {
let docMetaYMap = null as YMap<any> | null; if (docMetaIndexCache >= 0) {
meta.forEach(doc => { const doc = meta.get(docMetaIndexCache);
if (doc.get('id') === id) { if (doc && doc.get('id') === id) {
docMetaYMap = doc; return doc as YMap<any>;
} }
}); }
return docMetaYMap;
// meta is YArray, `for-of` is faster then `for`
let i = 0;
for (const doc of meta) {
if (doc && doc.get('id') === id) {
docMetaIndexCache = i;
return doc as YMap<any>;
}
i++;
}
return null;
} else { } else {
return null; return null;
} }

View File

@@ -7,6 +7,7 @@ import {
import { import {
AttachmentBlockService, AttachmentBlockService,
AttachmentBlockSpec, AttachmentBlockSpec,
ImageBlockService,
} from '@blocksuite/affine/blocks'; } from '@blocksuite/affine/blocks';
import bytes from 'bytes'; import bytes from 'bytes';
@@ -19,6 +20,15 @@ class CustomAttachmentBlockService extends AttachmentBlockService {
} }
} }
class CustomImageBlockService extends ImageBlockService {
override mounted(): void {
// blocksuite default max file size is 10MB, we override it to 2GB
// but the real place to limit blob size is CloudQuotaModal / LocalQuotaModal
this.maxFileSize = bytes.parse('2GB');
super.mounted();
}
}
export const CustomAttachmentBlockSpec: ExtensionType[] = [ export const CustomAttachmentBlockSpec: ExtensionType[] = [
...AttachmentBlockSpec, ...AttachmentBlockSpec,
{ {
@@ -28,6 +38,11 @@ export const CustomAttachmentBlockSpec: ExtensionType[] = [
CustomAttachmentBlockService, CustomAttachmentBlockService,
[StdIdentifier, BlockFlavourIdentifier('affine:attachment')] [StdIdentifier, BlockFlavourIdentifier('affine:attachment')]
); );
di.override(
BlockServiceIdentifier('affine:image'),
CustomImageBlockService,
[StdIdentifier, BlockFlavourIdentifier('affine:image')]
);
}, },
}, },
]; ];

View File

@@ -1,10 +1,10 @@
import { Button } from '@affine/component'; import { Button } from '@affine/component';
import { SubscriptionService } from '@affine/core/modules/cloud'; import { AuthService, SubscriptionService } from '@affine/core/modules/cloud';
import { SubscriptionRecurring } from '@affine/graphql'; import { SubscriptionRecurring } from '@affine/graphql';
import { Trans, useI18n } from '@affine/i18n'; import { Trans, useI18n } from '@affine/i18n';
import { useLiveData, useService } from '@toeverything/infra'; import { useLiveData, useService } from '@toeverything/infra';
import { Upgrade } from '../plan-card'; import { SignUpAction, Upgrade } from '../plan-card';
import { BelieverCard } from './believer-card'; import { BelieverCard } from './believer-card';
import { BelieverBenefits } from './benefits'; import { BelieverBenefits } from './benefits';
import * as styles from './style.css'; import * as styles from './style.css';
@@ -12,6 +12,8 @@ import * as styles from './style.css';
export const LifetimePlan = () => { export const LifetimePlan = () => {
const t = useI18n(); const t = useI18n();
const subscriptionService = useService(SubscriptionService); const subscriptionService = useService(SubscriptionService);
const loggedIn =
useLiveData(useService(AuthService).session.status$) === 'authenticated';
const readableLifetimePrice = useLiveData( const readableLifetimePrice = useLiveData(
subscriptionService.prices.readableLifetimePrice$ subscriptionService.prices.readableLifetimePrice$
@@ -32,7 +34,11 @@ export const LifetimePlan = () => {
<div className={styles.price}>{readableLifetimePrice}</div> <div className={styles.price}>{readableLifetimePrice}</div>
{isBeliever ? ( {!loggedIn ? (
<SignUpAction className={styles.purchase}>
{t['com.affine.payment.sign-up-free']()}
</SignUpAction>
) : isBeliever ? (
<Button className={styles.purchase} size="default" disabled> <Button className={styles.purchase} size="default" disabled>
{t['com.affine.payment.lifetime.purchased']()} {t['com.affine.payment.lifetime.purchased']()}
</Button> </Button>

View File

@@ -383,7 +383,10 @@ const ChangeRecurring = ({
); );
}; };
const SignUpAction = ({ children }: PropsWithChildren) => { export const SignUpAction = ({
children,
className,
}: PropsWithChildren<{ className?: string }>) => {
const setOpen = useSetAtom(authAtom); const setOpen = useSetAtom(authAtom);
const onClickSignIn = useCallback(() => { const onClickSignIn = useCallback(() => {
@@ -396,7 +399,7 @@ const SignUpAction = ({ children }: PropsWithChildren) => {
return ( return (
<Button <Button
onClick={onClickSignIn} onClick={onClickSignIn}
className={styles.planAction} className={clsx(styles.planAction, className)}
variant="primary" variant="primary"
> >
{children} {children}

View File

@@ -1,5 +1,5 @@
import { OpenInAppPage } from '@affine/core/modules/open-in-app/views/open-in-app-page'; import { OpenInAppPage } from '@affine/core/modules/open-in-app/views/open-in-app-page';
import { appSchemes } from '@affine/core/utils/channel'; import { appSchemes, channelToScheme } from '@affine/core/utils/channel';
import type { GetCurrentUserQuery } from '@affine/graphql'; import type { GetCurrentUserQuery } from '@affine/graphql';
import { fetcher, getCurrentUserQuery } from '@affine/graphql'; import { fetcher, getCurrentUserQuery } from '@affine/graphql';
import type { LoaderFunction } from 'react-router-dom'; import type { LoaderFunction } from 'react-router-dom';
@@ -37,7 +37,9 @@ const OpenOAuthJwt = () => {
const [params] = useSearchParams(); const [params] = useSearchParams();
const maybeScheme = appSchemes.safeParse(params.get('scheme')); const maybeScheme = appSchemes.safeParse(params.get('scheme'));
const scheme = maybeScheme.success ? maybeScheme.data : 'affine'; const scheme = maybeScheme.success
? maybeScheme.data
: channelToScheme[BUILD_CONFIG.appBuildType];
const next = params.get('next'); const next = params.get('next');
if (!currentUser || !currentUser?.token?.sessionToken) { if (!currentUser || !currentUser?.token?.sessionToken) {