Skip to content

Commit

Permalink
Merge pull request #522 from VitNode/refactor/upload_files_to_editor
Browse files Browse the repository at this point in the history
refactor: Upload files using Editor
  • Loading branch information
aXenDeveloper authored Sep 22, 2024
2 parents d325893 + e48916c commit 6943850
Show file tree
Hide file tree
Showing 35 changed files with 630 additions and 410 deletions.
1 change: 1 addition & 0 deletions apps/backend/schema.gql
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ enum AllowTypeFilesEnum {
}

type AuthorizationAdminSessionsObj {
files: FilesAuthorizationCoreSessions!
restart_server: Boolean!
user: AuthorizationCurrentUserObj
version: String!
Expand Down
1 change: 1 addition & 0 deletions apps/frontend/src/graphql/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export const AllowTypeFilesEnum = {
export type AllowTypeFilesEnum = typeof AllowTypeFilesEnum[keyof typeof AllowTypeFilesEnum];
export type AuthorizationAdminSessionsObj = {
__typename?: 'AuthorizationAdminSessionsObj';
files: FilesAuthorizationCoreSessions;
restart_server: Scalars['Boolean']['output'];
user?: Maybe<AuthorizationCurrentUserObj>;
version: Scalars['String']['output'];
Expand Down
11 changes: 3 additions & 8 deletions apps/frontend/src/plugins/core/langs/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -141,14 +141,8 @@
"images_videos": "Images & Videos",
"images": "Images",
"errors": {
"invalid_file_type": {
"title": "Invalid file type!",
"desc": "Allowed types: {types}."
},
"max_storage_for_submit": {
"title": "Maximum storage reached!",
"desc": "You can't upload more files then total {size} for one submit."
}
"max_storage_extended": "Max storage exceeded. You can't upload more files. Max storage is {size}.",
"invalid_file_type": "Invalid file type! Allowed types: {types}."
}
},

Expand Down Expand Up @@ -294,6 +288,7 @@
"desc": "Manage your files.",
"search": "Search files by name...",
"temp_file": "Temporary File will be deleted soon",
"storage_usage": "{used} of {total} ({percent}%) storage used",
"table": {
"file_size": "File Size",
"count_uses": "Count Uses",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ export class CreateDatabaseAdminInstallService {
protected: true,
guest: true,
files_allow_upload: false,
files_total_max_storage: -1,
})
.returning();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { AuthorizationCurrentUserObj } from '@/core/sessions/authorization/authorization.dto';
import {
AuthorizationCurrentUserObj,
FilesAuthorizationCoreSessions,
} from '@/core/sessions/authorization/authorization.dto';
import { Field, ObjectType } from '@nestjs/graphql';

@ObjectType()
export class AuthorizationAdminSessionsObj {
@Field(() => FilesAuthorizationCoreSessions)
files: FilesAuthorizationCoreSessions;

@Field(() => Boolean)
restart_server: boolean;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import { core_files } from '@/database/schema/files';
import { core_sessions_known_devices } from '@/database/schema/sessions';
import { currentUnixDate, getUserAgentData, getUserIp } from '@/functions';
import {
AccessDeniedError,
getConfigFile,
GqlContext,
InternalServerError,
NotFoundError,
} from '@/index';
import { getUser } from '@/utils/database/helpers/get-user';
import { InternalDatabaseService } from '@/utils/database/internal_database.service';
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { JwtService } from '@nestjs/jwt';
import { eq } from 'drizzle-orm';
import { eq, sum } from 'drizzle-orm';
import * as fs from 'fs';
import { join } from 'path';

Expand All @@ -31,7 +33,27 @@ export class AuthorizationAdminSessionsService {
async authorization(
context: GqlContext,
): Promise<AuthorizationAdminSessionsObj> {
const user = await this.initialAuthorization(context);
const currentUser = await this.initialAuthorization(context);
const user = await this.databaseService.db.query.core_users.findFirst({
where: (table, { eq }) => eq(table.id, currentUser.id),
columns: {
id: true,
},
with: {
group: {
columns: {
files_allow_upload: true,
files_max_storage_for_submit: true,
files_total_max_storage: true,
},
},
},
});

if (!user) {
throw new InternalServerError();
}

const config = getConfigFile();

const packageJSONPath = join(__dirname, '../../../../../../package.json');
Expand All @@ -42,10 +64,28 @@ export class AuthorizationAdminSessionsService {
fs.readFileSync(packageJSONPath, 'utf8'),
);

const countStorageUsedDb = await this.databaseService.db
.select({
space_used: sum(core_files.file_size),
})
.from(core_files)
.where(eq(core_files.user_id, currentUser.id));
const countStorageUsed = +(countStorageUsedDb[0].space_used ?? 0);

return {
user,
user: currentUser,
version: packageJSON.version,
restart_server: config.restart_server,
files: {
allow_upload: user.group.files_allow_upload,
max_storage_for_submit: user.group.files_max_storage_for_submit
? user.group.files_max_storage_for_submit * 1024
: user.group.files_max_storage_for_submit,
total_max_storage: user.group.files_total_max_storage
? user.group.files_total_max_storage * 1024
: user.group.files_total_max_storage,
space_used: countStorageUsed,
},
};
}

Expand Down
30 changes: 19 additions & 11 deletions packages/backend/src/core/editor/upload/upload.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { ShowCoreFiles } from '../../files/show/show.dto';
import { UploadCoreEditorArgs } from './upload.dto';

interface GetFilesAfterUploadArgs extends UploadCoreEditorArgs {
maxUploadSizeKb: number;
maxUploadSizeBytes: number;
}

@Injectable()
Expand Down Expand Up @@ -59,7 +59,7 @@ export class UploadCoreEditorService extends HelpersUploadCoreFilesService {
private async getFilesAfterUpload({
file,
folder,
maxUploadSizeKb,
maxUploadSizeBytes,
plugin,
}: GetFilesAfterUploadArgs): Promise<UploadCoreFilesObj> {
const acceptMimeType = this.getAcceptMineType();
Expand All @@ -70,7 +70,7 @@ export class UploadCoreEditorService extends HelpersUploadCoreFilesService {
});
const args: Omit<UploadCoreFilesArgs, 'acceptMimeType' | 'secure'> = {
file,
maxUploadSizeBytes: maxUploadSizeKb * 1024,
maxUploadSizeBytes,
plugin,
folder,
};
Expand Down Expand Up @@ -127,19 +127,27 @@ export class UploadCoreEditorService extends HelpersUploadCoreFilesService {
: 0;

const remainingStorage =
findGroup.files_total_max_storage - countStorageUsed;

const maxUploadSizeKb =
remainingStorage < findGroup.files_max_storage_for_submit &&
remainingStorage > 0
? remainingStorage
: findGroup.files_max_storage_for_submit;
findGroup.files_total_max_storage !== 0
? findGroup.files_total_max_storage * 1024 - countStorageUsed
: 0;
const maxStorage = (() => {
if (remainingStorage) {
return findGroup.files_max_storage_for_submit
? Math.min(
findGroup.files_max_storage_for_submit * 1024,
remainingStorage,
)
: remainingStorage;
}

return findGroup.files_max_storage_for_submit * 1024 || -1;
})();

const uploadFile = await this.getFilesAfterUpload({
file,
plugin,
folder,
maxUploadSizeKb,
maxUploadSizeBytes: maxStorage,
});

const security_key = this.acceptMimeTypeToFrontend.includes(
Expand Down
2 changes: 1 addition & 1 deletion packages/backend/src/core/files/files.cron.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export class CoreFilesCron {
.select()
.from(core_files)
.leftJoin(core_files_using, eq(core_files_using.file_id, core_files.id))
.where(lt(core_files.created, new Date(Date.now() - 1000 * 60 * 60 * 24)))
.where(lt(core_files.created, new Date(Date.now() - 1000 * 60 * 60))) // 1 hours
.groupBy(
core_files.id,
core_files_using.file_id,
Expand Down
2 changes: 1 addition & 1 deletion packages/backend/src/core/files/helpers/upload/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export class HelpersUploadCoreFilesService {

const fileSizeInBytes = Buffer.concat(chunks).length;

if (fileSizeInBytes > maxUploadSizeBytes) {
if (fileSizeInBytes > maxUploadSizeBytes && maxUploadSizeBytes !== -1) {
throw new CustomError({
code: 'FILE_TOO_LARGE',
message: `${filename} file is too large! We only accept files up to ${maxUploadSizeBytes} bytes.`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export class AuthorizationCurrentUserObj extends User {
}

@ObjectType()
class FilesAuthorizationCoreSessions {
export class FilesAuthorizationCoreSessions {
@Field(() => Boolean)
allow_upload: boolean;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export class AuthorizationCoreSessionsService {
total_max_storage: user.group.files_total_max_storage
? user.group.files_total_max_storage * 1024
: user.group.files_total_max_storage,
space_used: countStorageUsed * 1024,
space_used: countStorageUsed,
},
};
} catch (_) {
Expand Down
55 changes: 35 additions & 20 deletions packages/frontend/src/components/editor/editor.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
'use client';

import { StringLanguage } from '@/graphql/types';
import { useSession } from '@/hooks/use-session';
import { useSessionAdmin } from '@/hooks/use-session-admin';
import { Content, EditorContent, useEditor } from '@tiptap/react';
import { useLocale } from 'next-intl';
import React from 'react';
Expand All @@ -9,16 +11,18 @@ import { cn } from '../../helpers/classnames';
import { useGlobals } from '../../hooks/use-globals';
import { Skeleton } from '../ui/skeleton';
import { EmojiExtensionEditor } from './extensions/emoji/emoji';
import { extensionsEditor } from './extensions/extensions';
import {
UploadFilesHandlerEditorArgs,
useUploadFilesHandlerEditor,
} from './extensions/files/hooks/use-upload-files-handler-editor.ts';
import { useExtensionsEditor } from './extensions/extensions';
import { getFilesFromContent } from './extensions/files/hooks/functions';
import { useFilesExtensionEditor } from './extensions/files/hooks/use-files-extension-editor';
import { FooterEditor } from './footer/footer';
import { EditorStateContext } from './hooks/use-editor-state';
import { ToolBarEditor } from './toolbar/toolbar';

interface Props extends Omit<UploadFilesHandlerEditorArgs, 'value'> {
interface Props {
allowUploadFiles?: {
folder: string;
plugin: string;
};
autofocus?: boolean;
className?: string;
disabled?: boolean;
Expand Down Expand Up @@ -49,24 +53,36 @@ export const Editor = ({
value,
disabled,
}: WithLanguage | WithoutLanguage) => {
const { files, setFiles, uploadFiles } = useUploadFilesHandlerEditor({
value,
allowUploadFiles,
});
const locale = useLocale();
const { defaultLanguage } = useGlobals();
const [selectedLanguage, setSelectedLanguage] = React.useState(
locale || defaultLanguage,
);
const session = useSession();
const adminSession = useSessionAdmin();
const allowUploadFilesSession =
session.files.allow_upload || adminSession.files.allow_upload;
const { handleDelete, checkUploadFile, uploadFile } = useFilesExtensionEditor(
{
allowUploadFiles,
},
);
const extensions = useExtensionsEditor({
fileSystem: {
editorValue: value,
files: Array.isArray(value) ? getFilesFromContent(value) : [],
selectedLanguage,
handleDelete,
checkUploadFile,
uploadFile,
allowUpload: allowUploadFilesSession,
},
});

const editor = useEditor({
autofocus: !!autofocus,
immediatelyRender: false,
extensions: [
...extensionsEditor({
uploadFiles,
}),
EmojiExtensionEditor,
],
extensions: [...extensions, EmojiExtensionEditor],
editorProps: {
attributes: {
class: cn(
Expand Down Expand Up @@ -132,14 +148,13 @@ export const Editor = ({
return (
<EditorStateContext.Provider
value={{
files,
editor,
uploadFiles,
allowUploadFiles,
allowUploadFiles: allowUploadFilesSession
? allowUploadFiles
: undefined,
value,
onChange: onChange as (value: string | StringLanguage[]) => void,
selectedLanguage,
setFiles,
}}
>
<div
Expand Down
Loading

0 comments on commit 6943850

Please sign in to comment.