Skip to content

Commit

Permalink
Merge branch 'dev' into feature-be-boostcampwm-2024#160
Browse files Browse the repository at this point in the history
  • Loading branch information
mintaek22 authored Dec 3, 2024
2 parents 081e8cf + 1b37e9d commit 7fca794
Show file tree
Hide file tree
Showing 26 changed files with 372 additions and 124 deletions.
11 changes: 10 additions & 1 deletion BE/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion BE/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@
"typeorm": "^0.3.20",
"uuid": "^11.0.2",
"winston": "^3.17.0",
"winston-daily-rotate-file": "^5.0.0"
"winston-daily-rotate-file": "^5.0.0",
"zod": "^3.23.8"
},
"devDependencies": {
"@nestjs/cli": "^10.0.0",
Expand Down
1 change: 0 additions & 1 deletion BE/src/common/exception/exception.handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ export class ExceptionHandler implements ExceptionFilter {
catch(error: Error, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();

const status =
error instanceof HttpException
? error.getStatus()
Expand Down
6 changes: 6 additions & 0 deletions BE/src/interceptors/serialize.interceptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { plainToInstance } from 'class-transformer';
import { catchError } from 'rxjs/operators';
import { throwError } from 'rxjs';

interface ClassConstructor<T> {
new (...args: any[]): T;
Expand All @@ -23,6 +25,9 @@ class SerializeInterceptor<T> implements NestInterceptor {
next: CallHandler<any>,
): Observable<any> {
return next.handle().pipe(
catchError((error) => {
return throwError(() => error);
}),
map((inputData: any) => {
let data;
if (inputData instanceof Array) {
Expand Down Expand Up @@ -51,6 +56,7 @@ class SerializeInterceptor<T> implements NestInterceptor {
}

removeNullProperties(obj: any): any {
if (!obj) return;
return Object.fromEntries(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
Object.entries(obj).filter(([_, value]) => value !== null),
Expand Down
3 changes: 1 addition & 2 deletions BE/src/interceptors/user-db-connection.interceptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,8 @@ export class UserDBConnectionInterceptor implements NestInterceptor {
await request.dbConnection.commit();
}),
catchError(async (err) => {
if (err instanceof DataLimitExceedException) {
if (err instanceof DataLimitExceedException)
await request.dbConnection.rollback();
}
throw err;
}),
finalize(async () => await request.dbConnection.end()),
Expand Down
31 changes: 31 additions & 0 deletions BE/src/record/constant/random-record.constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,34 @@ export const TypeToConstructor = {
sex: SexGenerator,
boolean: BooleanGenerator,
};

export const DomainToTypes = {
name: 'string',
country: 'string',
city: 'string',
email: 'string',
phone: 'string',
sex: 'string',
boolean: 'number',
number: 'number',
enum: 'string',
};

export const mysqlToJsType = (mysqlType: string): string => {
const baseType = mysqlType.split('(')[0];
const typeMap: Record<string, string> = {
VARCHAR: 'string',
CHAR: 'string',
TEXT: 'string',
INT: 'number',
TINYINT: 'number',
BIGINT: 'number',
FLOAT: 'number',
DOUBLE: 'number',
DECIMAL: 'number',
DATETIME: 'string',
DATE: 'string',
TIMESTAMP: 'string',
};
return typeMap[baseType.toUpperCase()] || 'unknown';
};
2 changes: 1 addition & 1 deletion BE/src/record/random-column.entity.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Domains } from './dto/create-random-record.dto';
import { RandomValueGenerator } from './domain';

export interface RandomColumnEntity {
export interface RandomColumnModel {
name: string;
type: Domains;
generator: RandomValueGenerator<any>;
Expand Down
3 changes: 2 additions & 1 deletion BE/src/record/record.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@ export class RecordController {
@ExecuteRecordSwagger()
@Serialize(ResRecordDto)
@Post()
insertRandomRecord(
async insertRandomRecord(
@Req() req: Request,
@Body() randomRecordInsertDto: CreateRandomRecordDto,
) {
await this.recordService.validateDto(randomRecordInsertDto, req.sessionID);
return this.recordService.insertRandomRecord(req, randomRecordInsertDto);
}
}
3 changes: 2 additions & 1 deletion BE/src/record/record.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import { RecordService } from './record.service';
import { RedisModule } from '../config/redis/redis.module';
import { QueryDBModule } from '../config/query-database/query-db.moudle';
import { UsageModule } from '../usage/usage.module';
import { TableModule } from 'src/table/table.module';

@Module({
imports: [RedisModule, QueryDBModule, UsageModule],
imports: [RedisModule, QueryDBModule, UsageModule, TableModule],
controllers: [RecordController],
providers: [RecordService],
})
Expand Down
62 changes: 56 additions & 6 deletions BE/src/record/record.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
BadRequestException,
Injectable,
InternalServerErrorException,
OnModuleInit,
Expand All @@ -8,7 +9,7 @@ import {
CreateRandomRecordDto,
RandomColumnInfo,
} from './dto/create-random-record.dto';
import { RandomColumnEntity } from './random-column.entity';
import { RandomColumnModel } from './random-column.entity';
import fs from 'fs/promises';
import crypto from 'crypto';
import path from 'path';
Expand All @@ -22,12 +23,19 @@ import {
} from './constant/random-record.constant';
import { UserDBManager } from '../config/query-database/user-db-manager.service';
import { UsageService } from '../usage/usage.service';
import { TableService } from 'src/table/table.service';
import { ResTableDto } from 'src/table/dto/res-table.dto';
import {
DomainToTypes,
mysqlToJsType,
} from './constant/random-record.constant';

@Injectable()
export class RecordService implements OnModuleInit {
constructor(
private readonly userDBManager: UserDBManager,
private readonly usageService: UsageService,
private readonly tableService: TableService,
) {}

async onModuleInit() {
Expand All @@ -42,11 +50,53 @@ export class RecordService implements OnModuleInit {
}
}

async validateDto(
createRandomRecordDto: CreateRandomRecordDto,
identify: string,
) {
const tableInfo: ResTableDto = (await this.tableService.find(
identify,
createRandomRecordDto.tableName,
)) as ResTableDto;

if (!tableInfo?.tableName)
throw new BadRequestException(
`${createRandomRecordDto.tableName} 테이블이 존재하지 않습니다.`,
);

const baseColumns = tableInfo.columns;
const columnInfos: RandomColumnInfo[] = createRandomRecordDto.columns;

columnInfos.forEach((columnInfo) => {
const targetName = columnInfo.name;
const targetDomain = columnInfo.type;
const baseColumn = baseColumns.find(
(column) => column.name === columnInfo.name,
);

if (!baseColumn)
throw new BadRequestException(
`${targetName} 컬럼이 ${createRandomRecordDto.tableName} 에 존재하지 않습니다.`,
);

if (!this.checkDomainAvailability(baseColumn.type, targetDomain))
throw new BadRequestException(
`${targetName}(${baseColumn.type}) 컬럼에 ${targetDomain} 랜덤 값을 넣을 수 없습니다.`,
);
});
}

checkDomainAvailability(mysqlType: string, targetDomain: string) {
const baseType = mysqlToJsType(mysqlType);
const targetType = DomainToTypes[targetDomain];
if (baseType === 'number' && targetType === 'string') return false;
return true;
}
async insertRandomRecord(
req: any,
createRandomRecordDto: CreateRandomRecordDto,
): Promise<ResRecordDto> {
const columnEntities: RandomColumnEntity[] =
const columnEntities: RandomColumnModel[] =
createRandomRecordDto.columns.map((column) => this.toEntity(column));
const columnNames = columnEntities.map((column) => column.name);

Expand All @@ -72,7 +122,7 @@ export class RecordService implements OnModuleInit {
});
}

private toEntity(randomColumnInfo: RandomColumnInfo): RandomColumnEntity {
private toEntity(randomColumnInfo: RandomColumnInfo): RandomColumnModel {
let generator: RandomValueGenerator<any>;
if (generalDomain.includes(randomColumnInfo.type))
generator = new TypeToConstructor[randomColumnInfo.type]();
Expand All @@ -93,7 +143,7 @@ export class RecordService implements OnModuleInit {
}

private async generateCsvFile(
columnEntities: RandomColumnEntity[],
columnEntities: RandomColumnModel[],
rows: number,
): Promise<string> {
const randomString = crypto.randomBytes(10).toString('hex');
Expand Down Expand Up @@ -126,12 +176,12 @@ export class RecordService implements OnModuleInit {
);
}

private generateCsvHeader(columnEntities: RandomColumnEntity[]): string {
private generateCsvHeader(columnEntities: RandomColumnModel[]): string {
return columnEntities.map((column) => column.name).join(', ') + '\n';
}

private generateCsvData(
columnEntities: RandomColumnEntity[],
columnEntities: RandomColumnModel[],
rows: number,
): string {
let data = columnEntities.map((column) =>
Expand Down
4 changes: 2 additions & 2 deletions BE/src/table/table.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export class TableService {
);
}

async find(sessionId: string, tableName: string) {
async find(sessionId: string, tableName: string): Promise<ResTableDto | []> {
const tables = await this.getTables(sessionId, tableName);
const columns = await this.getColumns(sessionId, tableName);
const foreignKeys = await this.getForeignKeys(sessionId, tableName);
Expand All @@ -45,7 +45,7 @@ export class TableService {
return tables as any[];
}

private async getColumns(schema: string, tableName?: string) {
async getColumns(schema: string, tableName?: string) {
const query = `
SELECT TABLE_NAME, COLUMN_NAME, COLUMN_TYPE, COLUMN_KEY, EXTRA, IS_NULLABLE
FROM INFORMATION_SCHEMA.COLUMNS
Expand Down
77 changes: 41 additions & 36 deletions FE/src/components/LeftSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,28 @@ import {
} from '@/components/ui/sidebar'
import logo from '@/assets/logo.svg'

import { TableType } from '@/types/interfaces'
import TableTool from '@/components/TableTool'
import RecordTool from '@/components/RecordTool'
import ExampleQueryTool from '@/components/ExampleQueryTool'
import { ErrorBoundary } from 'react-error-boundary'
import SidebarErrorPage from '@/pages/SideBarErrorPage'
import useToastErrorHandler from '@/hooks/error/toastErrorHandler'
import LoadingPage from '@/pages/LoadingPage'
import { useTables } from '@/hooks/query/useTableQuery'

type LeftSidebarProps = React.ComponentProps<typeof Sidebar> & {
activeItem: (typeof MENU)[0]
setActiveItem: React.Dispatch<React.SetStateAction<(typeof MENU)[0]>>
tables: TableType[]
}

export default function LeftSidebar({
activeItem,
setActiveItem,
tables,
...props
}: LeftSidebarProps) {
const menu = MENU.slice(0, -1)

const tables = useTables()
const { setOpen, toggleSidebar } = useSidebar()
const handleError = useToastErrorHandler()

Expand Down Expand Up @@ -101,39 +102,43 @@ export default function LeftSidebar({
</button>
</div>
</SidebarHeader>
<SidebarContent className="h-full">
<SidebarGroup className="h-full p-0">
<SidebarGroupContent className="h-full">
{activeItem.title === MENU_TITLE.TABLE && (
<ErrorBoundary
FallbackComponent={SidebarErrorPage}
onReset={() => window.location.reload()}
onError={handleError}
>
<TableTool tableData={tables} />
</ErrorBoundary>
)}
{activeItem.title === MENU_TITLE.RECORD && (
<ErrorBoundary
FallbackComponent={SidebarErrorPage}
onReset={() => window.location.reload()}
onError={handleError}
>
<RecordTool tableData={tables} />
</ErrorBoundary>
)}
{activeItem.title === MENU_TITLE.TESTQUERY && (
<ErrorBoundary
FallbackComponent={SidebarErrorPage}
onReset={() => window.location.reload()}
onError={handleError}
>
<ExampleQueryTool />
</ErrorBoundary>
)}
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
{tables.isLoading ? (
<LoadingPage />
) : (
<SidebarContent className="h-full">
<SidebarGroup className="h-full p-0">
<SidebarGroupContent className="h-full">
{activeItem.title === MENU_TITLE.TABLE && (
<ErrorBoundary
FallbackComponent={SidebarErrorPage}
onReset={() => window.location.reload()}
onError={handleError}
>
<TableTool tableData={tables.data || []} />
</ErrorBoundary>
)}
{activeItem.title === MENU_TITLE.RECORD && (
<ErrorBoundary
FallbackComponent={SidebarErrorPage}
onReset={() => window.location.reload()}
onError={handleError}
>
<RecordTool tableData={tables.data || []} />
</ErrorBoundary>
)}
{activeItem.title === MENU_TITLE.TESTQUERY && (
<ErrorBoundary
FallbackComponent={SidebarErrorPage}
onReset={() => window.location.reload()}
onError={handleError}
>
<ExampleQueryTool />
</ErrorBoundary>
)}
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
)}
</Sidebar>
</Sidebar>
)
Expand Down
Loading

0 comments on commit 7fca794

Please sign in to comment.