Merge remote-tracking branch 'upstream/develop_backend' into develop
# Conflicts: # .idea/misc.xml # .idea/modules.xml
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -45,6 +45,7 @@ $RECYCLE.BIN/
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/dataSources.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
|
||||
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="jbr-11" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
5
.idea/modules.xml
generated
5
.idea/modules.xml
generated
@@ -2,7 +2,10 @@
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/vanillameta.iml" filepath="$PROJECT_DIR$/.idea/vanillameta.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/backend-api/backend-api.iml" filepath="$PROJECT_DIR$/backend-api/backend-api.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/design/design.iml" filepath="$PROJECT_DIR$/design/design.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/docs/docs.iml" filepath="$PROJECT_DIR$/docs/docs.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/frontend-web/frontend-web.iml" filepath="$PROJECT_DIR$/frontend-web/frontend-web.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
7
.idea/prettier.xml
generated
Normal file
7
.idea/prettier.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="PrettierConfiguration">
|
||||
<option name="myRunOnSave" value="true" />
|
||||
<option name="myRunOnReformat" value="true" />
|
||||
</component>
|
||||
</project>
|
||||
15
.idea/webResources.xml
generated
Normal file
15
.idea/webResources.xml
generated
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="WebResourcesPaths">
|
||||
<contentEntries>
|
||||
<entry url="file://$PROJECT_DIR$/frontend-web">
|
||||
<entryData>
|
||||
<resourceRoots>
|
||||
<path value="file://$PROJECT_DIR$/frontend-web/public" />
|
||||
<path value="file://$PROJECT_DIR$/frontend-web/storybook-static" />
|
||||
</resourceRoots>
|
||||
</entryData>
|
||||
</entry>
|
||||
</contentEntries>
|
||||
</component>
|
||||
</project>
|
||||
83
CONTRIBUTING.md
Normal file
83
CONTRIBUTING.md
Normal file
@@ -0,0 +1,83 @@
|
||||
Contributing to Metabase
|
||||
Thank you
|
||||
First off, thanks for your interest in Metabase and for wanting to contribute!
|
||||
|
||||
In this guide, we’ll discuss how Metabase is built. This should give you a good sense of our process and where you might want to fit in.
|
||||
|
||||
In general, we like to have an open issue for every pull request as a place to discuss the nature of any bug or proposed improvement. Each pull request should address a single issue, and contain both the fix as well as a description of the pull request and tests that validate that the PR fixes the issue in question.
|
||||
|
||||
For bug fixes, please submit the pull request to target the master branch. From time to time, our team will backport selected critical bug fixes to the stable/release branch.
|
||||
|
||||
For significant feature additions, it is expected that discussion will have taken place in the attached issue. Any feature that requires a major decision to be reached will need to have an explicit design document written. The goals of this document are to make explicit the assumptions, constraints and tradeoffs any given feature implementation will contain. The point is not to generate documentation but to allow discussion to reference a specific proposed design and to allow others to consider the implications of a given design.
|
||||
|
||||
Contributor License Agreement
|
||||
We don’t like getting sued, so before merging any pull request, we’ll need each person contributing code to sign a Contributor License Agreement.
|
||||
|
||||
What we’re trying to build
|
||||
Metabase is all about letting non-technical users get access to the their organization’s data. We’re trying to maximize the amount of power that can be comfortably used by someone who understands their business, is quantitatively bent, but probably only comfortable with Excel.
|
||||
|
||||
It’s important to keep in mind these goals of the Metabase project. Many times proposals will be marked “Out of Scope” or otherwise deprioritized. This doesn’t mean the proposal isn’t useful, or that we wouldn’t be interested in seeing it done as a side project or as an experimental branch. However, it does mean that we won’t point the core team or contributors to it in the near term. Issues that are slightly out of scope will be kept open in case there is community support (and ideally contributions).
|
||||
|
||||
To get a sense for the end goals, make sure to read the Zen of Metabase.
|
||||
|
||||
Our product process:
|
||||
The core team runs a pretty well defined product process. It is actively being tweaked, but the below is a pretty faithful description of it at the time of writing. You should have a clear idea of how we work before jumping in with a PR.
|
||||
|
||||
A) Identify product needs from the community
|
||||
We actively look for new feature ideas from our community, user base and our own use of Metabase internally. We concentrate on the underlying problem or need as opposed to requests for specific features. While sometimes suggested features are built as requested, often we find that they involve changes to existing features, and perhaps an entirely different solution to the underlying problem. These will typically be collected in a number of issues, and tagged Proposal
|
||||
|
||||
B) Synthesize these needs into a concrete feature
|
||||
We typically will collect a group of issues or suggestions into a new topline feature concept. Typically we’ll create a working document that collects all “Open Questions” regarding to what the feature is meant to do, and more importantly not do. We’ll chat with our users, maybe do in depth interviews and generally try to tightly define the feature. If a feature seems like it will need time to be discussed and scoped, it will be tagged Proposal/Being Discussed to signify that it is still actively under discussion.
|
||||
|
||||
C) Design the feature
|
||||
Once a feature has been defined, typically it will be taken on by a product designer. Here, they will produce low fi mocks, get feedback from our users and community, and iterate.
|
||||
|
||||
Once the main UX flows have been dialed in, there will be a hi-fidelity visual design.
|
||||
|
||||
Features that are ready for design are tagged Design Needed. Once a feature has had a reasonably complete visual design it should be tagged Help Wanted.
|
||||
|
||||
D) Build the feature
|
||||
Once a feature is tagged Help Wanted, it is considered ready to be built. A core team member (or you, awesomely helpful person that you are) can start working on it.
|
||||
|
||||
If you’re building something that users will see in Metabase, please refer to the Style Guide (found at https://localhost:3000/_internal while running the development environment to learn how and when to use various Metabase UI elements.
|
||||
|
||||
Once one or more people have started to work on a feature, it should be marked In Progress. Once there is a branch+some code, a pull request is opened, linked to the feature + any issues that were pulled together to inform the feature.
|
||||
|
||||
E) Verification and merging
|
||||
All PRs that involve more than an insignificant change should be reviewed. See our Code Review Process.
|
||||
|
||||
If all goes well, the feature gets coded up, verified and then the pull request gets merged! High-fives all around.
|
||||
|
||||
If there are tests missing, code style concerns or specific architectural issues in the pull request, they should be fixed before merging. We have a very high bar on both code and product quality and it’s important that this be maintained going forward, so please be patient with us here.
|
||||
|
||||
Ways to help
|
||||
The starting point would be to get familiar with Metabase the product, and know your way around. If you’re using it at work, that’s great! If not, download Metabase and play around with it. Read the docs and generally get a feel for the flow of the product.
|
||||
|
||||
Here are some ways you can help, in order of increasing coordination + interaction with us:
|
||||
|
||||
Help with identifying needs and problems Metabase can solve
|
||||
If you want to help, try out Metabase. Use it at your company, and report back the things you like, dislike and any problems you run into. Help us understand your data model, required metrics and common usage patterns as much as you can. This information directly affects the quality of the product. The more you tell us about the kinds of problems you’re facing, the better we’ll be able to address them.
|
||||
|
||||
Help us triage and support other users
|
||||
Spend time on discourse.metabase.com and on new issues and try to reproduce the bugs reported. For people having trouble with their databases where you have significant knowledge, help them out. Who knows, maybe they’ll end up helping you with something in the future.
|
||||
|
||||
It is helpful if you understand our prioritization framework when responding.
|
||||
|
||||
Tell your friends
|
||||
Let your friends know about Metabase. Start a user group in your area. Tweet about us. Blog about how you’re using Metabase, and share what you’ve learned.
|
||||
|
||||
Fix bugs
|
||||
By our definition, “Bugs” are situations where the program doesn’t do what it was expected to according to the design or specification. These are typically scoped to issues where there is a clearly defined correct behavior. It’s usually safe to grab one of these, fix it, and submit a PR (with tests!). These will be merged without too much drama unless the PR touches a lot of code. Don’t be offended if we ask you to make small modifications or add more tests. We’re a bit OCD on code coverage and coding style.
|
||||
|
||||
Help with Documentation
|
||||
There are a lot of docs. We often have difficulties keeping them up to date. If you are reading them and you notice inconsistencies, errors or outdated information, please help up keep them current!
|
||||
|
||||
Working on features
|
||||
Some features, eg Database drivers, don’t have any user facing pixels. These are a great place to start off contributing as they don’t require as much communication, discussions about tradeoffs and process in general.
|
||||
|
||||
In situations where a design has already been done, we can always use some help. Chime in on a pull request or an issue and offer to help.
|
||||
|
||||
Generally speaking, any issue in Help Wanted is fair game.
|
||||
|
||||
#YOLO JUST SUBMIT A PR
|
||||
If you come up with something really cool, and want to share it with us, just submit a PR. If it hasn’t gone through the above process, we probably won’t merge it as is, but if it’s compelling, we’re more than willing to help you via code review, design review and generally OCD nitpicking so that it fits into the rest of our codebase.
|
||||
47
README.md
47
README.md
@@ -1,46 +1,23 @@
|
||||
# VanillaMeta
|
||||
|
||||
## 바닐라메타 - 통합 데이터 분석/시각화 시스템
|
||||
/// image ///
|
||||
|
||||
- 데이터 소스 연결
|
||||
- 적합한 템플릿 추천
|
||||
- 데이터 시각화 편집
|
||||
intro
|
||||
|
||||
## Git Commit 작성하기
|
||||
## Navigation
|
||||
[How to Use](https://github.com/godyuo/Algorithm/edit/main/README.md#How%20to%20Use)<br/>
|
||||
[Development Status]()<br/>
|
||||
[Things that helped contribute]()<br/>
|
||||
[Wiki]()<br/>
|
||||
|
||||
## How to Use
|
||||
```
|
||||
[commitType] 작업내용을 작성합니다 #2
|
||||
|
||||
1. 필요시 본문을 부연 작성합니다.
|
||||
2. 필요시 본문을 부연 작성합니다.
|
||||
```
|
||||
|
||||
### commit에 들어갈 내용
|
||||
## Development Status
|
||||
|
||||
- 제목에는 작업내용이 무엇인지 명확히 작성합니다.
|
||||
- 제목의 마지막에 마침표를 붙이지 않습니다.
|
||||
- 필요시 제목의 뒤에 이슈 번호를 붙입니다.
|
||||
- 필요시 본문을 덧붙여 작성하며, 번호를 붙여 작업내용을 정리합니다.
|
||||
- 본문은 '어떻게' 보다 '무엇을', '왜'에 맞춰 작성합니다.
|
||||
|
||||
### commit 방법
|
||||
|
||||
```shell
|
||||
git commit -m "[commitType] 작업내용을 작성합니다 #2
|
||||
|
||||
1. 필요시 본문을 부연 작성합니다.
|
||||
2. 필요시 본문을 부연 작성합니다."
|
||||
## Things that helped contribute
|
||||
```
|
||||
```
|
||||
|
||||
### commit type 목록
|
||||
|
||||
```
|
||||
feat : 기능 (새로운 기능)
|
||||
fix : 버그 (버그 수정)
|
||||
refactor : 리팩토링
|
||||
style : 스타일 (코드 형식, 세미콜론 추가: 비즈니스 로직에 변경 없음)
|
||||
docs : 문서 (문서 추가, 수정, 삭제)
|
||||
test : 테스트 (테스트 코드 추가, 수정, 삭제: 비즈니스 로직에 변경 없음)
|
||||
chore : 기타 변경사항 (package.json, 빌드 스크립트 수정 등)
|
||||
remove : 코드, 파일, 폴더 등 삭제
|
||||
```
|
||||
## Wiki
|
||||
9
backend-api/.env.sample.dev
Normal file
9
backend-api/.env.sample.dev
Normal file
@@ -0,0 +1,9 @@
|
||||
DB_TYPE='mysql'
|
||||
DB_HOST='localhost'
|
||||
DB_PORT=3306
|
||||
DB_USERNAME='vanillameta'
|
||||
DB_PASSWORD='pw'
|
||||
DB_NAME='vanillameta'
|
||||
|
||||
CORS_ORIGIN='*'
|
||||
|
||||
25
backend-api/.eslintrc.js
Normal file
25
backend-api/.eslintrc.js
Normal file
@@ -0,0 +1,25 @@
|
||||
module.exports = {
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
project: 'tsconfig.json',
|
||||
tsconfigRootDir : __dirname,
|
||||
sourceType: 'module',
|
||||
},
|
||||
plugins: ['@typescript-eslint/eslint-plugin'],
|
||||
extends: [
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:prettier/recommended',
|
||||
],
|
||||
root: true,
|
||||
env: {
|
||||
node: true,
|
||||
jest: true,
|
||||
},
|
||||
ignorePatterns: ['.eslintrc.js'],
|
||||
rules: {
|
||||
'@typescript-eslint/interface-name-prefix': 'off',
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
},
|
||||
};
|
||||
42
backend-api/.gitignore
vendored
Normal file
42
backend-api/.gitignore
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
#db정보
|
||||
/config.serverless.yml
|
||||
/.env.dev
|
||||
/.env.prod
|
||||
/.ormconfig.json
|
||||
/vanillameta
|
||||
.
|
||||
# compiled output
|
||||
/dist
|
||||
/node_modules
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
pnpm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
|
||||
# Tests
|
||||
/coverage
|
||||
/.nyc_output
|
||||
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
|
||||
# IDE - VSCode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
19
backend-api/.prettierrc
Normal file
19
backend-api/.prettierrc
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"parser": "typescript",
|
||||
"singleQuote": true,
|
||||
"printWidth": 100,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"semi": true,
|
||||
"quoteProps": "as-needed",
|
||||
"jsxSingleQuote": false,
|
||||
"trailingComma": "all",
|
||||
"arrowParens": "avoid",
|
||||
"endOfLine": "auto",
|
||||
"bracketSpacing": true,
|
||||
"jsxBracketSameLine": false,
|
||||
"requirePragma": false,
|
||||
"insertPragma": false,
|
||||
"proseWrap": "preserve",
|
||||
"vueIndentScriptAndStyle": false
|
||||
}
|
||||
73
backend-api/README.md
Normal file
73
backend-api/README.md
Normal file
@@ -0,0 +1,73 @@
|
||||
<p align="center">
|
||||
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="200" alt="Nest Logo" /></a>
|
||||
</p>
|
||||
|
||||
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
|
||||
[circleci-url]: https://circleci.com/gh/nestjs/nest
|
||||
|
||||
<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p>
|
||||
<p align="center">
|
||||
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
|
||||
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
|
||||
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/common.svg" alt="NPM Downloads" /></a>
|
||||
<a href="https://circleci.com/gh/nestjs/nest" target="_blank"><img src="https://img.shields.io/circleci/build/github/nestjs/nest/master" alt="CircleCI" /></a>
|
||||
<a href="https://coveralls.io/github/nestjs/nest?branch=master" target="_blank"><img src="https://coveralls.io/repos/github/nestjs/nest/badge.svg?branch=master#9" alt="Coverage" /></a>
|
||||
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
|
||||
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
|
||||
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
|
||||
<a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg"/></a>
|
||||
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://img.shields.io/badge/Support%20us-Open%20Collective-41B883.svg" alt="Support us"></a>
|
||||
<a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow"></a>
|
||||
</p>
|
||||
<!--[](https://opencollective.com/nest#backer)
|
||||
[](https://opencollective.com/nest#sponsor)-->
|
||||
|
||||
## Description
|
||||
|
||||
[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
$ npm install
|
||||
```
|
||||
|
||||
## Running the app
|
||||
|
||||
```bash
|
||||
# development
|
||||
$ npm run start
|
||||
|
||||
# watch mode
|
||||
$ npm run start:dev
|
||||
|
||||
# production mode
|
||||
$ npm run start:prod
|
||||
```
|
||||
|
||||
## Test
|
||||
|
||||
```bash
|
||||
# unit tests
|
||||
$ npm run test
|
||||
|
||||
# e2e tests
|
||||
$ npm run test:e2e
|
||||
|
||||
# test coverage
|
||||
$ npm run test:cov
|
||||
```
|
||||
|
||||
## Support
|
||||
|
||||
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
|
||||
|
||||
## Stay in touch
|
||||
|
||||
- Author - [Kamil Myśliwiec](https://kamilmysliwiec.com)
|
||||
- Website - [https://nestjs.com](https://nestjs.com/)
|
||||
- Twitter - [@nestframework](https://twitter.com/nestframework)
|
||||
|
||||
## License
|
||||
|
||||
Nest is [MIT licensed](LICENSE).
|
||||
@@ -2,7 +2,10 @@
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
||||
</content>
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
17
backend-api/config.serverless.sample.yml
Normal file
17
backend-api/config.serverless.sample.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
AWS_REGION: ap-northeast-2
|
||||
STAGE: dev
|
||||
DB_CONFIG:
|
||||
dev:
|
||||
DB_TYPE: mysql
|
||||
DB_HOST: localhost
|
||||
DB_PORT: 3306
|
||||
DB_USER: vanillameta
|
||||
DB_PASSWORD: pw
|
||||
DB_NAME: vanillameta
|
||||
prod:
|
||||
DB_TYPE: mysql
|
||||
DB_HOST: localhost
|
||||
DB_PORT: 3306
|
||||
DB_USER: vanillameta
|
||||
DB_PASSWORD: pw
|
||||
DB_NAME: vanillameta
|
||||
9
backend-api/jest.config.json
Normal file
9
backend-api/jest.config.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"moduleFileExtensions": ["js", "json", "ts"],
|
||||
"rootDir": ".",
|
||||
"testEnvironment": "node",
|
||||
"testRegex": ".*spec.ts$",
|
||||
"transform": {
|
||||
"^.+\\.(t|j)s$": "ts-jest"
|
||||
}
|
||||
}
|
||||
5
backend-api/nest-cli.json
Normal file
5
backend-api/nest-cli.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/nest-cli",
|
||||
"collection": "@nestjs/schematics",
|
||||
"sourceRoot": "src"
|
||||
}
|
||||
29
backend-api/ormconfig.json
Normal file
29
backend-api/ormconfig.json
Normal file
@@ -0,0 +1,29 @@
|
||||
[
|
||||
{
|
||||
"name": "default",
|
||||
"type": "sqlite",
|
||||
"host" : "127.0.0.1",
|
||||
"post" : 5432,
|
||||
"username" : "username",
|
||||
"password" : "password",
|
||||
"database" : "db_01",
|
||||
"entities": [
|
||||
"dist/**/*.entity.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "test_db",
|
||||
"tpye":
|
||||
"database" : "vanillameta",
|
||||
},
|
||||
{
|
||||
"name": "db_02",
|
||||
"type": "mysql",
|
||||
"host": "localhost",
|
||||
"port": 3306,
|
||||
"username": "vanillameta",
|
||||
"password": "pw",
|
||||
"database": "vanillameta",
|
||||
"autoLoadEntities": true
|
||||
|
||||
]
|
||||
18864
backend-api/package-lock.json
generated
Normal file
18864
backend-api/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
119
backend-api/package.json
Normal file
119
backend-api/package.json
Normal file
@@ -0,0 +1,119 @@
|
||||
{
|
||||
"name": "backend-api",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"author": "",
|
||||
"private": true,
|
||||
"license": "UNLICENSED",
|
||||
"scripts": {
|
||||
"prebuild": "rimraf dist",
|
||||
"build": "nest build",
|
||||
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
||||
"start": "nest start",
|
||||
"start:dev": "cross-env NODE_ENV=dev nest start --watch",
|
||||
"start:debug": "nest start --debug --watch",
|
||||
"start:prod": "node dist/main",
|
||||
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch",
|
||||
"test:cov": "jest --coverage",
|
||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
||||
"test:e2e": "jest --config ./test/jest-e2e.json",
|
||||
"deploy": "serverless deploy --stage prod",
|
||||
"deploy:dev": "serverless deploy --stage dev",
|
||||
"deploy:prod": "serverless deploy --stage prod"
|
||||
},
|
||||
"dependencies": {
|
||||
"@google-cloud/bigquery": "^6.0.3",
|
||||
"@nestjs/axios": "^0.1.0",
|
||||
"@nestjs/common": "^9.0.0",
|
||||
"@nestjs/config": "^2.2.0",
|
||||
"@nestjs/core": "^9.0.0",
|
||||
"@nestjs/mapped-types": "*",
|
||||
"@nestjs/platform-express": "^9.0.0",
|
||||
"@nestjs/swagger": "^6.1.2",
|
||||
"@nestjs/typeorm": "^9.0.1",
|
||||
"@nestjsplus/knex": "^1.0.0",
|
||||
"@types/mustache": "^4.2.1",
|
||||
"@types/winston": "^2.4.4",
|
||||
"aws-lambda": "^1.0.7",
|
||||
"aws-serverless-express": "^3.4.0",
|
||||
"axios": "^0.27.2",
|
||||
"better-sqlite3": "^7.6.2",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.13.2",
|
||||
"cookie-parser": "^1.4.5",
|
||||
"cross-env": "^7.0.3",
|
||||
"js-joda": "^1.11.0",
|
||||
"knex": "^2.3.0",
|
||||
"knex-bigquery": "^2.0.3",
|
||||
"knex-schema-inspector": "^2.0.4",
|
||||
"knex-snowflake-dialect": "^1.0.1",
|
||||
"mustache": "^4.2.0",
|
||||
"mysql": "^2.18.1",
|
||||
"mysql2": "^2.3.3",
|
||||
"nest-winston": "^1.7.0",
|
||||
"oracledb": "^5.5.0",
|
||||
"pg": "^8.8.0",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rimraf": "^3.0.2",
|
||||
"rxjs": "^7.2.0",
|
||||
"snowflake-sdk": "^1.6.13",
|
||||
"sqlite3": "^5.1.1",
|
||||
"swagger-ui-express": "^4.5.0",
|
||||
"tedious": "^15.1.0",
|
||||
"ts-jenum": "^2.2.2",
|
||||
"typeorm": "^0.3.9",
|
||||
"typeorm-naming-strategies": "^4.1.0",
|
||||
"winston": "^3.8.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "^9.0.0",
|
||||
"@nestjs/schematics": "^9.0.0",
|
||||
"@nestjs/testing": "^9.0.0",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/jest": "28.1.8",
|
||||
"@types/node": "^16.0.0",
|
||||
"@types/supertest": "^2.0.11",
|
||||
"@typescript-eslint/eslint-plugin": "^5.0.0",
|
||||
"@typescript-eslint/parser": "^5.0.0",
|
||||
"eslint": "^8.0.1",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"jest": "28.1.3",
|
||||
"prettier": "^2.3.2",
|
||||
"serverless": "^2.61.0",
|
||||
"serverless-domain-manager": "^5.1.5",
|
||||
"serverless-http": "^2.7.0",
|
||||
"serverless-latest-layer-version": "^2.1.1",
|
||||
"serverless-offline": "^8.2.0",
|
||||
"serverless-plugin-optimize": "^4.2.1-rc.1",
|
||||
"serverless-plugin-typescript": "^2.1.0",
|
||||
"serverless-plugin-warmup": "^6.0.0",
|
||||
"serverless-webpack": "^5.5.5",
|
||||
"source-map-support": "^0.5.20",
|
||||
"supertest": "^6.1.3",
|
||||
"ts-jest": "28.0.8",
|
||||
"ts-loader": "^9.2.3",
|
||||
"ts-node": "^10.0.0",
|
||||
"tsconfig-paths": "4.1.0",
|
||||
"typescript": "^4.7.4"
|
||||
},
|
||||
"jest": {
|
||||
"moduleFileExtensions": [
|
||||
"js",
|
||||
"json",
|
||||
"ts"
|
||||
],
|
||||
"rootDir": "src",
|
||||
"testRegex": ".*\\.spec\\.ts$",
|
||||
"transform": {
|
||||
"^.+\\.(t|j)s$": "ts-jest"
|
||||
},
|
||||
"collectCoverageFrom": [
|
||||
"**/*.(t|j)s"
|
||||
],
|
||||
"coverageDirectory": "../coverage",
|
||||
"testEnvironment": "node"
|
||||
}
|
||||
}
|
||||
102
backend-api/serverless.yml
Normal file
102
backend-api/serverless.yml
Normal file
@@ -0,0 +1,102 @@
|
||||
service: vanillameta-backend-api
|
||||
|
||||
#frameworkVersion: '2'
|
||||
|
||||
plugins:
|
||||
- serverless-plugin-typescript
|
||||
- serverless-offline
|
||||
- serverless-latest-layer-version
|
||||
- serverless-plugin-warmup
|
||||
provider:
|
||||
name: aws
|
||||
runtime: nodejs14.x
|
||||
lambdaHashingVersion: 20201221
|
||||
## memorySize: 512 - 기본은 512
|
||||
timeout: 10
|
||||
# layers:
|
||||
# - arn:aws:lambda:${opt:region, self:provider.region}:${AWS::AccountId}:layer:AWSNodeLibs:latest
|
||||
stage: ${opt:stage, file(./config.serverless.yml):STAGE}
|
||||
region: ${opt:region, file(./config.serverless.yml):AWS_REGION}
|
||||
# deploymentBucket: ${file(./config.serverless.yml):DEPLOYMENT_BUCKET}
|
||||
environment:
|
||||
STAGE: ${self:provider.stage}
|
||||
NODE_ENV: ${self:provider.stage}
|
||||
NODE_PATH: "./:/opt/node_modules"
|
||||
# # DB_HOST: ${self:custom.DB_CONFIG.${self:custom.STAGE}.DB_HOST}
|
||||
# # DB_USER: ${self:custom.DB_CONFIG.${self:custom.STAGE}.DB_USER}
|
||||
# # DB_PASSWORD: ${self:custom.DB_CONFIG.${self:custom.STAGE}.DB_PASSWORD}
|
||||
#
|
||||
## iamRoleStatements:
|
||||
## - Effect: Allow
|
||||
## Action:
|
||||
## - dynamodb:DescribeTable
|
||||
## - dynamodb:Query
|
||||
## - dynamodb:Scan
|
||||
## - dynamodb:GetItem
|
||||
## - dynamodb:PutItem
|
||||
## - dynamodb:UpdateItem
|
||||
## - dynamodb:DeleteItem
|
||||
## Resource: "arn:aws:dynamodb:${opt:region, self:provider.region}:*:*"
|
||||
apiGateway:
|
||||
binaryMediaTypes:
|
||||
- '*/*'
|
||||
|
||||
|
||||
custom:
|
||||
# Enable warmup on all functions (only for production)
|
||||
warmup:
|
||||
- prod
|
||||
STAGE: ${self:provider.stage}
|
||||
DB_CONFIG: ${file(./config.serverless.yml):DB_CONFIG}
|
||||
DOMAINS:
|
||||
prod: vanillameta-api.vanillabrain.com
|
||||
dev: dev-vanillameta-api.vanillabrain.com
|
||||
customDomain:
|
||||
domainName: ${self:custom.DOMAINS.${self:custom.STAGE}}
|
||||
basePath: 'v1'
|
||||
stage: ${self:custom.STAGE}
|
||||
createRoute53Record: true
|
||||
serverless-offline:
|
||||
httpPort: 4000
|
||||
host: '0.0.0.0'
|
||||
serverlessPluginTypescript:
|
||||
tsConfigFileLocation: './tsconfig.build.json'
|
||||
webpack:
|
||||
includeModules: true
|
||||
|
||||
package:
|
||||
include:
|
||||
- .env.dev
|
||||
- .env.prod
|
||||
- src/templates/**
|
||||
exclude:
|
||||
# - node_modules/**
|
||||
- .git/**
|
||||
- test/**
|
||||
- e2e/**
|
||||
- nodemon.json
|
||||
- README.md
|
||||
# - src/**cd
|
||||
|
||||
|
||||
functions:
|
||||
app: # 람다함수 이름
|
||||
# 'handler' 모듈은 'src/serverless' 파일에서 export 되어있음
|
||||
handler: src/serverless.handler
|
||||
events:
|
||||
- http:
|
||||
method: ANY
|
||||
path: /
|
||||
cors:
|
||||
origin: '*'
|
||||
headers: '*'
|
||||
allowCredentials: true
|
||||
maxAge: 86400
|
||||
- http:
|
||||
method: ANY
|
||||
path: '{proxy+}'
|
||||
cors:
|
||||
origin: '*'
|
||||
headers: '*'
|
||||
allowCredentials: true
|
||||
maxAge: 86400
|
||||
22
backend-api/src/app.controller.spec.ts
Normal file
22
backend-api/src/app.controller.spec.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
|
||||
describe('AppController', () => {
|
||||
let appController: AppController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const app: TestingModule = await Test.createTestingModule({
|
||||
controllers: [AppController],
|
||||
providers: [AppService],
|
||||
}).compile();
|
||||
|
||||
appController = app.get<AppController>(AppController);
|
||||
});
|
||||
|
||||
describe('root', () => {
|
||||
it('should return "Hello World!"', () => {
|
||||
expect(appController.getHello()).toBe('Hello World!');
|
||||
});
|
||||
});
|
||||
});
|
||||
12
backend-api/src/app.controller.ts
Normal file
12
backend-api/src/app.controller.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
import { AppService } from './app.service';
|
||||
|
||||
@Controller()
|
||||
export class AppController {
|
||||
constructor(private readonly appService: AppService) {}
|
||||
|
||||
@Get()
|
||||
getHello(): string {
|
||||
return this.appService.getHello();
|
||||
}
|
||||
}
|
||||
49
backend-api/src/app.module.ts
Normal file
49
backend-api/src/app.module.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import {Module} from '@nestjs/common';
|
||||
import {AppController} from './app.controller';
|
||||
import {AppService} from './app.service';
|
||||
import {ConfigModule} from '@nestjs/config';
|
||||
import {TypeOrmModule} from '@nestjs/typeorm';
|
||||
import {DatabaseModule} from './database/database.module';
|
||||
import {DatasetModule} from './dataset/dataset.module';
|
||||
import {WidgetModule} from './widget/widget.module';
|
||||
import {DashboardModule} from './dashboard/dashboard.module';
|
||||
import {TemplateModule} from './template/template.module';
|
||||
import {CommonModule} from './common/common.module';
|
||||
import {ComponentModule} from './component/component.module';
|
||||
import {WidgetViewModule} from './widget-view/widget-view.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
isGlobal: true,
|
||||
envFilePath: process.env.NODE_ENV == 'dev' ? '.env.dev' : '.env.prod',
|
||||
}),
|
||||
TypeOrmModule.forRoot({
|
||||
type: 'mysql',
|
||||
host: process.env.DB_HOST,
|
||||
port: parseInt(process.env.DB_PORT) || 3306,
|
||||
username: process.env.DB_USERNAME,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_NAME,
|
||||
// type: 'sqlite',
|
||||
// database: 'vanillameta',
|
||||
autoLoadEntities: true,
|
||||
entities: [__dirname + '/**/*.entity{.ts,.js}'],
|
||||
synchronize: false,
|
||||
logging: process.env.NODE_ENV == 'dev',
|
||||
retryAttempts: 1,
|
||||
}),
|
||||
DatabaseModule,
|
||||
DatasetModule,
|
||||
WidgetModule,
|
||||
DashboardModule,
|
||||
TemplateModule,
|
||||
CommonModule,
|
||||
ComponentModule,
|
||||
WidgetViewModule,
|
||||
],
|
||||
controllers: [AppController],
|
||||
providers: [AppService],
|
||||
})
|
||||
export class AppModule {
|
||||
}
|
||||
8
backend-api/src/app.service.ts
Normal file
8
backend-api/src/app.service.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class AppService {
|
||||
getHello(): string {
|
||||
return 'Hello World!';
|
||||
}
|
||||
}
|
||||
4
backend-api/src/common/common.module.ts
Normal file
4
backend-api/src/common/common.module.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
@Module({})
|
||||
export class CommonModule {}
|
||||
9
backend-api/src/common/entities/base.entity.ts
Normal file
9
backend-api/src/common/entities/base.entity.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { CreateDateColumn, UpdateDateColumn } from 'typeorm';
|
||||
|
||||
export abstract class BaseEntity {
|
||||
@CreateDateColumn({comment:'생성일'})
|
||||
createdAt: Date;
|
||||
|
||||
@UpdateDateColumn({comment:'수정일'})
|
||||
updatedAt: Date;
|
||||
}
|
||||
4
backend-api/src/common/enum/dataset-type.enum.ts
Normal file
4
backend-api/src/common/enum/dataset-type.enum.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export enum DatasetType {
|
||||
DATASET = 'DATASET',
|
||||
WIDGET = 'WIDGET_VIEW'
|
||||
}
|
||||
4
backend-api/src/common/enum/yn.enum.ts
Normal file
4
backend-api/src/common/enum/yn.enum.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export enum YesNo {
|
||||
YES = 'Y',
|
||||
NO = 'N'
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { ValueTransformer } from 'typeorm';
|
||||
|
||||
/**
|
||||
* `JSON.stringify` 이슈로 인해 `find` 함수들에서 모두 에러가 발생한다.
|
||||
* 그래서 bigint 타입으로의 전환은 불가능하다.
|
||||
* https://github.com/tc39/proposal-bigint/issues/24 에서 bigint가 대상에 포함되면 그때 아래 코드로 전환한다.
|
||||
* <code>
|
||||
export class BigintValueTransformer implements ValueTransformer {
|
||||
to(entityValue: bigint) {
|
||||
return entityValue;
|
||||
}
|
||||
from(databaseValue: string): bigint {
|
||||
return BigInt(databaseValue);
|
||||
}
|
||||
}
|
||||
</code>
|
||||
*/
|
||||
export class BigintValueTransformer implements ValueTransformer {
|
||||
to(entityValue: number): number {
|
||||
return entityValue;
|
||||
}
|
||||
|
||||
from(databaseValue: string): number {
|
||||
return parseInt(databaseValue, 10);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { ValueTransformer } from 'typeorm';
|
||||
import { LocalDateTime } from 'js-joda';
|
||||
import { DateTimeUtil } from '../util/datetime.util';
|
||||
|
||||
export class LocalDateTimeTransformer implements ValueTransformer {
|
||||
to(entityValue: LocalDateTime): Date {
|
||||
return DateTimeUtil.toDate(entityValue);
|
||||
}
|
||||
|
||||
from(databaseValue: Date): LocalDateTime {
|
||||
return DateTimeUtil.toLocalDateTime(databaseValue);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { ValueTransformer } from 'typeorm';
|
||||
import { LocalDate } from 'js-joda';
|
||||
import { DateTimeUtil } from '../util/datetime.util';
|
||||
|
||||
export class LocalDateTransformer implements ValueTransformer {
|
||||
// entity -> db로 넣을때
|
||||
to(entityValue: LocalDate): Date {
|
||||
return DateTimeUtil.toDate(entityValue);
|
||||
}
|
||||
|
||||
// db -> entity로 가져올때
|
||||
from(databaseValue: Date): LocalDate {
|
||||
return DateTimeUtil.toLocalDate(databaseValue);
|
||||
}
|
||||
}
|
||||
52
backend-api/src/common/util/datetime.util.ts
Normal file
52
backend-api/src/common/util/datetime.util.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { convert, DateTimeFormatter, LocalDate, LocalDateTime, nativeJs } from 'js-joda';
|
||||
|
||||
export class DateTimeUtil {
|
||||
private static DATE_FORMATTER = DateTimeFormatter.ofPattern('yyyy-MM-dd');
|
||||
private static DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern('yyyy-MM-dd HH:mm:ss');
|
||||
|
||||
static toString(localDate: LocalDate | LocalDateTime): string {
|
||||
if (!localDate) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (localDate instanceof LocalDate) {
|
||||
return localDate.format(DateTimeUtil.DATE_FORMATTER);
|
||||
}
|
||||
return localDate.format(DateTimeUtil.DATE_TIME_FORMATTER);
|
||||
}
|
||||
|
||||
static toDate(localDate: LocalDate | LocalDateTime): Date {
|
||||
if (!localDate) {
|
||||
return null;
|
||||
}
|
||||
return convert(localDate).toDate();
|
||||
}
|
||||
|
||||
static toLocalDate(date: Date): LocalDate {
|
||||
if (!date) {
|
||||
return null;
|
||||
}
|
||||
return LocalDate.from(nativeJs(date));
|
||||
}
|
||||
|
||||
static toLocalDateTime(date: Date): LocalDateTime {
|
||||
if (!date) {
|
||||
return null;
|
||||
}
|
||||
return LocalDateTime.from(nativeJs(date));
|
||||
}
|
||||
|
||||
static toLocalDateBy(strDate: string): LocalDate {
|
||||
if (!strDate) {
|
||||
return null;
|
||||
}
|
||||
return LocalDate.parse(strDate, DateTimeUtil.DATE_FORMATTER);
|
||||
}
|
||||
|
||||
static toLocalDateTimeBy(strDate: string): LocalDateTime {
|
||||
if (!strDate) {
|
||||
return null;
|
||||
}
|
||||
return LocalDateTime.parse(strDate, DateTimeUtil.DATE_TIME_FORMATTER);
|
||||
}
|
||||
}
|
||||
20
backend-api/src/component/component.controller.spec.ts
Normal file
20
backend-api/src/component/component.controller.spec.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { ComponentController } from './component.controller';
|
||||
import { ComponentService } from './component.service';
|
||||
|
||||
describe('ComponentController', () => {
|
||||
let controller: ComponentController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [ComponentController],
|
||||
providers: [ComponentService],
|
||||
}).compile();
|
||||
|
||||
controller = module.get<ComponentController>(ComponentController);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(controller).toBeDefined();
|
||||
});
|
||||
});
|
||||
39
backend-api/src/component/component.controller.ts
Normal file
39
backend-api/src/component/component.controller.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
|
||||
import { ComponentService } from './component.service';
|
||||
import {CreateComponentDto} from "./dto/create-component.dto";
|
||||
|
||||
@Controller('component')
|
||||
export class ComponentController {
|
||||
constructor(private readonly componentService: ComponentService,
|
||||
) {}
|
||||
|
||||
@Post('/seed')
|
||||
async multipleCreate(@Body() createComponents:CreateComponentDto[]) {
|
||||
return this.componentService.multipleCreate(createComponents);
|
||||
}
|
||||
|
||||
@Post()
|
||||
async create(@Body() createComponent:CreateComponentDto) {
|
||||
return this.componentService.create(createComponent);
|
||||
}
|
||||
|
||||
@Get()
|
||||
findAll() {
|
||||
return this.componentService.findAll();
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
findOne(@Param('id') id: number) {
|
||||
return this.componentService.findOne(+id);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
update(@Param('id') id: number, @Body() body: any) {
|
||||
return this.componentService.update(+id, body);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
remove(@Param('id') id: number) {
|
||||
return this.componentService.remove(+id);
|
||||
}
|
||||
}
|
||||
12
backend-api/src/component/component.module.ts
Normal file
12
backend-api/src/component/component.module.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ComponentService } from './component.service';
|
||||
import { ComponentController } from './component.controller';
|
||||
import { TypeOrmModule } from "@nestjs/typeorm";
|
||||
import { Component } from "./entities/component.entity";
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Component])],
|
||||
controllers: [ComponentController],
|
||||
providers: [ComponentService]
|
||||
})
|
||||
export class ComponentModule {}
|
||||
18
backend-api/src/component/component.service.spec.ts
Normal file
18
backend-api/src/component/component.service.spec.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { ComponentService } from './component.service';
|
||||
|
||||
describe('ComponentService', () => {
|
||||
let service: ComponentService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [ComponentService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<ComponentService>(ComponentService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
124
backend-api/src/component/component.service.ts
Normal file
124
backend-api/src/component/component.service.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
import {Injectable} from '@nestjs/common';
|
||||
import {CreateComponentDto} from './dto/create-component.dto';
|
||||
import {InjectRepository} from "@nestjs/typeorm";
|
||||
import {Component} from "./entities/component.entity";
|
||||
import {Repository} from "typeorm";
|
||||
import {YesNo} from "../common/enum/yn.enum";
|
||||
|
||||
@Injectable()
|
||||
export class ComponentService {
|
||||
constructor(
|
||||
@InjectRepository(Component)
|
||||
private componentRepository: Repository<Component>) {
|
||||
}
|
||||
|
||||
async multipleCreate(createComponents: CreateComponentDto[]) {
|
||||
// await this.componentRepository.save(createComponents);
|
||||
// for(let i =0 ; createComponents.length > i; i ++){
|
||||
// const createComponentDto = createComponents[i];
|
||||
// const find_component = await this.componentRepository.findOne({ where: { title: body[i].title }})
|
||||
//
|
||||
// if(!find_component){
|
||||
//
|
||||
// await this.componentRepository.save({
|
||||
// type: body[i].type,
|
||||
// title: body[i].title,
|
||||
// description: body[i].description,
|
||||
// category: body[i].category,
|
||||
// icon: body[i].icon,
|
||||
// option: JSON.stringify(body[i].option),
|
||||
// seq: body[i].seq,
|
||||
// uesYn: body[i].useYn
|
||||
// })
|
||||
// return 'success add a new component'
|
||||
// }
|
||||
// }
|
||||
|
||||
createComponents.forEach((item) => {
|
||||
item.option = JSON.stringify(item.option);
|
||||
})
|
||||
|
||||
return await this.componentRepository.save(createComponents);
|
||||
}
|
||||
|
||||
// async shortCreate(body: CreateComponentDto) {
|
||||
// const find_component = await this.componentRepository.findOne({ where: { title: body.title }})
|
||||
// if(find_component){
|
||||
// return 'exist same widget'
|
||||
// } else {
|
||||
// await this.componentRepository.save({
|
||||
// type: body.type,
|
||||
// title: body.title,
|
||||
// description: body.description,
|
||||
// category: body.category,
|
||||
// icon: body.icon,
|
||||
// option: JSON.stringify(body.option),
|
||||
// seq: body.seq,
|
||||
// uesYn: body.useYn
|
||||
// })
|
||||
// }
|
||||
// return 'This action adds a new widget';
|
||||
// }
|
||||
|
||||
async create(createComponent: CreateComponentDto) {
|
||||
const find_component = await this.componentRepository.findOne({where: {type: createComponent.type}})
|
||||
if (find_component) {
|
||||
return 'exist same widget'
|
||||
} else {
|
||||
const saveObj: CreateComponentDto = new CreateComponentDto();
|
||||
saveObj.type = createComponent.type;
|
||||
saveObj.title = createComponent.title;
|
||||
saveObj.category = createComponent.category;
|
||||
saveObj.option = JSON.stringify(createComponent.option);
|
||||
|
||||
if (createComponent.seq) saveObj.seq = createComponent.seq;
|
||||
if (createComponent.useYn) saveObj.useYn = createComponent.useYn;
|
||||
if (createComponent.icon) saveObj.icon = createComponent.icon;
|
||||
if (createComponent.description) saveObj.description = createComponent.description;
|
||||
|
||||
return await this.componentRepository.save(saveObj);
|
||||
}
|
||||
}
|
||||
|
||||
async findAll() {
|
||||
const components = await this.componentRepository.find({where: {useYn: YesNo.YES}})
|
||||
components.forEach((component, index) => {
|
||||
component.option = JSON.parse(component.option);
|
||||
})
|
||||
|
||||
return components;
|
||||
}
|
||||
|
||||
async findOne(id: number) {
|
||||
const find_component_one = await this.componentRepository.findOne({where: {id: id}})
|
||||
return find_component_one;
|
||||
}
|
||||
|
||||
async update(id: number, body: any) {
|
||||
|
||||
const find_component = await this.componentRepository.findOne({where: {id: id}})
|
||||
if (!find_component) {
|
||||
return 'No exist type'
|
||||
} else {
|
||||
find_component.type = body.type;
|
||||
find_component.title = body.title;
|
||||
find_component.description = body.description;
|
||||
find_component.category = body.category;
|
||||
find_component.option = JSON.stringify(body.option);
|
||||
find_component.icon = body.icon;
|
||||
find_component.seq = body.seq;
|
||||
find_component.useYn = body.useYn;
|
||||
|
||||
await this.componentRepository.save(find_component)
|
||||
|
||||
return 'Success update'
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
async remove(id: number) {
|
||||
await this.componentRepository.delete({id})
|
||||
return `This action removes a #${id} component`;
|
||||
}
|
||||
}
|
||||
35
backend-api/src/component/dto/create-component.dto.ts
Normal file
35
backend-api/src/component/dto/create-component.dto.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import {IsNotEmpty, IsNumber, IsString} from "class-validator";
|
||||
import {YesNo} from "../../common/enum/yn.enum";
|
||||
|
||||
export class CreateComponentDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
type: string;
|
||||
|
||||
@IsString()
|
||||
title: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
description: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
category: string
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
option: string
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
icon: string
|
||||
|
||||
@IsNumber()
|
||||
@IsNotEmpty()
|
||||
seq: number
|
||||
|
||||
@IsString()
|
||||
useYn: YesNo
|
||||
|
||||
}
|
||||
4
backend-api/src/component/dto/update-component.dto.ts
Normal file
4
backend-api/src/component/dto/update-component.dto.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { PartialType } from '@nestjs/swagger';
|
||||
import { CreateComponentDto } from './create-component.dto';
|
||||
|
||||
export class UpdateComponentDto extends PartialType(CreateComponentDto) {}
|
||||
41
backend-api/src/component/entities/component.entity.ts
Normal file
41
backend-api/src/component/entities/component.entity.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import {Column, Entity, OneToMany, PrimaryGeneratedColumn} from "typeorm";
|
||||
import {BaseEntity} from "../../common/entities/base.entity";
|
||||
import {YesNo} from "../../common/enum/yn.enum";
|
||||
import {Widget} from "../../widget/entities/widget.entity";
|
||||
|
||||
@Entity()
|
||||
export class Component extends BaseEntity {
|
||||
@PrimaryGeneratedColumn({comment: '컴포넌트 ID'})
|
||||
id: number
|
||||
|
||||
@Column({unique: true, length: 45, comment: '타입 코드'})
|
||||
type: string
|
||||
|
||||
@Column({length: 100, comment: '타입명'})
|
||||
title: string
|
||||
|
||||
@Column({length: 300, nullable: true, comment: '설명'})
|
||||
description: string
|
||||
|
||||
@Column({length: 45, nullable: true, comment: '분류'})
|
||||
category: string
|
||||
|
||||
@Column({type: 'text', comment: '컴포넌트 속성'})
|
||||
option: string
|
||||
|
||||
@Column({length: 45, nullable: true, comment: '컴포넌트 아이콘 경로'})
|
||||
icon: string
|
||||
|
||||
@Column({comment: '순서', nullable: true})
|
||||
seq: number
|
||||
|
||||
@Column({length: 1, comment: '사용여부', default: YesNo.YES})
|
||||
useYn: YesNo
|
||||
|
||||
@OneToMany(
|
||||
(type => Widget),
|
||||
(widget) => widget.componentId
|
||||
)
|
||||
widgets!: Widget[]
|
||||
|
||||
}
|
||||
20
backend-api/src/dashboard/dashboard.controller.spec.ts
Normal file
20
backend-api/src/dashboard/dashboard.controller.spec.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { DashboardController } from './dashboard.controller';
|
||||
import { DashboardService } from './dashboard.service';
|
||||
|
||||
describe('DashboardController', () => {
|
||||
let controller: DashboardController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [DashboardController],
|
||||
providers: [DashboardService],
|
||||
}).compile();
|
||||
|
||||
controller = module.get<DashboardController>(DashboardController);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(controller).toBeDefined();
|
||||
});
|
||||
});
|
||||
34
backend-api/src/dashboard/dashboard.controller.ts
Normal file
34
backend-api/src/dashboard/dashboard.controller.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
|
||||
import { DashboardService } from './dashboard.service';
|
||||
import { CreateDashboardDto } from './dto/create-dashboard.dto';
|
||||
import { UpdateDashboardDto } from './dto/update-dashboard.dto';
|
||||
|
||||
@Controller('dashboard')
|
||||
export class DashboardController {
|
||||
constructor(private readonly dashboardService: DashboardService) {}
|
||||
|
||||
@Post()
|
||||
create(@Body() createDashboardDto: CreateDashboardDto) {
|
||||
return this.dashboardService.create(createDashboardDto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
findAll() {
|
||||
return this.dashboardService.findAll();
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
findOne(@Param('id') id: string) {
|
||||
return this.dashboardService.findOne(+id);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
update(@Param('id') id: string, @Body() updateDashboardDto: UpdateDashboardDto) {
|
||||
return this.dashboardService.update(+id, updateDashboardDto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
remove(@Param('id') id: string) {
|
||||
return this.dashboardService.remove(+id);
|
||||
}
|
||||
}
|
||||
9
backend-api/src/dashboard/dashboard.module.ts
Normal file
9
backend-api/src/dashboard/dashboard.module.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { DashboardService } from './dashboard.service';
|
||||
import { DashboardController } from './dashboard.controller';
|
||||
|
||||
@Module({
|
||||
controllers: [DashboardController],
|
||||
providers: [DashboardService],
|
||||
})
|
||||
export class DashboardModule {}
|
||||
18
backend-api/src/dashboard/dashboard.service.spec.ts
Normal file
18
backend-api/src/dashboard/dashboard.service.spec.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { DashboardService } from './dashboard.service';
|
||||
|
||||
describe('DashboardService', () => {
|
||||
let service: DashboardService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [DashboardService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<DashboardService>(DashboardService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
26
backend-api/src/dashboard/dashboard.service.ts
Normal file
26
backend-api/src/dashboard/dashboard.service.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { CreateDashboardDto } from './dto/create-dashboard.dto';
|
||||
import { UpdateDashboardDto } from './dto/update-dashboard.dto';
|
||||
|
||||
@Injectable()
|
||||
export class DashboardService {
|
||||
create(createDashboardDto: CreateDashboardDto) {
|
||||
return 'This action adds a new dashboard';
|
||||
}
|
||||
|
||||
findAll() {
|
||||
return `This action returns all dashboard`;
|
||||
}
|
||||
|
||||
findOne(id: number) {
|
||||
return `This action returns a #${id} dashboard`;
|
||||
}
|
||||
|
||||
update(id: number, updateDashboardDto: UpdateDashboardDto) {
|
||||
return `This action updates a #${id} dashboard`;
|
||||
}
|
||||
|
||||
remove(id: number) {
|
||||
return `This action removes a #${id} dashboard`;
|
||||
}
|
||||
}
|
||||
1
backend-api/src/dashboard/dto/create-dashboard.dto.ts
Normal file
1
backend-api/src/dashboard/dto/create-dashboard.dto.ts
Normal file
@@ -0,0 +1 @@
|
||||
export class CreateDashboardDto {}
|
||||
4
backend-api/src/dashboard/dto/update-dashboard.dto.ts
Normal file
4
backend-api/src/dashboard/dto/update-dashboard.dto.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { PartialType } from '@nestjs/mapped-types';
|
||||
import { CreateDashboardDto } from './create-dashboard.dto';
|
||||
|
||||
export class UpdateDashboardDto extends PartialType(CreateDashboardDto) {}
|
||||
32
backend-api/src/dashboard/entities/dashboard.entity.ts
Normal file
32
backend-api/src/dashboard/entities/dashboard.entity.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import {Column, Entity, ManyToOne, PrimaryGeneratedColumn} from "typeorm";
|
||||
import {BaseEntity} from "../../common/entities/base.entity";
|
||||
import {YesNo} from "../../common/enum/yn.enum";
|
||||
import {Template} from "../../template/entities/template.entity";
|
||||
|
||||
@Entity()
|
||||
export class Dashboard extends BaseEntity {
|
||||
@PrimaryGeneratedColumn({comment: '대시보드 ID'})
|
||||
id: number
|
||||
|
||||
@Column({length: 300, comment: '대시보드명'})
|
||||
title: string
|
||||
|
||||
@Column({nullable: true, comment: '템플릿 ID'})
|
||||
templateId: number
|
||||
|
||||
@Column({type: 'text', nullable: true, comment: '레이아웃 정보'})
|
||||
layout: string
|
||||
|
||||
@Column({comment: '순서', nullable: true})
|
||||
seq: number
|
||||
|
||||
@Column({length: 1, default: YesNo.NO, comment: '삭제여부'})
|
||||
delYn: YesNo
|
||||
|
||||
@ManyToOne(
|
||||
(type) => Template,
|
||||
(template) => template.dashboards
|
||||
)
|
||||
template!: Template
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { ConnectionService } from './connection.service';
|
||||
|
||||
describe('ConnectionService', () => {
|
||||
let service: ConnectionService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [ConnectionService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<ConnectionService>(ConnectionService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
79
backend-api/src/database/connection/connection.service.ts
Normal file
79
backend-api/src/database/connection/connection.service.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { CreateDatabaseDto } from '../dto/create-database.dto';
|
||||
import { QueryExecuteDto } from '../dto/query-execute.dto';
|
||||
import { DatabaseService } from '../database.service';
|
||||
import { Knex, knex } from 'knex';
|
||||
|
||||
const knexConnections = new Map<number, Knex>();
|
||||
|
||||
@Injectable()
|
||||
export class ConnectionService {
|
||||
constructor(private readonly databaseService: DatabaseService) {}
|
||||
|
||||
/**
|
||||
* Knex 객체 생성 후 pool에 추가
|
||||
* @param id
|
||||
* @param options
|
||||
*/
|
||||
addKnex(id: number, options: Knex.Config) {
|
||||
if (!this.hasKnex(id)) {
|
||||
knexConnections.set(id, knex(options));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Knex 객체 pool에서 삭제
|
||||
* @param id
|
||||
*/
|
||||
removeKnex(id: number) {
|
||||
knexConnections.delete(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Knex 객체 pool 안에 존재 유무
|
||||
* @param id
|
||||
*/
|
||||
hasKnex(id: number): boolean {
|
||||
return knexConnections.has(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Knex 객체 가져오기 - 만약 없으면 가져오기
|
||||
* @param id
|
||||
*/
|
||||
async getKnex(id: number): Promise<Knex> {
|
||||
if (!this.hasKnex(id)) {
|
||||
const one = await this.databaseService.findOne(id);
|
||||
this.addKnex(id, one.knexConfig as Knex.Config);
|
||||
}
|
||||
return knexConnections.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 데이터베이스 연결 테스트
|
||||
* @param createDatabaseDto
|
||||
*/
|
||||
async testConnection(createDatabaseDto: CreateDatabaseDto): Promise<boolean> {
|
||||
let _knex = knex(createDatabaseDto.knexConfig as Knex.Config);
|
||||
try {
|
||||
await _knex.raw('SELECT 1');
|
||||
console.log('knex connected');
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.log('knex not connected');
|
||||
console.error(e);
|
||||
return false;
|
||||
} finally {
|
||||
await _knex.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 쿼리 실행
|
||||
* @param queryExecuteDto
|
||||
*/
|
||||
async executeQuery(queryExecuteDto: QueryExecuteDto) {
|
||||
const knex = await this.getKnex(queryExecuteDto.id);
|
||||
return knex.raw(queryExecuteDto.query);
|
||||
}
|
||||
}
|
||||
20
backend-api/src/database/database.controller.spec.ts
Normal file
20
backend-api/src/database/database.controller.spec.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { DatabaseController } from './database.controller';
|
||||
import { DatabaseService } from './database.service';
|
||||
|
||||
describe('DatabaseController', () => {
|
||||
let controller: DatabaseController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [DatabaseController],
|
||||
providers: [DatabaseService],
|
||||
}).compile();
|
||||
|
||||
controller = module.get<DatabaseController>(DatabaseController);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(controller).toBeDefined();
|
||||
});
|
||||
});
|
||||
49
backend-api/src/database/database.controller.ts
Normal file
49
backend-api/src/database/database.controller.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
|
||||
import { DatabaseService } from './database.service';
|
||||
import { CreateDatabaseDto } from './dto/create-database.dto';
|
||||
import { UpdateDatabaseDto } from './dto/update-database.dto';
|
||||
import { QueryExecuteDto } from './dto/query-execute.dto';
|
||||
import { ConnectionService } from './connection/connection.service';
|
||||
|
||||
@Controller('database')
|
||||
export class DatabaseController {
|
||||
constructor(
|
||||
private readonly databaseService: DatabaseService,
|
||||
private readonly connectionService: ConnectionService,
|
||||
) {}
|
||||
|
||||
@Post()
|
||||
create(@Body() createDatabaseDto: CreateDatabaseDto) {
|
||||
return this.databaseService.create(createDatabaseDto);
|
||||
}
|
||||
|
||||
@Post('test')
|
||||
testConnection(@Body() createDatabaseDto: CreateDatabaseDto) {
|
||||
return this.connectionService.testConnection(createDatabaseDto);
|
||||
}
|
||||
|
||||
@Post('execute')
|
||||
executeQuery(@Body() queryExecuteDto: QueryExecuteDto) {
|
||||
return this.connectionService.executeQuery(queryExecuteDto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
findAll() {
|
||||
return this.databaseService.findAll();
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
findOne(@Param('id') id: string) {
|
||||
return this.databaseService.findOne(+id);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
update(@Param('id') id: string, @Body() updateDatabaseDto: UpdateDatabaseDto) {
|
||||
return this.databaseService.update(+id, updateDatabaseDto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
remove(@Param('id') id: string) {
|
||||
return this.databaseService.remove(+id);
|
||||
}
|
||||
}
|
||||
13
backend-api/src/database/database.module.ts
Normal file
13
backend-api/src/database/database.module.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { DatabaseService } from './database.service';
|
||||
import { DatabaseController } from './database.controller';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Database } from './entities/database.entity';
|
||||
import { ConnectionService } from './connection/connection.service';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Database])],
|
||||
controllers: [DatabaseController],
|
||||
providers: [DatabaseService, ConnectionService],
|
||||
})
|
||||
export class DatabaseModule {}
|
||||
18
backend-api/src/database/database.service.spec.ts
Normal file
18
backend-api/src/database/database.service.spec.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { DatabaseService } from './database.service';
|
||||
|
||||
describe('DatabaseService', () => {
|
||||
let service: DatabaseService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [DatabaseService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<DatabaseService>(DatabaseService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
41
backend-api/src/database/database.service.ts
Normal file
41
backend-api/src/database/database.service.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { CreateDatabaseDto } from './dto/create-database.dto';
|
||||
import { UpdateDatabaseDto } from './dto/update-database.dto';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Database } from './entities/database.entity';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
@Injectable()
|
||||
export class DatabaseService {
|
||||
constructor(@InjectRepository(Database) private databaseRepository: Repository<Database>) {}
|
||||
|
||||
getHello() {
|
||||
return 'Hello';
|
||||
}
|
||||
|
||||
async create(createDatabaseDto: CreateDatabaseDto): Promise<Database> {
|
||||
const database = Database.toDto(createDatabaseDto);
|
||||
return await this.databaseRepository.save(database);
|
||||
}
|
||||
|
||||
async findAll(): Promise<Database[]> {
|
||||
return await this.databaseRepository.find();
|
||||
}
|
||||
|
||||
async findOne(id: number): Promise<Database> {
|
||||
return await this.databaseRepository.findOne({ where: { id } });
|
||||
}
|
||||
|
||||
async update(id: number, updateDatabaseDto: UpdateDatabaseDto) {
|
||||
const one = await this.findOne(id);
|
||||
if (!one) throw new NotFoundException(`조건에 맞는 데이터베이스를 찾지 못했습니다. id:${id}`);
|
||||
one.name = updateDatabaseDto.name;
|
||||
return await this.databaseRepository.save(one);
|
||||
}
|
||||
|
||||
async remove(id: number) {
|
||||
const one = await this.findOne(id);
|
||||
if (!one) throw new NotFoundException(`조건에 맞는 데이터베이스를 찾지 못했습니다. id:${id}`);
|
||||
return this.databaseRepository.remove(one);
|
||||
}
|
||||
}
|
||||
44
backend-api/src/database/dto/create-database.dto.ts
Normal file
44
backend-api/src/database/dto/create-database.dto.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class CreateDatabaseDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@ApiProperty({
|
||||
example: 'mysql 데이터베이스',
|
||||
description: '데이터베이스 이름',
|
||||
})
|
||||
name: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@ApiProperty({
|
||||
example: '상세 내용',
|
||||
description: '데이터베이스 상세 내용',
|
||||
})
|
||||
description: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@ApiProperty({
|
||||
example: '{}',
|
||||
description: '설정 JSON 상세',
|
||||
})
|
||||
knexConfig: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@ApiProperty({
|
||||
example: 'mysql',
|
||||
description: '데이터베이스 엔진',
|
||||
})
|
||||
engine: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@ApiProperty({
|
||||
example: 'Asia/Seoul',
|
||||
description: '서비스 타임존',
|
||||
})
|
||||
timezone: string;
|
||||
}
|
||||
20
backend-api/src/database/dto/query-execute.dto.ts
Normal file
20
backend-api/src/database/dto/query-execute.dto.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { IsNotEmpty, IsNumber, IsString } from 'class-validator';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class QueryExecuteDto {
|
||||
@IsNumber()
|
||||
@IsNotEmpty()
|
||||
@ApiProperty({
|
||||
example: '1',
|
||||
description: 'database id',
|
||||
})
|
||||
id: number;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@ApiProperty({
|
||||
example: 'select * from sample_table1',
|
||||
description: '실행 쿼리',
|
||||
})
|
||||
query: string;
|
||||
}
|
||||
4
backend-api/src/database/dto/update-database.dto.ts
Normal file
4
backend-api/src/database/dto/update-database.dto.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { PartialType } from '@nestjs/mapped-types';
|
||||
import { CreateDatabaseDto } from './create-database.dto';
|
||||
|
||||
export class UpdateDatabaseDto extends PartialType(CreateDatabaseDto) {}
|
||||
48
backend-api/src/database/entities/database.entity.ts
Normal file
48
backend-api/src/database/entities/database.entity.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import { BaseEntity } from '../../common/entities/base.entity';
|
||||
import { CreateDatabaseDto } from '../dto/create-database.dto';
|
||||
|
||||
@Entity()
|
||||
export class Database extends BaseEntity {
|
||||
@PrimaryGeneratedColumn({ comment: '데이터베이스 ID' })
|
||||
id: number;
|
||||
|
||||
@Column({ length: 300, comment: '데이터베이스명' })
|
||||
name: string;
|
||||
|
||||
@Column({ length: 1000, nullable: true, comment: '설명' })
|
||||
description: string;
|
||||
|
||||
@Column({ type: 'text', comment: '속성' })
|
||||
knexConfig: string; // 기타 속성 json으로 .. host, schema, filePath...
|
||||
|
||||
@Column({ length: 100, comment: '데이터베이스 구분' })
|
||||
engine: string;
|
||||
|
||||
@Column({ length: 100, comment: '타임존', nullable: true })
|
||||
timezone: string;
|
||||
|
||||
static of(
|
||||
name: string,
|
||||
description: string,
|
||||
details: string,
|
||||
engine: string,
|
||||
timezone: string,
|
||||
): Database {
|
||||
const obj = new Database();
|
||||
obj.name = name;
|
||||
obj.description = description;
|
||||
obj.knexConfig = details;
|
||||
obj.engine = engine;
|
||||
obj.timezone = timezone;
|
||||
return obj;
|
||||
}
|
||||
|
||||
static toDto(dto: CreateDatabaseDto): Database {
|
||||
return Database.of(dto.name, dto.description, dto.knexConfig, dto.engine, dto.timezone);
|
||||
}
|
||||
|
||||
getFullDescription(): string {
|
||||
return `${this.name} ${this.description}`;
|
||||
}
|
||||
}
|
||||
20
backend-api/src/dataset/dataset.controller.spec.ts
Normal file
20
backend-api/src/dataset/dataset.controller.spec.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { DatasetController } from './dataset.controller';
|
||||
import { DatasetService } from './dataset.service';
|
||||
|
||||
describe('DatasetController', () => {
|
||||
let controller: DatasetController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [DatasetController],
|
||||
providers: [DatasetService],
|
||||
}).compile();
|
||||
|
||||
controller = module.get<DatasetController>(DatasetController);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(controller).toBeDefined();
|
||||
});
|
||||
});
|
||||
34
backend-api/src/dataset/dataset.controller.ts
Normal file
34
backend-api/src/dataset/dataset.controller.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
|
||||
import { DatasetService } from './dataset.service';
|
||||
import { CreateDatasetDto } from './dto/create-dataset.dto';
|
||||
import { UpdateDatasetDto } from './dto/update-dataset.dto';
|
||||
|
||||
@Controller('dataset')
|
||||
export class DatasetController {
|
||||
constructor(private readonly datasetService: DatasetService) {}
|
||||
|
||||
@Post()
|
||||
create(@Body() createDatasetDto: CreateDatasetDto) {
|
||||
return this.datasetService.create(createDatasetDto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
findAll() {
|
||||
return this.datasetService.findAll();
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
findOne(@Param('id') id: string) {
|
||||
return this.datasetService.findOne(+id);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
update(@Param('id') id: string, @Body() updateDatasetDto: UpdateDatasetDto) {
|
||||
return this.datasetService.update(+id, updateDatasetDto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
remove(@Param('id') id: string) {
|
||||
return this.datasetService.remove(+id);
|
||||
}
|
||||
}
|
||||
12
backend-api/src/dataset/dataset.module.ts
Normal file
12
backend-api/src/dataset/dataset.module.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { DatasetService } from './dataset.service';
|
||||
import { DatasetController } from './dataset.controller';
|
||||
import {TypeOrmModule} from "@nestjs/typeorm";
|
||||
import {Dataset} from "./entities/dataset.entity";
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Dataset])],
|
||||
controllers: [DatasetController],
|
||||
providers: [DatasetService],
|
||||
})
|
||||
export class DatasetModule {}
|
||||
18
backend-api/src/dataset/dataset.service.spec.ts
Normal file
18
backend-api/src/dataset/dataset.service.spec.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { DatasetService } from './dataset.service';
|
||||
|
||||
describe('DatasetService', () => {
|
||||
let service: DatasetService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [DatasetService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<DatasetService>(DatasetService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
26
backend-api/src/dataset/dataset.service.ts
Normal file
26
backend-api/src/dataset/dataset.service.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { CreateDatasetDto } from './dto/create-dataset.dto';
|
||||
import { UpdateDatasetDto } from './dto/update-dataset.dto';
|
||||
|
||||
@Injectable()
|
||||
export class DatasetService {
|
||||
create(createDatasetDto: CreateDatasetDto) {
|
||||
return 'This action adds a new dataset';
|
||||
}
|
||||
|
||||
findAll() {
|
||||
return `This action returns all dataset`;
|
||||
}
|
||||
|
||||
findOne(id: number) {
|
||||
return `This action returns a #${id} dataset`;
|
||||
}
|
||||
|
||||
update(id: number, updateDatasetDto: UpdateDatasetDto) {
|
||||
return `This action updates a #${id} dataset`;
|
||||
}
|
||||
|
||||
remove(id: number) {
|
||||
return `This action removes a #${id} dataset`;
|
||||
}
|
||||
}
|
||||
1
backend-api/src/dataset/dto/create-dataset.dto.ts
Normal file
1
backend-api/src/dataset/dto/create-dataset.dto.ts
Normal file
@@ -0,0 +1 @@
|
||||
export class CreateDatasetDto {}
|
||||
4
backend-api/src/dataset/dto/update-dataset.dto.ts
Normal file
4
backend-api/src/dataset/dto/update-dataset.dto.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { PartialType } from '@nestjs/mapped-types';
|
||||
import { CreateDatasetDto } from './create-dataset.dto';
|
||||
|
||||
export class UpdateDatasetDto extends PartialType(CreateDatasetDto) {}
|
||||
26
backend-api/src/dataset/entities/dataset.entity.ts
Normal file
26
backend-api/src/dataset/entities/dataset.entity.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import {Column, Entity, JoinTable, ManyToMany, OneToMany, PrimaryGeneratedColumn} from "typeorm";
|
||||
import {Database} from "../../database/entities/database.entity";
|
||||
import {BaseEntity} from "../../common/entities/base.entity";
|
||||
import {Widget} from "../../widget/entities/widget.entity";
|
||||
|
||||
@Entity()
|
||||
export class Dataset extends BaseEntity {
|
||||
@PrimaryGeneratedColumn({comment: '데이터셋 ID'})
|
||||
id: number
|
||||
|
||||
@Column({comment: '데이터셋명', nullable: true})
|
||||
title: string
|
||||
|
||||
@Column({comment: '데이터베이스 ID'})
|
||||
databaseId: number
|
||||
|
||||
@Column({type: 'text', comment: '조회 sql'})
|
||||
query: string
|
||||
|
||||
@OneToMany(
|
||||
(type) => Widget,
|
||||
(widget) => widget.datasetId
|
||||
)
|
||||
widgets!: Widget
|
||||
|
||||
}
|
||||
15
backend-api/src/main.ts
Normal file
15
backend-api/src/main.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import {NestFactory} from '@nestjs/core';
|
||||
import {AppModule} from './app.module';
|
||||
import express from "express";
|
||||
import {ExpressAdapter} from "@nestjs/platform-express";
|
||||
import {HttpExceptionFilter} from "./nest-utils/http-exception.filter";
|
||||
|
||||
async function bootstrap() {
|
||||
const expressApp = express();
|
||||
const app = await NestFactory.create(AppModule, new ExpressAdapter(expressApp));
|
||||
app.useGlobalFilters(new HttpExceptionFilter());
|
||||
// const app = await NestFactory.create(AppModule);
|
||||
await app.listen(3000);
|
||||
}
|
||||
|
||||
bootstrap();
|
||||
26
backend-api/src/nest-utils/http-exception.filter.ts
Normal file
26
backend-api/src/nest-utils/http-exception.filter.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
|
||||
import { Response } from 'express';
|
||||
|
||||
@Catch(HttpException)
|
||||
export class HttpExceptionFilter implements ExceptionFilter {
|
||||
catch(exception: HttpException, host: ArgumentsHost) {
|
||||
const ctx = host.switchToHttp();
|
||||
const response = ctx.getResponse<Response>();
|
||||
const status = exception.getStatus();
|
||||
const err = exception.getResponse() as string | { error: string; statusCode: 400; message: string[] };
|
||||
// let msg = '';
|
||||
if (typeof err !== 'string' && err.error === 'Bad Request') {
|
||||
return response.status(status).json({
|
||||
success: false,
|
||||
code: status,
|
||||
data: err.message,
|
||||
});
|
||||
}
|
||||
|
||||
response.status(status).json({
|
||||
success: false,
|
||||
code: status,
|
||||
data: err,
|
||||
});
|
||||
}
|
||||
}
|
||||
54
backend-api/src/seed/seed_data.ts
Normal file
54
backend-api/src/seed/seed_data.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
// import {Connection} from "typeorm";
|
||||
// import {Seeder, Factory} from 'typeorm-seeding';
|
||||
// import {Component} from "../component/entities/component.entity";
|
||||
//
|
||||
//
|
||||
// export class CreateInitialData implements Seeder {
|
||||
// public async run(factory: Factory, connection: Connection): Promise<any> {
|
||||
// await connection
|
||||
// .createQueryBuilder()
|
||||
// .insert()
|
||||
// .into(Component)
|
||||
// .values([
|
||||
// {
|
||||
// type: 'CHART_LINE',
|
||||
// title: '선형 차트',
|
||||
// category: 'LINE',
|
||||
// icon: 'icon/line-chart.png',
|
||||
// description: 'Line Chart',
|
||||
// option: `{
|
||||
// "title": "test",
|
||||
// "xField": "",
|
||||
// "series": [
|
||||
// {
|
||||
// "field": "",
|
||||
// "color": "#5470c6",
|
||||
// "aggregation": "",
|
||||
// },
|
||||
// ],
|
||||
// "legendPosition": "left",
|
||||
// }`,
|
||||
// },
|
||||
// {
|
||||
// type: 'CHART_AREA',
|
||||
// title: '영역형 차트',
|
||||
// category: 'AREA',
|
||||
// icon: 'icon/area-chart.png',
|
||||
// description: 'Area Chart',
|
||||
// option: `{
|
||||
// "title": "",
|
||||
// "xField": "",
|
||||
// "series": [
|
||||
// {
|
||||
// "field": "",
|
||||
// "color": "#5470c6",
|
||||
// "aggregation": "",
|
||||
// },
|
||||
// ],
|
||||
// "legendPosition": "left",
|
||||
// }`,
|
||||
// }
|
||||
// ]
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
54
backend-api/src/serverless.ts
Normal file
54
backend-api/src/serverless.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
// lambda.ts
|
||||
import { Handler, Context } from 'aws-lambda';
|
||||
import { Server } from 'http';
|
||||
import { createServer, proxy } from 'aws-serverless-express';
|
||||
import { eventContext } from 'aws-serverless-express/middleware';
|
||||
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { ExpressAdapter } from '@nestjs/platform-express';
|
||||
import { AppModule } from './app.module';
|
||||
|
||||
import express from 'express';
|
||||
// import { logger } from './core/middleware/logger.middleware';
|
||||
import cookieParser from 'cookie-parser';
|
||||
|
||||
// NOTE: If you get ERR_CONTENT_DECODING_FAILED in your browser, this is likely
|
||||
// due to a compressed response (e.g. gzip) which has not been handled correctly
|
||||
// by aws-serverless-express and/or API Gateway. Add the necessary MIME types to
|
||||
// binaryMimeTypes below
|
||||
const binaryMimeTypes: string[] = ['application/octet-stream', 'image/png', 'image/jpeg'];
|
||||
|
||||
let cachedServer: Server;
|
||||
|
||||
async function bootstrapServer(): Promise<Server> {
|
||||
// some legacy browsers (IE11, various SmartTVs) choke on 204
|
||||
if (!cachedServer) {
|
||||
const expressApp = express();
|
||||
const nestApp = await NestFactory.create(AppModule, new ExpressAdapter(expressApp), {
|
||||
logger: console,
|
||||
});
|
||||
nestApp.setGlobalPrefix('v1');
|
||||
nestApp.use(cookieParser());
|
||||
nestApp.use(eventContext());
|
||||
await nestApp.init();
|
||||
cachedServer = createServer(expressApp, undefined, binaryMimeTypes);
|
||||
}
|
||||
return cachedServer;
|
||||
}
|
||||
|
||||
export const handler: Handler = async (event: any, context: Context) => {
|
||||
// 아래 파일 업로드 시 안되는 문제 때문에 추가.
|
||||
// if (
|
||||
// event.body &&
|
||||
// event.headers['Content-Type'] &&
|
||||
// event.headers['Content-Type'].includes('multipart/form-data')
|
||||
// ) {
|
||||
// // before => typeof event.body === string
|
||||
// console.log('file Upoad 해야 한다.~~~~~~~~~~~~~~~~~~~~ : ', event.body);
|
||||
// event.body = Buffer.from(event.body, 'binary') as unknown as string;
|
||||
// // after => typeof event.body === <Buffer ...>
|
||||
// }
|
||||
|
||||
cachedServer = await bootstrapServer();
|
||||
return proxy(cachedServer, event, context, 'PROMISE').promise;
|
||||
};
|
||||
20
backend-api/src/template/dto/create-template-item.dto.ts
Normal file
20
backend-api/src/template/dto/create-template-item.dto.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import {IsNumber, IsOptional, IsString} from "class-validator";
|
||||
|
||||
export class CreateTemplateItemDto {
|
||||
@IsNumber()
|
||||
readonly templateId: number;
|
||||
@IsNumber()
|
||||
readonly x: number;
|
||||
@IsNumber()
|
||||
readonly y: number;
|
||||
@IsNumber()
|
||||
readonly width: number;
|
||||
@IsNumber()
|
||||
readonly height: number;
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
readonly recommendCategory: string;
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
readonly recommendType: string;
|
||||
}
|
||||
9
backend-api/src/template/dto/create-template.dto.ts
Normal file
9
backend-api/src/template/dto/create-template.dto.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import {IsOptional, IsString} from "class-validator";
|
||||
|
||||
export class CreateTemplateDto {
|
||||
@IsString()
|
||||
readonly title: string;
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
readonly description: string;
|
||||
}
|
||||
19
backend-api/src/template/dto/item-info.dto.ts
Normal file
19
backend-api/src/template/dto/item-info.dto.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import {TemplateItem} from "../entities/template-item.entity";
|
||||
|
||||
export class ItemInfoDto {
|
||||
x: number;
|
||||
y: number;
|
||||
w: number;
|
||||
h: number;
|
||||
category: string;
|
||||
type: string;
|
||||
|
||||
constructor(templateItem:TemplateItem) {
|
||||
this.x = templateItem.x;
|
||||
this.y = templateItem.y;
|
||||
this.w = templateItem.width;
|
||||
this.h = templateItem.height;
|
||||
this.category = templateItem.recommendCategory;
|
||||
this.type = templateItem.recommendType;
|
||||
};
|
||||
}
|
||||
15
backend-api/src/template/dto/template-info.dto.ts
Normal file
15
backend-api/src/template/dto/template-info.dto.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import {Template} from "../entities/template.entity";
|
||||
import {ItemInfoDto} from "./item-info.dto";
|
||||
|
||||
export class TemplateInfoDto {
|
||||
id: number;
|
||||
title: string;
|
||||
description: string;
|
||||
layout: ItemInfoDto[];
|
||||
|
||||
constructor(template: Template) {
|
||||
this.id = template.id;
|
||||
this.title = template.title;
|
||||
this.description = template.description;
|
||||
};
|
||||
}
|
||||
4
backend-api/src/template/dto/update-template-item.dto.ts
Normal file
4
backend-api/src/template/dto/update-template-item.dto.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import {PartialType} from '@nestjs/mapped-types';
|
||||
import {CreateTemplateItemDto} from "./create-template-item.dto";
|
||||
|
||||
export class UpdateTemplateItemDto extends PartialType(CreateTemplateItemDto) {}
|
||||
4
backend-api/src/template/dto/update-template.dto.ts
Normal file
4
backend-api/src/template/dto/update-template.dto.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { PartialType } from '@nestjs/mapped-types';
|
||||
import { CreateTemplateDto } from './create-template.dto';
|
||||
|
||||
export class UpdateTemplateDto extends PartialType(CreateTemplateDto) {}
|
||||
39
backend-api/src/template/entities/template-item.entity.ts
Normal file
39
backend-api/src/template/entities/template-item.entity.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import {Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn} from "typeorm";
|
||||
import {BaseEntity} from "../../common/entities/base.entity";
|
||||
import {YesNo} from "../../common/enum/yn.enum";
|
||||
import {Dashboard} from "../../dashboard/entities/dashboard.entity";
|
||||
import {Template} from "./template.entity";
|
||||
|
||||
@Entity()
|
||||
export class TemplateItem extends BaseEntity {
|
||||
@PrimaryGeneratedColumn({comment: '템플릿 아이템 ID'})
|
||||
id: number
|
||||
|
||||
@Column({comment: '템플릿 ID'})
|
||||
templateId: number
|
||||
|
||||
@Column({comment: 'x좌표 값'})
|
||||
x: number
|
||||
|
||||
@Column({comment: 'y좌표 값'})
|
||||
y: number
|
||||
|
||||
@Column({comment: '너비'})
|
||||
width: number
|
||||
|
||||
@Column({comment: '높이'})
|
||||
height: number
|
||||
|
||||
@Column({length: 45, nullable: true, comment: '컴포넌트 분류'})
|
||||
recommendCategory: string
|
||||
|
||||
@Column({length: 45, nullable: true, comment: '컴포넌트 타입 코드'})
|
||||
recommendType: string
|
||||
|
||||
@ManyToOne(
|
||||
(type => Template),
|
||||
(template) => template.id
|
||||
)
|
||||
template!: Template[]
|
||||
|
||||
}
|
||||
36
backend-api/src/template/entities/template.entity.ts
Normal file
36
backend-api/src/template/entities/template.entity.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import {Column, Entity, OneToMany, PrimaryGeneratedColumn} from "typeorm";
|
||||
import {BaseEntity} from "../../common/entities/base.entity";
|
||||
import {YesNo} from "../../common/enum/yn.enum";
|
||||
import {Dashboard} from "../../dashboard/entities/dashboard.entity";
|
||||
import {TemplateItem} from "./template-item.entity";
|
||||
|
||||
@Entity()
|
||||
export class Template extends BaseEntity {
|
||||
@PrimaryGeneratedColumn({comment: '템플릿 ID'})
|
||||
id: number
|
||||
|
||||
@Column({length: 300, nullable: true, comment: '템플릿명'})
|
||||
title: string
|
||||
|
||||
@Column({length: 1000, nullable: true, comment: '템플릿 설명'})
|
||||
description: string
|
||||
//
|
||||
// @Column({type: 'text', comment: '레이아웃'})
|
||||
// layout: string
|
||||
|
||||
@Column({length: 1, default: YesNo.YES, comment: '사용여부'})
|
||||
useYn: YesNo
|
||||
|
||||
@OneToMany(
|
||||
(type => Dashboard),
|
||||
(dashboard) => dashboard.template
|
||||
)
|
||||
dashboards!: Dashboard[]
|
||||
|
||||
@OneToMany(
|
||||
(type => TemplateItem),
|
||||
(templateItem) => templateItem.templateId
|
||||
)
|
||||
templateItems!: TemplateItem[]
|
||||
|
||||
}
|
||||
20
backend-api/src/template/template.controller.spec.ts
Normal file
20
backend-api/src/template/template.controller.spec.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { TemplateController } from './template.controller';
|
||||
import { TemplateService } from './template.service';
|
||||
|
||||
describe('TemplateController', () => {
|
||||
let controller: TemplateController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [TemplateController],
|
||||
providers: [TemplateService],
|
||||
}).compile();
|
||||
|
||||
controller = module.get<TemplateController>(TemplateController);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(controller).toBeDefined();
|
||||
});
|
||||
});
|
||||
88
backend-api/src/template/template.controller.ts
Normal file
88
backend-api/src/template/template.controller.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import {Body, Controller, Delete, Get, HttpStatus, Param, Post, Put, Res} from '@nestjs/common';
|
||||
import {TemplateService} from './template.service';
|
||||
import {CreateTemplateDto} from './dto/create-template.dto';
|
||||
import {UpdateTemplateDto} from './dto/update-template.dto';
|
||||
import {CreateTemplateItemDto} from "./dto/create-template-item.dto";
|
||||
import {UpdateTemplateItemDto} from "./dto/update-template-item.dto";
|
||||
import {TemplateInfoDto} from "./dto/template-info.dto";
|
||||
|
||||
@Controller('template')
|
||||
export class TemplateController {
|
||||
constructor(private readonly templateService: TemplateService) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 템플릿 생성
|
||||
* @param createTemplateDto
|
||||
*/
|
||||
@Post()
|
||||
create(@Body() createTemplateDto: CreateTemplateDto) {
|
||||
return this.templateService.create(createTemplateDto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 템플릿 상세 아이템 생성
|
||||
* @param createTemplateItemDto
|
||||
*/
|
||||
@Post('/item')
|
||||
createItem(@Body() createTemplateItemDto: CreateTemplateItemDto) {
|
||||
return this.templateService.createItem(createTemplateItemDto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 템플릿 목록 조회
|
||||
*/
|
||||
@Get()
|
||||
findAll() {
|
||||
return this.templateService.findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* 템플릿 단건 조회
|
||||
* @param id
|
||||
*/
|
||||
@Get(':id')
|
||||
async findOne(@Res() res, @Param('id') id: number) {
|
||||
const resultTemplate: TemplateInfoDto = await this.templateService.findOne(id);
|
||||
return res.status(HttpStatus.OK).json(resultTemplate);
|
||||
}
|
||||
|
||||
/**
|
||||
* 템플릿 단건 업데이트
|
||||
* @param id
|
||||
* @param updateTemplateDto
|
||||
*/
|
||||
@Put(':id')
|
||||
update(@Param('id') id: string, @Body() updateTemplateDto: UpdateTemplateDto) {
|
||||
return this.templateService.update(+id, updateTemplateDto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 템플릿 상세 아이템 단건 업데이트
|
||||
* @param id
|
||||
* @param updateTemplateDto
|
||||
*/
|
||||
@Put('/item/:id')
|
||||
updateItem(@Param('id') id: string, @Body() updateTemplateItemDto: UpdateTemplateItemDto) {
|
||||
return this.templateService.updateItem(+id, updateTemplateItemDto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 템플릿 삭제 (사용여부 N 처리)
|
||||
* @param id
|
||||
*/
|
||||
@Delete(':id')
|
||||
remove(@Param('id') id: string) {
|
||||
return this.templateService.remove(+id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 템플릿 추천 목록 조회
|
||||
* @param widgets
|
||||
*/
|
||||
//todo:: yhs:: 추천 알고리즘 적용해서 조회해 와야함
|
||||
@Post('/recommend')
|
||||
findRecommendAll(@Body() widgets: any[]) {
|
||||
return this.templateService.findRecommendTemplates(widgets);
|
||||
}
|
||||
}
|
||||
14
backend-api/src/template/template.module.ts
Normal file
14
backend-api/src/template/template.module.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import {Module} from '@nestjs/common';
|
||||
import {TemplateService} from './template.service';
|
||||
import {TemplateController} from './template.controller';
|
||||
import {TypeOrmModule} from "@nestjs/typeorm";
|
||||
import {Template} from "./entities/template.entity";
|
||||
import {TemplateItem} from "./entities/template-item.entity";
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Template, TemplateItem])],
|
||||
controllers: [TemplateController],
|
||||
providers: [TemplateService],
|
||||
})
|
||||
export class TemplateModule {
|
||||
}
|
||||
18
backend-api/src/template/template.service.spec.ts
Normal file
18
backend-api/src/template/template.service.spec.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { TemplateService } from './template.service';
|
||||
|
||||
describe('TemplateService', () => {
|
||||
let service: TemplateService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [TemplateService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<TemplateService>(TemplateService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
191
backend-api/src/template/template.service.ts
Normal file
191
backend-api/src/template/template.service.ts
Normal file
@@ -0,0 +1,191 @@
|
||||
import {Injectable} from '@nestjs/common';
|
||||
import {CreateTemplateDto} from './dto/create-template.dto';
|
||||
import {UpdateTemplateDto} from './dto/update-template.dto';
|
||||
import {InjectRepository} from "@nestjs/typeorm";
|
||||
import {Template} from "./entities/template.entity";
|
||||
import {Repository} from "typeorm";
|
||||
import {YesNo} from "../common/enum/yn.enum";
|
||||
import {TemplateItem} from "./entities/template-item.entity";
|
||||
import {CreateTemplateItemDto} from "./dto/create-template-item.dto";
|
||||
import {UpdateTemplateItemDto} from "./dto/update-template-item.dto";
|
||||
import {TemplateInfoDto} from "./dto/template-info.dto";
|
||||
import {ItemInfoDto} from "./dto/item-info.dto";
|
||||
|
||||
@Injectable()
|
||||
export class TemplateService {
|
||||
constructor(
|
||||
@InjectRepository(Template)
|
||||
private readonly templateRepository: Repository<Template>,
|
||||
@InjectRepository(TemplateItem)
|
||||
private readonly templateItemRepository: Repository<TemplateItem>
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 템플릿 추가
|
||||
* @param createTemplate
|
||||
*/
|
||||
async create(createTemplate: CreateTemplateDto): Promise<Template> {
|
||||
const insertItem = await this.templateRepository.save({
|
||||
title: createTemplate.title,
|
||||
description: createTemplate.description,
|
||||
|
||||
})
|
||||
return insertItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* 템플릿 상세 아이템 추가
|
||||
* @param createTemplateItem
|
||||
*/
|
||||
async createItem(createTemplateItem: CreateTemplateItemDto): Promise<TemplateItem> {
|
||||
const insertItem = await this.templateItemRepository.save({
|
||||
templateId: createTemplateItem.templateId,
|
||||
x: createTemplateItem.x,
|
||||
y: createTemplateItem.y,
|
||||
width: createTemplateItem.width,
|
||||
height: createTemplateItem.height,
|
||||
recommendCategory: createTemplateItem.recommendCategory,
|
||||
})
|
||||
return insertItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* 템플릿 목록 조회
|
||||
*/
|
||||
async findAll() {
|
||||
const result = await this.templateRepository.find({
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
description: true,
|
||||
},
|
||||
where: {
|
||||
useYn: YesNo.YES
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 템플릿 단건 조회
|
||||
* @param id
|
||||
*/
|
||||
async findOne(id: number): Promise<TemplateInfoDto> {
|
||||
let returnObj: TemplateInfoDto;
|
||||
|
||||
// 템플릿 기본 정보 조회
|
||||
const templateInfo = await this.templateRepository.findOne({
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
description: true,
|
||||
},
|
||||
where: {
|
||||
useYn: YesNo.YES,
|
||||
id: id,
|
||||
}
|
||||
});
|
||||
|
||||
if(templateInfo.id){
|
||||
returnObj = new TemplateInfoDto(templateInfo);
|
||||
// 템플릿 상세 아이템 조회(layout 조회 및 가공)
|
||||
const layoutList = await this.templateItemRepository.find({
|
||||
where: {
|
||||
templateId: templateInfo.id
|
||||
}
|
||||
});
|
||||
const layout = [];
|
||||
layoutList.map((item) => {
|
||||
const itemInfo:ItemInfoDto = new ItemInfoDto(item);
|
||||
layout.push(itemInfo);
|
||||
})
|
||||
returnObj.layout = layout;
|
||||
}
|
||||
|
||||
return returnObj;
|
||||
}
|
||||
|
||||
/**
|
||||
* 템플릿 업데이트
|
||||
* @param id
|
||||
* @param updateTemplateDto
|
||||
*/
|
||||
async update(id: number, updateTemplateDto: UpdateTemplateDto) {
|
||||
|
||||
const updateItem = await this.templateRepository.update({
|
||||
id: id
|
||||
}, {
|
||||
title: updateTemplateDto.title,
|
||||
description: updateTemplateDto.description,
|
||||
});
|
||||
|
||||
let msg = `This action updates a #${id} template`
|
||||
if (updateItem.affected < 1) {
|
||||
msg = '변동사항 없음';
|
||||
} else if (updateItem.affected > 1) {
|
||||
msg = '여러개 바뀜';
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* 템플릿 상세 아이템 수정
|
||||
* @param id
|
||||
* @param updateTemplateItem
|
||||
*/
|
||||
async updateItem(id: number,updateTemplateItem: UpdateTemplateItemDto) {
|
||||
const updateItem = await this.templateItemRepository.update({
|
||||
id: id
|
||||
}, {
|
||||
x: updateTemplateItem.x,
|
||||
y: updateTemplateItem.y,
|
||||
width: updateTemplateItem.width,
|
||||
height: updateTemplateItem.height,
|
||||
recommendCategory: updateTemplateItem.recommendCategory
|
||||
});
|
||||
|
||||
let msg = `This action updates a #${id} templateItem`
|
||||
if (updateItem.affected < 1) {
|
||||
msg = '변동사항 없음';
|
||||
} else if (updateItem.affected > 1) {
|
||||
msg = '여러개 바뀜';
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* 템플릿 비활성화(useYn='Y')
|
||||
* @param id
|
||||
*/
|
||||
async remove(id: number) {
|
||||
const deleteItem = await this.templateRepository.update({
|
||||
id: id
|
||||
}, {
|
||||
useYn: YesNo.YES
|
||||
});
|
||||
|
||||
let msg = `#${id} template useYn='Y' 변경완료 `
|
||||
if (deleteItem.affected < 1) {
|
||||
msg = '변동사항 없음';
|
||||
} else if (deleteItem.affected > 1) {
|
||||
msg = '여러개 바뀜';
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* 선택된 위젯목록으로 추천될 template 목록
|
||||
* @param widgets
|
||||
*/
|
||||
async findRecommendTemplates(widgets: any[]): Promise<TemplateInfoDto[]> {
|
||||
const returnArr = [];
|
||||
const tempTemplateInfo = new TemplateItem();
|
||||
returnArr.push(tempTemplateInfo);
|
||||
|
||||
|
||||
|
||||
return returnArr;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export class CreateWidgetViewDto {}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { PartialType } from '@nestjs/swagger';
|
||||
import { CreateWidgetViewDto } from './create-widget-view.dto';
|
||||
|
||||
export class UpdateWidgetViewDto extends PartialType(CreateWidgetViewDto) {}
|
||||
22
backend-api/src/widget-view/entities/widget-view.entity.ts
Normal file
22
backend-api/src/widget-view/entities/widget-view.entity.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import {Column, Entity, JoinTable, ManyToMany, OneToMany, PrimaryGeneratedColumn} from "typeorm";
|
||||
import {Database} from "../../database/entities/database.entity";
|
||||
import {BaseEntity} from "../../common/entities/base.entity";
|
||||
import {Widget} from "../../widget/entities/widget.entity";
|
||||
|
||||
@Entity()
|
||||
export class WidgetView extends BaseEntity {
|
||||
@PrimaryGeneratedColumn({comment: '위젯 데이터셋 ID'})
|
||||
id: number
|
||||
|
||||
@Column({comment: '데이터베이스 ID'})
|
||||
databaseId: number
|
||||
|
||||
@Column({type: 'text', comment: '조회 sql'})
|
||||
query: string
|
||||
|
||||
@OneToMany(
|
||||
(type) => Widget,
|
||||
(widget) => widget.datasetId
|
||||
)
|
||||
widgets!: Widget
|
||||
}
|
||||
20
backend-api/src/widget-view/widget-view.controller.spec.ts
Normal file
20
backend-api/src/widget-view/widget-view.controller.spec.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { WidgetViewController } from './widget-view.controller';
|
||||
import { WidgetViewService } from './widget-view.service';
|
||||
|
||||
describe('WidgetViewController', () => {
|
||||
let controller: WidgetViewController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [WidgetViewController],
|
||||
providers: [WidgetViewService],
|
||||
}).compile();
|
||||
|
||||
controller = module.get<WidgetViewController>(WidgetViewController);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(controller).toBeDefined();
|
||||
});
|
||||
});
|
||||
34
backend-api/src/widget-view/widget-view.controller.ts
Normal file
34
backend-api/src/widget-view/widget-view.controller.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
|
||||
import { WidgetViewService } from './widget-view.service';
|
||||
import { CreateWidgetViewDto } from './dto/create-widget-view.dto';
|
||||
import { UpdateWidgetViewDto } from './dto/update-widget-view.dto';
|
||||
|
||||
@Controller('widget-view')
|
||||
export class WidgetViewController {
|
||||
constructor(private readonly widgetViewService: WidgetViewService) {}
|
||||
|
||||
@Post()
|
||||
create(@Body() createWidgetViewDto: CreateWidgetViewDto) {
|
||||
return this.widgetViewService.create(createWidgetViewDto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
findAll() {
|
||||
return this.widgetViewService.findAll();
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
findOne(@Param('id') id: string) {
|
||||
return this.widgetViewService.findOne(+id);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
update(@Param('id') id: string, @Body() updateWidgetViewDto: UpdateWidgetViewDto) {
|
||||
return this.widgetViewService.update(+id, updateWidgetViewDto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
remove(@Param('id') id: string) {
|
||||
return this.widgetViewService.remove(+id);
|
||||
}
|
||||
}
|
||||
9
backend-api/src/widget-view/widget-view.module.ts
Normal file
9
backend-api/src/widget-view/widget-view.module.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { WidgetViewService } from './widget-view.service';
|
||||
import { WidgetViewController } from './widget-view.controller';
|
||||
|
||||
@Module({
|
||||
controllers: [WidgetViewController],
|
||||
providers: [WidgetViewService]
|
||||
})
|
||||
export class WidgetViewModule {}
|
||||
18
backend-api/src/widget-view/widget-view.service.spec.ts
Normal file
18
backend-api/src/widget-view/widget-view.service.spec.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { WidgetViewService } from './widget-view.service';
|
||||
|
||||
describe('WidgetViewService', () => {
|
||||
let service: WidgetViewService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [WidgetViewService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<WidgetViewService>(WidgetViewService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
28
backend-api/src/widget-view/widget-view.service.ts
Normal file
28
backend-api/src/widget-view/widget-view.service.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { CreateWidgetViewDto } from './dto/create-widget-view.dto';
|
||||
import { UpdateWidgetViewDto } from './dto/update-widget-view.dto';
|
||||
|
||||
@Injectable()
|
||||
export class WidgetViewService {
|
||||
create(createWidgetViewDto: CreateWidgetViewDto) {
|
||||
|
||||
|
||||
return 'This action adds a new widgetView';
|
||||
}
|
||||
|
||||
findAll() {
|
||||
return `This action returns all widgetView`;
|
||||
}
|
||||
|
||||
findOne(id: number) {
|
||||
return `This action returns a #${id} widgetView`;
|
||||
}
|
||||
|
||||
update(id: number, updateWidgetViewDto: UpdateWidgetViewDto) {
|
||||
return `This action updates a #${id} widgetView`;
|
||||
}
|
||||
|
||||
remove(id: number) {
|
||||
return `This action removes a #${id} widgetView`;
|
||||
}
|
||||
}
|
||||
34
backend-api/src/widget/dto/create-widget.dto.ts
Normal file
34
backend-api/src/widget/dto/create-widget.dto.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import {IsNotEmpty, IsNumber, IsString} from "class-validator";
|
||||
import {DatasetType} from "../../common/enum/dataset-type.enum";
|
||||
import {YesNo} from "../../common/enum/yn.enum";
|
||||
|
||||
export class CreateWidgetDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
title: string;
|
||||
|
||||
@IsString()
|
||||
description: string;
|
||||
|
||||
@IsNumber()
|
||||
@IsNotEmpty()
|
||||
componentId: number;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
datasetType: DatasetType
|
||||
|
||||
@IsNumber()
|
||||
@IsNotEmpty()
|
||||
datasetId: number
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
option: string
|
||||
|
||||
@IsString()
|
||||
delYn: YesNo
|
||||
|
||||
@IsNumber()
|
||||
widgetViewId: number
|
||||
}
|
||||
4
backend-api/src/widget/dto/update-widget.dto.ts
Normal file
4
backend-api/src/widget/dto/update-widget.dto.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { PartialType } from '@nestjs/mapped-types';
|
||||
import { CreateWidgetDto } from './create-widget.dto';
|
||||
|
||||
export class UpdateWidgetDto extends PartialType(CreateWidgetDto) {}
|
||||
73
backend-api/src/widget/entities/widget.entity.ts
Normal file
73
backend-api/src/widget/entities/widget.entity.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import {Column, Entity, JoinTable, ManyToMany, ManyToOne, PrimaryGeneratedColumn} from "typeorm";
|
||||
import {BaseEntity} from "../../common/entities/base.entity";
|
||||
import {DatasetType} from "../../common/enum/dataset-type.enum";
|
||||
import {YesNo} from "../../common/enum/yn.enum";
|
||||
import {Component} from "../../component/entities/component.entity";
|
||||
import {Dashboard} from "../../dashboard/entities/dashboard.entity";
|
||||
import {Dataset} from "../../dataset/entities/dataset.entity";
|
||||
import {WidgetView} from "../../widget-view/entities/widget-view.entity";
|
||||
|
||||
@Entity()
|
||||
export class Widget extends BaseEntity {
|
||||
@PrimaryGeneratedColumn({comment: '위젯 ID'})
|
||||
id: number
|
||||
|
||||
@Column({length: 300, nullable: true, comment: '위젯명'})
|
||||
title: string
|
||||
|
||||
@Column({length: 1000, nullable: true, comment: '설명'})
|
||||
description: string
|
||||
|
||||
@Column({comment: '컴포넌트 ID'})
|
||||
componentId: number
|
||||
|
||||
@Column({comment: '데이터셋 구분(데이터셋, 위젯 데이터셋)', default: DatasetType.WIDGET})
|
||||
datasetType: DatasetType
|
||||
|
||||
@Column({comment: '데이터셋 ID'})
|
||||
datasetId: number
|
||||
|
||||
@Column({type: "text", comment: '위젯 속성'})
|
||||
option: string
|
||||
|
||||
@Column({length: 1, default: YesNo.NO, comment: '삭제여부'})
|
||||
delYn: YesNo
|
||||
|
||||
@Column({ comment: '위젯뷰 ID'})
|
||||
widgetViewId: number
|
||||
|
||||
|
||||
|
||||
@ManyToMany(type => Dashboard)
|
||||
@JoinTable({
|
||||
name: 'dashboard_widget',
|
||||
joinColumn: {
|
||||
name: 'widgetId',
|
||||
referencedColumnName: 'id'
|
||||
},
|
||||
inverseJoinColumn: {
|
||||
name: 'dashboardId',
|
||||
referencedColumnName: 'id'
|
||||
}
|
||||
})
|
||||
databases: Dashboard[];
|
||||
|
||||
@ManyToOne(
|
||||
(type) => Component,
|
||||
(component) => component
|
||||
)
|
||||
component!: Component
|
||||
|
||||
@ManyToOne(
|
||||
(type => Dataset),
|
||||
(dataset) => dataset
|
||||
)
|
||||
dataset!: Dataset[]
|
||||
|
||||
@ManyToOne(
|
||||
(type => WidgetView),
|
||||
(widgetView) => widgetView
|
||||
)
|
||||
widgetView!: WidgetView[]
|
||||
|
||||
}
|
||||
20
backend-api/src/widget/widget.controller.spec.ts
Normal file
20
backend-api/src/widget/widget.controller.spec.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { WidgetController } from './widget.controller';
|
||||
import { WidgetService } from './widget.service';
|
||||
|
||||
describe('WidgetController', () => {
|
||||
let controller: WidgetController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [WidgetController],
|
||||
providers: [WidgetService],
|
||||
}).compile();
|
||||
|
||||
controller = module.get<WidgetController>(WidgetController);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(controller).toBeDefined();
|
||||
});
|
||||
});
|
||||
34
backend-api/src/widget/widget.controller.ts
Normal file
34
backend-api/src/widget/widget.controller.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
|
||||
import { WidgetService } from './widget.service';
|
||||
import { CreateWidgetDto } from './dto/create-widget.dto';
|
||||
import { UpdateWidgetDto } from './dto/update-widget.dto';
|
||||
|
||||
@Controller('widget')
|
||||
export class WidgetController {
|
||||
constructor(private readonly widgetService: WidgetService) {}
|
||||
|
||||
@Post()
|
||||
create(@Body() createWidgetDto: CreateWidgetDto) {
|
||||
return this.widgetService.create(createWidgetDto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
findAll() {
|
||||
return this.widgetService.findAll();
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
findOne(@Param('id') id: string) {
|
||||
return this.widgetService.findOne(+id);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
update(@Param('id') id: string, @Body() updateWidgetDto: UpdateWidgetDto) {
|
||||
return this.widgetService.update(+id, updateWidgetDto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
remove(@Param('id') id: string) {
|
||||
return this.widgetService.remove(+id);
|
||||
}
|
||||
}
|
||||
12
backend-api/src/widget/widget.module.ts
Normal file
12
backend-api/src/widget/widget.module.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { WidgetService } from './widget.service';
|
||||
import { WidgetController } from './widget.controller';
|
||||
import {TypeOrmModule} from "@nestjs/typeorm";
|
||||
import {Widget} from "./entities/widget.entity";
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Widget])],
|
||||
controllers: [WidgetController],
|
||||
providers: [WidgetService],
|
||||
})
|
||||
export class WidgetModule {}
|
||||
18
backend-api/src/widget/widget.service.spec.ts
Normal file
18
backend-api/src/widget/widget.service.spec.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { WidgetService } from './widget.service';
|
||||
|
||||
describe('WidgetService', () => {
|
||||
let service: WidgetService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [WidgetService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<WidgetService>(WidgetService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
68
backend-api/src/widget/widget.service.ts
Normal file
68
backend-api/src/widget/widget.service.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { CreateWidgetDto } from './dto/create-widget.dto';
|
||||
import { UpdateWidgetDto } from './dto/update-widget.dto';
|
||||
import { Repository } from "typeorm";
|
||||
import { Widget } from "./entities/widget.entity";
|
||||
import { InjectRepository } from "@nestjs/typeorm";
|
||||
|
||||
@Injectable()
|
||||
export class WidgetService {
|
||||
constructor(
|
||||
@InjectRepository(Widget)
|
||||
private widgetRepository: Repository<Widget>) {}
|
||||
|
||||
async create(body: CreateWidgetDto) {
|
||||
|
||||
const find_widget = await this.widgetRepository.findOne({where: { title: body.title }})
|
||||
if(find_widget){
|
||||
return 'exist same widget'
|
||||
} else {
|
||||
// await this.widgetRepository.save({
|
||||
// title: body.title,
|
||||
// description: body.description,
|
||||
// componentId: body.componentId,
|
||||
// datasetType: body.datasetType,
|
||||
// datasetId: body.datasetId,
|
||||
// option: JSON.stringify(body.option),
|
||||
// delYn: body.delYn
|
||||
// })
|
||||
}
|
||||
return 'This action adds a new widget';
|
||||
}
|
||||
|
||||
async findAll() {
|
||||
const find_all = await this.widgetRepository.find()
|
||||
return find_all
|
||||
}
|
||||
|
||||
async findOne(id: number) {
|
||||
|
||||
const find_widget = await this.widgetRepository.findOne({ where: { id: id }})
|
||||
return find_widget;
|
||||
}
|
||||
|
||||
async update(id: number, body: UpdateWidgetDto) {
|
||||
const find_widget = await this.widgetRepository.findOne({ where: { id: id}})
|
||||
if(!find_widget){
|
||||
return 'No exist widget'
|
||||
} else {
|
||||
|
||||
find_widget.title = body.title;
|
||||
find_widget.componentId = body.componentId;
|
||||
find_widget.datasetType = body.datasetType;
|
||||
find_widget.datasetId = body.datasetId;
|
||||
find_widget.option = JSON.stringify(body.option);
|
||||
find_widget.delYn = body.delYn;
|
||||
find_widget.widgetViewId = body.widgetViewId;
|
||||
|
||||
await this.widgetRepository.save(find_widget)
|
||||
|
||||
return `This action updates a #${id} widget`;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
remove(id: number) {
|
||||
return `This action removes a #${id} widget`;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user