commit b2dd82ef08907f74db72eeb605478a0c5c7bfc41 Author: Richard Park Date: Sun Apr 14 16:29:05 2019 +0900 Project is created diff --git a/.editorconfig b/.editorconfig new file mode 100755 index 0000000..bed9d1f --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +# Editor configuration, see https://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..6558190 --- /dev/null +++ b/.gitignore @@ -0,0 +1,46 @@ +# See http://help.github.com/ignore-files/ for more about ignoring files. + +# compiled output +/dist +/tmp +/out-tsc + +# dependencies +/node_modules + +# 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 + +# misc +/.sass-cache +/connect.lock +/coverage +/libpeerconnection.log +npm-debug.log +yarn-error.log +testem.log +/typings + +# System Files +.DS_Store +Thumbs.db + +yarn\.lock +.angulardoc.json +/dev +desktop.ini + +\.mongod/ diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100755 index 0000000..5ba9330 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,15 @@ +{ + "editor.tabSize": 2, + "editor.insertSpaces": true, + "editor.formatOnSave": true, + "editor.formatOnPaste": true, + "editor.autoClosingBrackets": true, + "editor.trimAutoWhitespace": true, + "files.trimTrailingWhitespace": true, + "files.trimFinalNewlines": true, + "git.ignoreLimitWarning": true, + + "prettier.singleQuote": true, + "debug.node.autoAttach": "on" + } + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100755 index 0000000..87fcb59 --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +# AngularUniversal + +This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 7.0.6. + +## Development server + +Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. + +## Code scaffolding + +Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. + +## Build + +Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. + +## Running unit tests + +Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). + +## Running end-to-end tests + +Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). + +## Further help + +To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). +` \ No newline at end of file diff --git a/angular.json b/angular.json new file mode 100755 index 0000000..ee97384 --- /dev/null +++ b/angular.json @@ -0,0 +1,166 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "2nd-round": { + "root": "", + "sourceRoot": "src", + "projectType": "application", + "prefix": "app", + "schematics": { + "@schematics/angular:component": { + "styleext": "scss" + } + }, + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/2nd-round", + "index": "src/index.html", + "main": "src/main.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "src/tsconfig.app.json", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "node_modules/@angular/material/prebuilt-themes/indigo-pink.css", + "src/styles.scss" + ], + "scripts": [] + }, + "configurations": { + "production": { + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + } + ], + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "extractCss": true, + "namedChunks": false, + "aot": true, + "extractLicenses": true, + "vendorChunk": false, + "buildOptimizer": true, + "budgets": [ + { + "type": "initial", + "maximumWarning": "2mb", + "maximumError": "5mb" + } + ] + }, + "hmr": { + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.hmr.ts" + } + ] + }, + "test": { + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.test.ts" + } + ] + } + } + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "options": { + "browserTarget": "2nd-round:build" + }, + "configurations": { + "production": { + "browserTarget": "2nd-round:build:production" + }, + "hmr": { + "hmrWarning": false, + "hmr": true, + "browserTarget": "2nd-round:build:hmr" + }, + "test": { + "hmrWarning": false, + "hmr": true, + "browserTarget": "2nd-round:build:test" + } + } + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "2nd-round:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "src/test.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "src/tsconfig.spec.json", + "karmaConfig": "src/karma.conf.js", + "styles": [ + "src/styles.scss" + ], + "scripts": [], + "assets": [ + "src/favicon.ico", + "src/assets" + ] + } + }, + "lint": { + "builder": "@angular-devkit/build-angular:tslint", + "options": { + "tsConfig": [ + "src/tsconfig.app.json", + "src/tsconfig.spec.json" + ], + "exclude": [ + "**/node_modules/**" + ] + } + } + } + }, + "2nd-round-e2e": { + "root": "e2e/", + "projectType": "application", + "prefix": "", + "architect": { + "e2e": { + "builder": "@angular-devkit/build-angular:protractor", + "options": { + "protractorConfig": "e2e/protractor.conf.js", + "devServerTarget": "2nd-round:serve" + }, + "configurations": { + "production": { + "devServerTarget": "2nd-round:serve:production" + } + } + }, + "lint": { + "builder": "@angular-devkit/build-angular:tslint", + "options": { + "tsConfig": "e2e/tsconfig.e2e.json", + "exclude": [ + "**/node_modules/**" + ] + } + } + } + } + }, + "defaultProject": "2nd-round" +} \ No newline at end of file diff --git a/e2e/protractor.conf.js b/e2e/protractor.conf.js new file mode 100755 index 0000000..ba328a6 --- /dev/null +++ b/e2e/protractor.conf.js @@ -0,0 +1,28 @@ +// Protractor configuration file, see link for more information +// https://github.com/angular/protractor/blob/master/lib/config.ts + +const { SpecReporter } = require('jasmine-spec-reporter'); + +exports.config = { + allScriptsTimeout: 11000, + specs: [ + './src/**/*.e2e-spec.ts' + ], + capabilities: { + 'browserName': 'chrome' + }, + directConnect: true, + baseUrl: 'http://localhost:4200/', + framework: 'jasmine', + jasmineNodeOpts: { + showColors: true, + defaultTimeoutInterval: 30000, + print: function() {} + }, + onPrepare() { + require('ts-node').register({ + project: require('path').join(__dirname, './tsconfig.e2e.json') + }); + jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); + } +}; \ No newline at end of file diff --git a/e2e/src/app.e2e-spec.ts b/e2e/src/app.e2e-spec.ts new file mode 100755 index 0000000..7920187 --- /dev/null +++ b/e2e/src/app.e2e-spec.ts @@ -0,0 +1,14 @@ +import { AppPage } from './app.po'; + +describe('workspace-project App', () => { + let page: AppPage; + + beforeEach(() => { + page = new AppPage(); + }); + + it('should display welcome message', () => { + page.navigateTo(); + expect(page.getTitleText()).toEqual('Welcome to 2nd-round!'); + }); +}); diff --git a/e2e/src/app.po.ts b/e2e/src/app.po.ts new file mode 100755 index 0000000..f38db75 --- /dev/null +++ b/e2e/src/app.po.ts @@ -0,0 +1,11 @@ +import { browser, by, element } from 'protractor'; + +export class AppPage { + navigateTo() { + return browser.get('/'); + } + + getTitleText() { + return element(by.css('app-root h1')).getText(); + } +} diff --git a/e2e/tsconfig.e2e.json b/e2e/tsconfig.e2e.json new file mode 100755 index 0000000..43d5b30 --- /dev/null +++ b/e2e/tsconfig.e2e.json @@ -0,0 +1,13 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/app", + "module": "commonjs", + "target": "es5", + "types": [ + "jasmine", + "jasminewd2", + "node" + ] + } +} \ No newline at end of file diff --git a/nodemon.json b/nodemon.json new file mode 100755 index 0000000..6ff96c3 --- /dev/null +++ b/nodemon.json @@ -0,0 +1,15 @@ +{ + "ignore": [ + "**/client/**", + "**/*.test.ts", + "**/*.spec.ts", + ".git", + "node_modules" + ], + "watch": [ + "src/server", + "src/shared" + ], + "exec": "yarn start:server", + "ext": "ts" +} diff --git a/package.json b/package.json new file mode 100755 index 0000000..6f51f86 --- /dev/null +++ b/package.json @@ -0,0 +1,138 @@ +{ + "name": "2nd-round", + "version": "0.0.0", + "scripts": { + "ng": "ng", + "start": "npm-run-all -p start:server start:client:hmr", + "start:client": "ng serve", + "start:client:hmr": "ng serve --configuration hmr", + "start:client:test": "ng serve --configuration=test --host 0.0.0.0 --port 4200", + "start:server": "cross-env TS_NODE_PROJECT=\"src/tsconfig.server.json\" node --inspect -r ts-node/register ./src/server/main.ts", + "start:server:nodemon": "nodemon", + "build": "ng build", + "build:server": "webpack --config webpack.server.config.js --progress --colors", + "test": "ng test", + "lint": "ng lint", + "e2e": "ng e2e" + }, + "private": true, + "dependencies": { + "@angular/animations": "~7.0.4", + "@angular/cdk": "^7.1.0", + "@angular/cdk-experimental": "^7.2.0", + "@angular/common": "~7.0.4", + "@angular/compiler": "~7.0.4", + "@angular/core": "~7.0.4", + "@angular/flex-layout": "^7.0.0-beta.19", + "@angular/forms": "~7.0.4", + "@angular/http": "~7.0.4", + "@angular/material": "^7.1.0", + "@angular/platform-browser": "~7.0.4", + "@angular/platform-browser-dynamic": "~7.0.4", + "@angular/router": "~7.0.4", + "@ngrx/effects": "^6.1.2", + "@ngrx/router-store": "^6.1.2", + "@ngrx/schematics": "^6.1.2", + "@ngrx/store": "^6.1.2", + "@ngrx/store-devtools": "^6.1.2", + "@ngx-translate/core": "^11.0.1", + "@ngx-translate/http-loader": "^4.0.0", + "@tsed/common": "^5.0.8", + "@tsed/core": "^5.0.8", + "@tsed/di": "^5.0.8", + "@tsed/mongoose": "^5.0.8", + "@tsed/multipartfiles": "^5.0.8", + "@tsed/swagger": "^5.0.8", + "@tsed/testing": "^5.0.8", + "@tsed/typeorm": "^5.0.8", + "axios": "^0.18.0", + "body-parser": "^1.18.3", + "compression": "^1.7.3", + "cookie-parser": "^1.4.3", + "core-js": "^2.5.4", + "cors": "^2.8.5", + "cropperjs": "^1.4.3", + "cross-env": "^5.2.0", + "express": "^4.16.4", + "fs-extra": "^7.0.1", + "gifshot": "^0.4.5", + "hammerjs": "^2.0.8", + "jimp": "^0.6.0", + "jsonwebtoken": "^8.4.0", + "method-override": "^3.0.0", + "mongodb": "^3.1.10", + "mongoose": "^5.4.4", + "ms": "^2.1.1", + "multer": "^1.4.1", + "ngx-clipboard": "^11.1.9", + "ngx-cookie-service": "^2.0.2", + "ngx-hm-carousel": "^1.4.0", + "ngx-image-cropper": "^1.3.7", + "ngx-virtual-scroller": "^1.0.16", + "oneall": "^0.1.5", + "passport": "^0.4.0", + "passport-jwt": "^4.0.0", + "passport-kakao": "^0.0.5", + "passport-local": "^1.0.0", + "passport-naver": "^1.0.6", + "reflect-metadata": "^0.1.12", + "rxjs": "~6.3.3", + "short-uuid": "^3.1.0", + "typeorm": "^0.2.9", + "uuid": "^3.3.2", + "zone.js": "~0.8.26" + }, + "devDependencies": { + "@angular-devkit/build-angular": "~0.10.0", + "@angular/cli": "~7.0.6", + "@angular/compiler-cli": "~7.0.4", + "@angular/language-service": "~7.0.4", + "@angularclass/hmr": "^2.1.3", + "@types/bcrypt": "^3.0.0", + "@types/cropperjs": "^1.1.4", + "@types/express": "^4.16.0", + "@types/fs-extra": "^5.0.4", + "@types/jasmine": "~2.8.8", + "@types/jasminewd2": "~2.0.3", + "@types/jimp": "^0.2.28", + "@types/jsonwebtoken": "^8.3.0", + "@types/mongoose": "^5.3.7", + "@types/ms": "^0.7.30", + "@types/multer": "^1.3.7", + "@types/node": "~8.9.4", + "@types/passport": "^0.4.7", + "@types/passport-jwt": "^3.0.1", + "@types/passport-kakao": "^0.2.0", + "@types/passport-local": "^1.0.33", + "@types/passport-naver": "^0.2.0", + "@types/swagger-schema-official": "^2.0.13", + "@types/unzipper": "^0.9.1", + "@types/uuid": "^3.4.4", + "@types/webpack": "^4.4.19", + "awesome-typescript-loader": "^5.2.1", + "codelyzer": "~4.5.0", + "jasmine-core": "~2.99.1", + "jasmine-spec-reporter": "~4.2.1", + "json-loader": "^0.5.7", + "karma": "~3.0.0", + "karma-chrome-launcher": "~2.2.0", + "karma-coverage-istanbul-reporter": "~2.0.1", + "karma-jasmine": "~1.1.2", + "karma-jasmine-html-reporter": "^0.2.2", + "mocha": "^5.2.0", + "mongodb-memory-server": "^2.9.1", + "nodemon": "^1.18.7", + "npm-run-all": "^4.1.3", + "protractor": "~5.4.0", + "resize-observer-polyfill": "^1.5.1", + "sqlite3": "^4.0.4", + "ts-node": "^7.0.1", + "tslint": "~5.11.0", + "typescript": "~3.1.6", + "unzipper": "^0.9.8", + "webpack-cli": "^3.1.2" + }, + "resolutions": { + "**/event-stream": "^4.0.1" + } +} diff --git a/src/app/app-provider.module.ts b/src/app/app-provider.module.ts new file mode 100755 index 0000000..7f7c49b --- /dev/null +++ b/src/app/app-provider.module.ts @@ -0,0 +1,76 @@ +/** + * 파 일 명: app-provider.module.ts + * 작성일자: 2018-12-23 + * 작 성 자: 박병준 + * 설 명: app 영역의 provider를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { NgModule, APP_INITIALIZER } from '@angular/core'; +import { HTTP_INTERCEPTORS } from '@angular/common/http'; + +import { CookieService } from 'ngx-cookie-service'; +import { ClipboardModule, ClipboardService } from 'ngx-clipboard'; + +import { AccountsModule } from '../modules/accounts/accounts.module'; +import { ArticleModule } from '../modules/article/article.module'; +import { AttachmentsModule } from '../modules/attachments/attachments.module'; +import { CartoonsModule } from '../modules/cartoons/cartoons.module'; +import { ContentsModule } from '../modules/contents/contents.module'; +import { IllustrationsModule } from '../modules/illustrations/illustrations.module'; +import { NovelModule } from '../modules/novel/novel.module'; +import { TagModule } from '../modules/tag/tag.module'; +import { UserModule } from '../modules/user/user.module'; +import { UserAnalysisModule } from '../modules/user-analysis/user-analysis.module'; +import { UserSupportModule } from '../modules/user-support/user-support.module'; +import { MetaModule } from '../modules/meta/meta.module'; + +import { AuthHttpInterceptor } from './service/auth.http.interceptor'; +import { AppService } from './service/app.service'; +import { GUARDS } from './guard'; + +export function initApp(appService: AppService) { + return () => appService.initApp(); +} + +@NgModule({ + imports: [ + AccountsModule.forRoot(), + ArticleModule.forRoot(), + AttachmentsModule.forRoot(), + CartoonsModule.forRoot(), + ContentsModule.forRoot(), + IllustrationsModule.forRoot(), + NovelModule.forRoot(), + TagModule.forRoot(), + UserModule.forRoot(), + UserAnalysisModule.forRoot(), + UserSupportModule.forRoot(), + MetaModule.forRoot(), + ClipboardModule + ], + exports: [], + providers: [ + AppService, + ...GUARDS, + CookieService, + ClipboardService, + { + provide: APP_INITIALIZER, + useFactory: initApp, + deps: [AppService], + multi: true + }, + { provide: HTTP_INTERCEPTORS, useClass: AuthHttpInterceptor, multi: true }, + { provide: 'virtualScroller.scrollThrottlingTime', useValue: 0 }, + { provide: 'virtualScroller.scrollDebounceTime', useValue: 0 }, + { provide: 'virtualScroller.scrollAnimationTime', useValue: 750 }, + { provide: 'virtualScroller.scrollbarWidth', useValue: undefined }, + { provide: 'virtualScroller.scrollbarHeight', useValue: undefined }, + { provide: 'virtualScroller.checkResizeInterval', useValue: 1000 }, + { provide: 'virtualScroller.resizeBypassRefreshThreshold', useValue: 5 } + ] +}) +export class AppProviderModule { } diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts new file mode 100755 index 0000000..7444e34 --- /dev/null +++ b/src/app/app-routing.module.ts @@ -0,0 +1,41 @@ +/** + * 파 일 명: app-routing.module.ts + * 작성일자: 2018-12-17 + * 작 성 자: 박병준 + * 설 명: app 영역의 routing을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +const routes: Routes = [ + { + path: '', + loadChildren: './pages/main/main.page.module#MainPageModule' + }, + { + path: 'accounts', + loadChildren: './pages/accounts/accounts.page.module#AccountsPageModule' + }, + { + path: 'user', + loadChildren: './pages/user/user.page.module#UserPageModule' + }, + { + path: 'article', + loadChildren: './pages/article/article.page.module#ArticlePageModule' + }, + { + path: 'admin', + loadChildren: './pages/admin/admin.page.module#AdminPageModule' + } +]; + +@NgModule({ + imports: [RouterModule.forRoot(routes, { onSameUrlNavigation: 'reload' })], + exports: [RouterModule] +}) +export class AppRoutingModule { } diff --git a/src/app/app-store.module.ts b/src/app/app-store.module.ts new file mode 100755 index 0000000..ce3c540 --- /dev/null +++ b/src/app/app-store.module.ts @@ -0,0 +1,59 @@ +/** + * 파 일 명: app-store.module.ts + * 작성일자: 2018-12-17 + * 작 성 자: 박병준 + * 설 명: app 영역의 ngrx를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { NgModule } from '@angular/core'; +import { StoreModule } from '@ngrx/store'; +import { StoreDevtoolsModule } from '@ngrx/store-devtools'; +import { + StoreRouterConnectingModule, +} from '@ngrx/router-store'; +import { EffectsModule } from '@ngrx/effects'; + +import { environment } from '../environments/environment'; +import { REDUCERS, META_REDUCERS, EFFECTS } from './store'; + +@NgModule({ + exports: [ + StoreModule, + ], + imports: [ + StoreModule.forRoot(REDUCERS, { metaReducers: META_REDUCERS }), + /** + * @ngrx/router-store keeps router state up-to-date in the store. + */ + StoreRouterConnectingModule.forRoot({ + /* + They stateKey defines the name of the state used by the router-store reducer. + This matches the key defined in the map of reducers + */ + stateKey: 'router', + }), + /** + * Store devtools instrument the store retaining past versions of state + * and recalculating new states. This enables powerful time-travel + * debugging. + * + * To use the debugger, install the Redux Devtools extension for either + * Chrome or Firefox + * + * See: https://github.com/zalmoxisus/redux-devtools-extension + */ + StoreDevtoolsModule.instrument({ + name: 'WebApp DevTools', + maxAge: 50, + logOnly: environment.production, + }), + EffectsModule.forRoot(EFFECTS), + ], + providers: [ + ], + +}) +export class AppStoreModule { } diff --git a/src/app/app-translate.module.ts b/src/app/app-translate.module.ts new file mode 100755 index 0000000..8ab3d0a --- /dev/null +++ b/src/app/app-translate.module.ts @@ -0,0 +1,36 @@ +/** + * 파 일 명: app-tanslate.module.ts + * 작성일자: 2018-12-17 + * 작 성 자: 박병준 + * 설 명: app 영역의 i18n을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { NgModule } from '@angular/core'; +import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; +import { TranslateHttpLoader } from '@ngx-translate/http-loader'; +import { HttpClient } from '@angular/common/http'; + +// AoT requires an exported function for factories +export function createTranslateLoader(http: HttpClient) { + return new TranslateHttpLoader(http, '../assets/i18n/', '.json'); +} + +@NgModule({ + imports: [ + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useFactory: (createTranslateLoader), + deps: [HttpClient] + } + }), + ], + exports: [ + ], + providers: [ + ] +}) +export class AppTranslateModule { } diff --git a/src/app/app.component.html b/src/app/app.component.html new file mode 100755 index 0000000..6c46b1d --- /dev/null +++ b/src/app/app.component.html @@ -0,0 +1 @@ + diff --git a/src/app/app.component.scss b/src/app/app.component.scss new file mode 100755 index 0000000..f8b5b2e --- /dev/null +++ b/src/app/app.component.scss @@ -0,0 +1,29 @@ +app-navbar { + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 2; +} + +pp-root>app-component-sidenav { + flex: 1; +} + +app-root>app-homepage, +app-root>app-guides, +app-root>guide-viewer { + overflow-y: auto; +} + +@media (max-width: 720px) { + app-root { + top: 92px; + } + + app-root>app-homepage, + app-root>app-guides, + app-root>guide-viewer { + overflow-y: visible; + } +} diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts new file mode 100755 index 0000000..b366a76 --- /dev/null +++ b/src/app/app.component.spec.ts @@ -0,0 +1,35 @@ +import { TestBed, async } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { AppComponent } from './app.component'; + +describe('AppComponent', () => { + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule + ], + declarations: [ + AppComponent + ], + }).compileComponents(); + })); + + it('should create the app', () => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.debugElement.componentInstance; + expect(app).toBeTruthy(); + }); + + it(`should have as title '2nd-round'`, () => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.debugElement.componentInstance; + expect(app.title).toEqual('2nd-round'); + }); + + it('should render title in a h1 tag', () => { + const fixture = TestBed.createComponent(AppComponent); + fixture.detectChanges(); + const compiled = fixture.debugElement.nativeElement; + expect(compiled.querySelector('h1').textContent).toContain('Welcome to 2nd-round!'); + }); +}); diff --git a/src/app/app.component.ts b/src/app/app.component.ts new file mode 100755 index 0000000..3ef0671 --- /dev/null +++ b/src/app/app.component.ts @@ -0,0 +1,320 @@ +/** + * 파 일 명: app.component.ts + * 작성일자: 2018-12-17 + * 작 성 자: 박병준 + * 설 명: app component를 정의한다. + * 수정일시: 2010-01-02 + * 수 정 자: 조현정 + * 수정내용: SVG ICONS 주입 + * 참고사항: + */ +import { Component, OnInit } from '@angular/core'; +// Mat-Icons +import { MatIconRegistry } from '@angular/material/icon'; +import { DomSanitizer } from '@angular/platform-browser'; + +@Component({ + selector: 'app-root', + templateUrl: './app.component.html', + styleUrls: ['./app.component.scss'] +}) +export class AppComponent implements OnInit { + constructor(iconRegistry: MatIconRegistry, sanitizer: DomSanitizer) { + // Mat - Icons: ToolBar; + iconRegistry.addSvgIcon( + 'home-outline', + sanitizer.bypassSecurityTrustResourceUrl( + 'assets/img/toolbar-icons/bot_home.svg' + ) + ); + iconRegistry.addSvgIcon( + 'search', + sanitizer.bypassSecurityTrustResourceUrl( + 'assets/img/toolbar-icons/bot_search.svg' + ) + ); + iconRegistry.addSvgIcon( + 'upload', + sanitizer.bypassSecurityTrustResourceUrl( + 'assets/img/toolbar-icons/bot_upload.svg' + ) + ); + iconRegistry.addSvgIcon( + 'bookmark', + sanitizer.bypassSecurityTrustResourceUrl( + 'assets/img/toolbar-icons/bot_bookmark.svg' + ) + ); + iconRegistry.addSvgIcon( + 'myprofil', + sanitizer.bypassSecurityTrustResourceUrl( + 'assets/img/toolbar-icons/bot_my.svg' + ) + ); + // Mat - Icons: General Icons + iconRegistry.addSvgIcon( + 'file-delete-circle', + sanitizer.bypassSecurityTrustResourceUrl( + 'assets/img/icons/close-circle.svg' + ) + ); + iconRegistry.addSvgIcon( + 'btn_bookmark', + sanitizer.bypassSecurityTrustResourceUrl( + 'assets/img/icons/btn_bookmark.svg' + ) + ); + iconRegistry.addSvgIcon( + 'btn_bookmark_checked', + sanitizer.bypassSecurityTrustResourceUrl( + 'assets/img/icons/bookmark-check.svg' + ) + ); + iconRegistry.addSvgIcon( + 'btn_close', + sanitizer.bypassSecurityTrustResourceUrl('assets/img/icons/btn_close.svg') + ); + iconRegistry.addSvgIcon( + 'btn_more', + sanitizer.bypassSecurityTrustResourceUrl('assets/img/icons/btn_more.svg') + ); + iconRegistry.addSvgIcon( + 'btn_settings', + sanitizer.bypassSecurityTrustResourceUrl('assets/img/icons/btn_settings.svg') + ); + iconRegistry.addSvgIcon( + 'btn_share', + sanitizer.bypassSecurityTrustResourceUrl('assets/img/icons/btn_share.svg') + ); + iconRegistry.addSvgIcon( + 'btn_tracing', + sanitizer.bypassSecurityTrustResourceUrl( + 'assets/img/icons/btn_tracing.svg' + ) + ); + iconRegistry.addSvgIcon( + 'btn_reply', + sanitizer.bypassSecurityTrustResourceUrl('assets/img/icons/ico_01.svg') + ); + iconRegistry.addSvgIcon( + 'btn_like', + sanitizer.bypassSecurityTrustResourceUrl('assets/img/icons/ico_02.svg') + ); + iconRegistry.addSvgIcon( + 'btn_like_checked', + sanitizer.bypassSecurityTrustResourceUrl('assets/img/icons/cards-heart.svg') + ); + iconRegistry.addSvgIcon( + 'btn_views', + sanitizer.bypassSecurityTrustResourceUrl('assets/img/icons/ico_03.svg') + ); + iconRegistry.addSvgIcon( + 'btn_camera', + sanitizer.bypassSecurityTrustResourceUrl( + 'assets/img/icons/btn_camera.svg' + ) + ); + iconRegistry.addSvgIcon( + 'top_recommend', + sanitizer.bypassSecurityTrustResourceUrl( + 'assets/img/icons/top_recommend.svg' + ) + ); + iconRegistry.addSvgIcon( + 'top_upload', + sanitizer.bypassSecurityTrustResourceUrl( + 'assets/img/icons/top_upload.svg' + ) + ); + iconRegistry.addSvgIcon( + 'tab_mypro_01', + sanitizer.bypassSecurityTrustResourceUrl('assets/img/icons/my_ico_01.svg') + ); + iconRegistry.addSvgIcon( + 'tab_mypro_02', + sanitizer.bypassSecurityTrustResourceUrl('assets/img/icons/my_ico_02.svg') + ); + iconRegistry.addSvgIcon( + 'tab_mypro_03', + sanitizer.bypassSecurityTrustResourceUrl('assets/img/icons/my_ico_03.svg') + ); + iconRegistry.addSvgIcon( + 'tab_mypro_04', + sanitizer.bypassSecurityTrustResourceUrl('assets/img/icons/my_ico_04.svg') + ); + iconRegistry.addSvgIcon( + 'tab_write_01', + sanitizer.bypassSecurityTrustResourceUrl( + 'assets/img/icons/btn_illustration.svg' + ) + ); + iconRegistry.addSvgIcon( + 'tab_write_02', + sanitizer.bypassSecurityTrustResourceUrl( + 'assets/img/icons/btn_cartoon.svg' + ) + ); + iconRegistry.addSvgIcon( + 'tab_write_03', + sanitizer.bypassSecurityTrustResourceUrl('assets/img/icons/btn_novel.svg') + ); + iconRegistry.addSvgIcon( + 'ico_set_01', + sanitizer.bypassSecurityTrustResourceUrl( + 'assets/img/icons/ico_set_01.svg' + ) + ); + iconRegistry.addSvgIcon( + 'ico_set_02', + sanitizer.bypassSecurityTrustResourceUrl( + 'assets/img/icons/ico_set_02.svg' + ) + ); + iconRegistry.addSvgIcon( + 'ico_set_03', + sanitizer.bypassSecurityTrustResourceUrl( + 'assets/img/icons/ico_set_03.svg' + ) + ); + iconRegistry.addSvgIcon( + 'ico_set_04', + sanitizer.bypassSecurityTrustResourceUrl( + 'assets/img/icons/ico_set_04.svg' + ) + ); + iconRegistry.addSvgIcon( + 'ico_set_05', + sanitizer.bypassSecurityTrustResourceUrl( + 'assets/img/icons/ico_set_05.svg' + ) + ); + iconRegistry.addSvgIcon( + 'ico_set_06', + sanitizer.bypassSecurityTrustResourceUrl( + 'assets/img/icons/ico_set_06.svg' + ) + ); + iconRegistry.addSvgIcon( + 'ico_set_07', + sanitizer.bypassSecurityTrustResourceUrl( + 'assets/img/icons/ico_set_07.svg' + ) + ); + iconRegistry.addSvgIcon( + 'ico_set_08', + sanitizer.bypassSecurityTrustResourceUrl( + 'assets/img/icons/ico_set_08.svg' + ) + ); + iconRegistry.addSvgIcon( + 'login_logo_facebook', + sanitizer.bypassSecurityTrustResourceUrl( + 'assets/img/login/login_logo_facebook.svg' + ) + ); + iconRegistry.addSvgIcon( + 'login_logo_google', + sanitizer.bypassSecurityTrustResourceUrl( + 'assets/img/login/login_logo_google.svg' + ) + ); + iconRegistry.addSvgIcon( + 'login_logo_kakao', + sanitizer.bypassSecurityTrustResourceUrl( + 'assets/img/login/login_logo_kakao.svg' + ) + ); + iconRegistry.addSvgIcon( + 'login_logo_line', + sanitizer.bypassSecurityTrustResourceUrl( + 'assets/img/login/login_logo_line.svg' + ) + ); + iconRegistry.addSvgIcon( + 'login_logo_naver', + sanitizer.bypassSecurityTrustResourceUrl( + 'assets/img/login/login_logo_naver.svg' + ) + ); + iconRegistry.addSvgIcon( + 'login_logo_twitter', + sanitizer.bypassSecurityTrustResourceUrl( + 'assets/img/login/login_logo_twitter.svg' + ) + ); + iconRegistry.addSvgIcon( + 'login_logo_weibo', + sanitizer.bypassSecurityTrustResourceUrl( + 'assets/img/login/login_logo_weibo.svg' + ) + ); + iconRegistry.addSvgIcon( + 'login_logo_instagram', + sanitizer.bypassSecurityTrustResourceUrl( + 'assets/img/login/login_logo_insta.svg' + ) + ); + iconRegistry.addSvgIcon( + 'login_logo_googlePlus', + sanitizer.bypassSecurityTrustResourceUrl( + 'assets/img/login/login_logo_gplus.svg' + ) + ); + iconRegistry.addSvgIcon( + 'login_caution', + sanitizer.bypassSecurityTrustResourceUrl( + 'assets/img/login/login_ico_01.svg' + ) + ); + iconRegistry.addSvgIcon( + 'ico_share_03', + sanitizer.bypassSecurityTrustResourceUrl( + 'assets/img/icons/ico_share_03.svg' + ) + ); + iconRegistry.addSvgIcon( + 'ico_share_04', + sanitizer.bypassSecurityTrustResourceUrl( + 'assets/img/icons/ico_share_04.svg' + ) + ); + iconRegistry.addSvgIcon( + 'step-backward', + sanitizer.bypassSecurityTrustResourceUrl( + 'assets/img/icons/ico_check2.svg' + ) + ); + iconRegistry.addSvgIcon( + 'step_backward', + sanitizer.bypassSecurityTrustResourceUrl( + 'assets/img/icons/step-backward.svg' + ) + ); + iconRegistry.addSvgIcon( + 'msg_ok', + sanitizer.bypassSecurityTrustResourceUrl( + 'assets/img/icons/comment-check.svg' + ) + ); + iconRegistry.addSvgIcon( + 'msg_notice', + sanitizer.bypassSecurityTrustResourceUrl( + 'assets/img/icons/message-alert.svg' + ) + ); + iconRegistry.addSvgIcon( + 'msg_fail', + sanitizer.bypassSecurityTrustResourceUrl( + 'assets/img/icons/message-bulleted-off.svg' + ) + ); + iconRegistry.addSvgIcon( + 'msg_bigfail', + sanitizer.bypassSecurityTrustResourceUrl( + 'assets/img/icons/alert-octagram.svg' + ) + ); + } + + ngOnInit(): void { } +} diff --git a/src/app/app.module.ts b/src/app/app.module.ts new file mode 100755 index 0000000..cac35a3 --- /dev/null +++ b/src/app/app.module.ts @@ -0,0 +1,44 @@ +/** + * 파 일 명: app.module.ts + * 작성일자: 2018-12-17 + * 작 성 자: 박병준 + * 설 명: app 영역의 module을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { BrowserModule } from '@angular/platform-browser'; +import { NgModule } from '@angular/core'; + +import { HttpClientModule } from '@angular/common/http'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; + +import { SharedModule } from '../modules/common/shared/shared.module'; + +import { AppRoutingModule } from './app-routing.module'; +import { AppTranslateModule } from './app-translate.module'; +import { AppStoreModule } from './app-store.module'; +import { AppProviderModule } from './app-provider.module'; + +import { AppComponent } from './app.component'; +import { LayoutModule } from './layout/layout.module'; + +@NgModule({ + imports: [ + BrowserModule, + HttpClientModule, + BrowserAnimationsModule, + AppRoutingModule, + AppStoreModule, + AppProviderModule, + AppTranslateModule, + SharedModule, + LayoutModule, + ], + declarations: [ + AppComponent, + ], + bootstrap: [AppComponent] +}) +export class AppModule { } diff --git a/src/app/guard/article-owner.guard.ts b/src/app/guard/article-owner.guard.ts new file mode 100755 index 0000000..f22dc28 --- /dev/null +++ b/src/app/guard/article-owner.guard.ts @@ -0,0 +1,53 @@ +import { Injectable } from '@angular/core'; +import { + CanActivate, + ActivatedRouteSnapshot, + RouterStateSnapshot, + Router, +} from '@angular/router'; + +import { Store, } from '@ngrx/store'; +import { of } from 'rxjs'; +import { map, take, catchError } from 'rxjs/operators'; + +import { ArticleGetService } from '../../modules/article/service/article.get.service'; +import { ArticleMongodb } from '../../shared/article/model/article.mongodb.model'; +import { AccountsUtil } from 'src/modules/accounts/util/accounts.util'; + +@Injectable() +export class ArticleOwnerGuard implements CanActivate { + constructor( + private store: Store, + private router: Router, + private articleGetService: ArticleGetService, + ) { } + + canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise { + return new Promise((resolve, reject) => { + const aid = route.paramMap.get('aid'); + if (!aid) { + return resolve(true); + } + + this.articleGetService.get(aid).pipe( + take(1), + map(async (articleMongodb: ArticleMongodb) => { + if (!articleMongodb) { + return resolve(false); + } + const isOwner = await AccountsUtil.checkOwner(this.store, articleMongodb.userId); + if (!isOwner) { + this.router.navigate(['/']); + return resolve(false); + } + return resolve(true); + }), + catchError(error => { + reject(error); + return of(error); + }), + ).subscribe(); + }); + } + +} diff --git a/src/app/guard/auth-nickname.guard.ts b/src/app/guard/auth-nickname.guard.ts new file mode 100755 index 0000000..808a5fc --- /dev/null +++ b/src/app/guard/auth-nickname.guard.ts @@ -0,0 +1,40 @@ +import { Injectable } from '@angular/core'; +import { + CanActivate, + ActivatedRouteSnapshot, + RouterStateSnapshot, + Router, +} from '@angular/router'; + +import { Store, } from '@ngrx/store'; + +import * as EventsStore from '../../modules/common/shared/store/events'; +import { AccountsUtil } from '../../modules/accounts/util/accounts.util'; + +@Injectable() +export class AuthNicknameGuard implements CanActivate { + constructor( + private store: Store, + private router: Router, + ) { } + + canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise { + return new Promise(async (resolve, reject) => { + const user = await AccountsUtil.getUser(this.store); + if (!user) { + return resolve(true); + } + + if (!user.nickname) { + this.store.dispatch(new EventsStore.ExecutionFailure({ + title: 'Nickname is not valid', + message: 'You need to specify your nickname before you can author.', + })); + this.router.navigate(['/user/edit']); + return resolve(false); + } + return resolve(true); + }); + } + +} diff --git a/src/app/guard/auth.guard.ts b/src/app/guard/auth.guard.ts new file mode 100755 index 0000000..fb5dac3 --- /dev/null +++ b/src/app/guard/auth.guard.ts @@ -0,0 +1,47 @@ +import { Injectable } from '@angular/core'; +import { + CanActivate, + ActivatedRouteSnapshot, + RouterStateSnapshot, + Router, +} from '@angular/router'; + +import { Store, select } from '@ngrx/store'; +import { Observable } from 'rxjs'; +import { map, take } from 'rxjs/operators'; +import { CookieService } from 'ngx-cookie-service'; + +import { AuthSelector } from '../../modules/accounts/store'; +import * as AppLoginStore from '../store/login'; + +@Injectable() +export class AuthGuard implements CanActivate { + constructor( + private store: Store, + private router: Router, + private cookieService: CookieService, + ) { } + + canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { + return this.store.pipe( + take(1), + select(AuthSelector.selectLogined), + map((logined) => { + if (!logined) { + if (this.cookieService.check('jwt')) { + this.store.dispatch(new AppLoginStore.LoginToken({ + token: this.cookieService.get('jwt'), + returnURL: state.url + })); + } else { + this.store.dispatch(new AppLoginStore.LoginRedirect({ returnURL: state.url })); + } + return false; + } + + return true; + }), + ); + } + +} diff --git a/src/app/guard/can-deactivate.guard.ts b/src/app/guard/can-deactivate.guard.ts new file mode 100755 index 0000000..bbc4979 --- /dev/null +++ b/src/app/guard/can-deactivate.guard.ts @@ -0,0 +1,27 @@ +import { Injectable } from '@angular/core'; +import { + ActivatedRouteSnapshot, + RouterStateSnapshot, + CanDeactivate as RouterCanDeactivate, +} from '@angular/router'; +import { Observable } from 'rxjs'; + +export interface CanDeactivate { + canDeactivate(): Observable | Promise | boolean; +} + +@Injectable() +export class CanDeactivateGuard implements RouterCanDeactivate { + + constructor() { } + + canDeactivate( + component: CanDeactivate, + currentRoute: ActivatedRouteSnapshot, + currentState: RouterStateSnapshot, + nextState?: RouterStateSnapshot): boolean | Observable | Promise { + + return component.canDeactivate ? component.canDeactivate() : true; + } + +} diff --git a/src/app/guard/index.ts b/src/app/guard/index.ts new file mode 100755 index 0000000..bf308db --- /dev/null +++ b/src/app/guard/index.ts @@ -0,0 +1,11 @@ +import { ArticleOwnerGuard } from './article-owner.guard'; +import { AuthGuard } from './auth.guard'; +import { AuthNicknameGuard } from './auth-nickname.guard'; +import { CanDeactivateGuard } from './can-deactivate.guard'; + +export const GUARDS = [ + ArticleOwnerGuard, + AuthGuard, + AuthNicknameGuard, + CanDeactivateGuard, +]; diff --git a/src/app/layout/component/accounts/accounts.layout.component.html b/src/app/layout/component/accounts/accounts.layout.component.html new file mode 100755 index 0000000..1bd6b12 --- /dev/null +++ b/src/app/layout/component/accounts/accounts.layout.component.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/src/app/layout/component/accounts/accounts.layout.component.scss b/src/app/layout/component/accounts/accounts.layout.component.scss new file mode 100755 index 0000000..487c701 --- /dev/null +++ b/src/app/layout/component/accounts/accounts.layout.component.scss @@ -0,0 +1,4 @@ +.accountsLayout { + margin-top: -55px; + margin-bottom: -80px; +} diff --git a/src/app/layout/component/accounts/accounts.layout.component.spec.ts b/src/app/layout/component/accounts/accounts.layout.component.spec.ts new file mode 100755 index 0000000..8b39519 --- /dev/null +++ b/src/app/layout/component/accounts/accounts.layout.component.spec.ts @@ -0,0 +1,35 @@ +import { TestBed, async } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { AccountsLayoutComponent } from './accounts.layout.component'; + +describe('AccountsLayoutComponent', () => { + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule + ], + declarations: [ + AccountsLayoutComponent + ], + }).compileComponents(); + })); + + it('should create the app', () => { + const fixture = TestBed.createComponent(AccountsLayoutComponent); + const app = fixture.debugElement.componentInstance; + expect(app).toBeTruthy(); + }); + + it(`should have as title '2nd-round'`, () => { + const fixture = TestBed.createComponent(AccountsLayoutComponent); + const app = fixture.debugElement.componentInstance; + expect(app.title).toEqual('2nd-round'); + }); + + it('should render title in a h1 tag', () => { + const fixture = TestBed.createComponent(AccountsLayoutComponent); + fixture.detectChanges(); + const compiled = fixture.debugElement.nativeElement; + expect(compiled.querySelector('h1').textContent).toContain('Welcome to 2nd-round!'); + }); +}); diff --git a/src/app/layout/component/accounts/accounts.layout.component.ts b/src/app/layout/component/accounts/accounts.layout.component.ts new file mode 100755 index 0000000..f3b52f5 --- /dev/null +++ b/src/app/layout/component/accounts/accounts.layout.component.ts @@ -0,0 +1,25 @@ +/** + * 파 일 명: accounts.layout.component.ts + * 작성일자: 2018-12-21 + * 작 성 자: 박병준 + * 설 명: Accounts layout component를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-layout-accounts', + templateUrl: './accounts.layout.component.html', + styleUrls: ['./accounts.layout.component.scss'] +}) +export class AccountsLayoutComponent implements OnInit { + constructor( + ) { + } + + ngOnInit(): void { + } +} diff --git a/src/app/layout/component/avatar/avatar.component.html b/src/app/layout/component/avatar/avatar.component.html new file mode 100755 index 0000000..9a4d3aa --- /dev/null +++ b/src/app/layout/component/avatar/avatar.component.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/src/app/layout/component/avatar/avatar.component.scss b/src/app/layout/component/avatar/avatar.component.scss new file mode 100755 index 0000000..d2662e7 --- /dev/null +++ b/src/app/layout/component/avatar/avatar.component.scss @@ -0,0 +1,8 @@ +:host { + cursor: pointer; +} + +.avatar-content { + max-width: 100%; + border-radius: 50%; +} diff --git a/src/app/layout/component/avatar/avatar.component.ts b/src/app/layout/component/avatar/avatar.component.ts new file mode 100755 index 0000000..dc4ed44 --- /dev/null +++ b/src/app/layout/component/avatar/avatar.component.ts @@ -0,0 +1,28 @@ +import { Component, Input, Output, EventEmitter } from '@angular/core'; + +@Component({ + selector: 'app-avatar', + templateUrl: './avatar.component.html', + styleUrls: ['./avatar.component.scss'] +}) +export class AvatarComponent { + @Input() + src: string; + @Input() + size = 50; + + @Output() + clickOnAvatar: EventEmitter = new EventEmitter(); + + constructor( + + ) { + + } + + handleClickEvent(event: any) { + if (event) { + this.clickOnAvatar.emit(); + } + } +} diff --git a/src/app/layout/component/default/default.layout.component.html b/src/app/layout/component/default/default.layout.component.html new file mode 100755 index 0000000..84a6c82 --- /dev/null +++ b/src/app/layout/component/default/default.layout.component.html @@ -0,0 +1,18 @@ + + + + + +
+ +
+ + +
+ +
+ +
+
+ +
diff --git a/src/app/layout/component/default/default.layout.component.scss b/src/app/layout/component/default/default.layout.component.scss new file mode 100755 index 0000000..f28d5d5 --- /dev/null +++ b/src/app/layout/component/default/default.layout.component.scss @@ -0,0 +1,37 @@ +app-navbar { + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 2; + +} + +pp-root>app-component-sidenav { + flex: 1; +} + +app-root>app-homepage, +app-root>app-guides, +app-root>guide-viewer { + overflow-y: auto; +} + +app-navbar-mobile { + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 2; + +} + +pp-root>app-component-sidenav { + flex: 1; +} + +app-root>app-homepage, +app-root>app-guides, +app-root>guide-viewer { + overflow-y: auto; +} diff --git a/src/app/layout/component/default/default.layout.component.spec.ts b/src/app/layout/component/default/default.layout.component.spec.ts new file mode 100755 index 0000000..52d469e --- /dev/null +++ b/src/app/layout/component/default/default.layout.component.spec.ts @@ -0,0 +1,35 @@ +import { TestBed, async } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { DefaultLayoutComponent } from './default.layout.component'; + +describe('DefaultLayoutComponent', () => { + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule + ], + declarations: [ + DefaultLayoutComponent + ], + }).compileComponents(); + })); + + it('should create the app', () => { + const fixture = TestBed.createComponent(DefaultLayoutComponent); + const app = fixture.debugElement.componentInstance; + expect(app).toBeTruthy(); + }); + + it(`should have as title '2nd-round'`, () => { + const fixture = TestBed.createComponent(DefaultLayoutComponent); + const app = fixture.debugElement.componentInstance; + expect(app.title).toEqual('2nd-round'); + }); + + it('should render title in a h1 tag', () => { + const fixture = TestBed.createComponent(DefaultLayoutComponent); + fixture.detectChanges(); + const compiled = fixture.debugElement.nativeElement; + expect(compiled.querySelector('h1').textContent).toContain('Welcome to 2nd-round!'); + }); +}); diff --git a/src/app/layout/component/default/default.layout.component.ts b/src/app/layout/component/default/default.layout.component.ts new file mode 100755 index 0000000..bbf54d2 --- /dev/null +++ b/src/app/layout/component/default/default.layout.component.ts @@ -0,0 +1,47 @@ +/** + * 파 일 명: default.layout.component.ts + * 작성일자: 2018-12-21 + * 작 성 자: 박병준 + * 설 명: Default layout component를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Store, select } from '@ngrx/store'; + +import { Subscription, of, Observable } from 'rxjs'; +import { map, catchError } from 'rxjs/operators'; + +import * as AppStore from '../../../store'; +import { Environment } from '../../../type/environment.type'; + +@Component({ + selector: 'app-layout-default', + templateUrl: './default.layout.component.html', + styleUrls: ['./default.layout.component.scss'] +}) +export class DefaultLayoutComponent implements OnInit, OnDestroy { + environment$: Observable; + + showProgressBar$: Observable; + + constructor( + private store: Store, + ) { + } + + ngOnInit(): void { + this.environment$ = this.store.pipe( + select(AppStore.AppLayoutSelector.selectEnvironment), + ); + this.showProgressBar$ = this.store.pipe( + select(AppStore.AppEventsSelector.selectShowProgressBar), + ); + } + + ngOnDestroy(): void { + } + +} diff --git a/src/app/layout/component/index.ts b/src/app/layout/component/index.ts new file mode 100755 index 0000000..751ce0a --- /dev/null +++ b/src/app/layout/component/index.ts @@ -0,0 +1,17 @@ +import { AccountsLayoutComponent } from './accounts/accounts.layout.component'; +import { AvatarComponent } from './avatar/avatar.component'; +import { DefaultLayoutComponent } from './default/default.layout.component'; +import { MainLayoutComponent } from './main/main.layout.component'; +import { NavBarComponent } from './navbar/navbar.component'; +import { NavBarMobileComponent } from './navbar/navbar-mobile/navbar-mobile.component'; +import { ToolBarComponent } from './toolbar/toolbar.component'; + +export const COMPONENTS = [ + AccountsLayoutComponent, + AvatarComponent, + DefaultLayoutComponent, + MainLayoutComponent, + NavBarComponent, + NavBarMobileComponent, + ToolBarComponent +]; diff --git a/src/app/layout/component/main/main.layout.component.html b/src/app/layout/component/main/main.layout.component.html new file mode 100755 index 0000000..63f8c69 --- /dev/null +++ b/src/app/layout/component/main/main.layout.component.html @@ -0,0 +1,21 @@ + + + + + +
+ +
+ + +
+ +
+ +
+ +
+ +
diff --git a/src/app/layout/component/main/main.layout.component.scss b/src/app/layout/component/main/main.layout.component.scss new file mode 100755 index 0000000..f28d5d5 --- /dev/null +++ b/src/app/layout/component/main/main.layout.component.scss @@ -0,0 +1,37 @@ +app-navbar { + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 2; + +} + +pp-root>app-component-sidenav { + flex: 1; +} + +app-root>app-homepage, +app-root>app-guides, +app-root>guide-viewer { + overflow-y: auto; +} + +app-navbar-mobile { + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 2; + +} + +pp-root>app-component-sidenav { + flex: 1; +} + +app-root>app-homepage, +app-root>app-guides, +app-root>guide-viewer { + overflow-y: auto; +} diff --git a/src/app/layout/component/main/main.layout.component.spec.ts b/src/app/layout/component/main/main.layout.component.spec.ts new file mode 100755 index 0000000..60c98a0 --- /dev/null +++ b/src/app/layout/component/main/main.layout.component.spec.ts @@ -0,0 +1,35 @@ +import { TestBed, async } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { MainLayoutComponent } from './main.layout.component'; + +describe('MainLayoutComponent', () => { + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule + ], + declarations: [ + MainLayoutComponent + ], + }).compileComponents(); + })); + + it('should create the app', () => { + const fixture = TestBed.createComponent(MainLayoutComponent); + const app = fixture.debugElement.componentInstance; + expect(app).toBeTruthy(); + }); + + it(`should have as title '2nd-round'`, () => { + const fixture = TestBed.createComponent(MainLayoutComponent); + const app = fixture.debugElement.componentInstance; + expect(app.title).toEqual('2nd-round'); + }); + + it('should render title in a h1 tag', () => { + const fixture = TestBed.createComponent(MainLayoutComponent); + fixture.detectChanges(); + const compiled = fixture.debugElement.nativeElement; + expect(compiled.querySelector('h1').textContent).toContain('Welcome to 2nd-round!'); + }); +}); diff --git a/src/app/layout/component/main/main.layout.component.ts b/src/app/layout/component/main/main.layout.component.ts new file mode 100755 index 0000000..33b91c7 --- /dev/null +++ b/src/app/layout/component/main/main.layout.component.ts @@ -0,0 +1,47 @@ +/** + * 파 일 명: Main.layout.component.ts + * 작성일자: 2019-1-23 + * 작 성 자: 조현정 + * 설 명: Main layout component를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Store, select } from '@ngrx/store'; + +import { Subscription, of, Observable } from 'rxjs'; +import { map, catchError } from 'rxjs/operators'; + +import * as AppStore from '../../../store'; +import { Environment } from '../../../type/environment.type'; + +@Component({ + selector: 'app-layout-main', + templateUrl: './main.layout.component.html', + styleUrls: ['./main.layout.component.scss'] +}) +export class MainLayoutComponent implements OnInit, OnDestroy { + environment$: Observable; + + showProgressBar$: Observable; + + constructor( + private store: Store, + ) { + } + + ngOnInit(): void { + this.environment$ = this.store.pipe( + select(AppStore.AppLayoutSelector.selectEnvironment), + ); + this.showProgressBar$ = this.store.pipe( + select(AppStore.AppEventsSelector.selectShowProgressBar), + ); + } + + ngOnDestroy(): void { + } + +} diff --git a/src/app/layout/component/navbar/navbar-mobile/navbar-mobile.component.html b/src/app/layout/component/navbar/navbar-mobile/navbar-mobile.component.html new file mode 100755 index 0000000..544d3e0 --- /dev/null +++ b/src/app/layout/component/navbar/navbar-mobile/navbar-mobile.component.html @@ -0,0 +1,17 @@ + + +
+
+

+ + coco's logo + +

+
+
+
+
diff --git a/src/app/layout/component/navbar/navbar-mobile/navbar-mobile.component.scss b/src/app/layout/component/navbar/navbar-mobile/navbar-mobile.component.scss new file mode 100755 index 0000000..ec142da --- /dev/null +++ b/src/app/layout/component/navbar/navbar-mobile/navbar-mobile.component.scss @@ -0,0 +1,41 @@ +@import '../../default/default.layout.component.scss'; +$topbar-height:54px; + +.mat-toolbar-multiple-rows { + min-height: $topbar-height; +} + +.mat-toolbar-row, +.mat-toolbar-single-row { + height: $topbar-height; +} + +.logo { + text-align: center; + display: flex; + margin: auto; + + a { + display: flex; + margin: auto; + + img { + height: 50px; + } + } +} + +mat-toolbar { + background: #fff; + border-bottom: 1px solid #e9e9e9; + + .mat-button, + .mat-button * { + min-width: 24px !important; + } + + .mat-button { + padding: 0 3px !important; + color: #bababa; + } +} diff --git a/src/app/layout/component/navbar/navbar-mobile/navbar-mobile.component.ts b/src/app/layout/component/navbar/navbar-mobile/navbar-mobile.component.ts new file mode 100755 index 0000000..5d0f193 --- /dev/null +++ b/src/app/layout/component/navbar/navbar-mobile/navbar-mobile.component.ts @@ -0,0 +1,28 @@ +import { Component, ViewChild } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { MatMenuTrigger } from '@angular/material'; +import { take } from 'rxjs/operators'; + +@Component({ + selector: 'app-navbar-mobile', + templateUrl: './navbar-mobile.component.html', + styleUrls: ['./navbar-mobile.component.scss'] +}) +export class NavBarMobileComponent { + @ViewChild('notificationMenuBtn', { read: MatMenuTrigger }) + protected notificationMenuBtn: MatMenuTrigger; + + constructor( + private translateService: TranslateService + ) { + + } + + changeLanguage(lang: string) { + this.translateService.use(lang).pipe(take(1)).subscribe((res: any) => { + localStorage.setItem('defaultLang', lang); + }); + + } + +} diff --git a/src/app/layout/component/navbar/navbar.component.html b/src/app/layout/component/navbar/navbar.component.html new file mode 100755 index 0000000..0120ca2 --- /dev/null +++ b/src/app/layout/component/navbar/navbar.component.html @@ -0,0 +1,34 @@ + + +
+
+

+ + coco's logo + +

+
+ + + + + +
+
+
diff --git a/src/app/layout/component/navbar/navbar.component.scss b/src/app/layout/component/navbar/navbar.component.scss new file mode 100755 index 0000000..79b7eaa --- /dev/null +++ b/src/app/layout/component/navbar/navbar.component.scss @@ -0,0 +1,44 @@ +@import '../default/default.layout.component.scss'; +$topbar-height:54px; + +.mat-toolbar-multiple-rows { + min-height: $topbar-height; + max-width: 800px; + margin: 0 auto; +} + +.mat-toolbar-row, +.mat-toolbar-single-row { + height: $topbar-height; +} + +.logo { + text-align: center; + display: flex; + margin: auto; + padding-left: 125px; + + a { + display: flex; + margin: auto; + + img { + height: 50px; + } + } +} + +mat-toolbar { + background: #fff; + border-bottom: 1px solid #e9e9e9; + + .mat-button, + .mat-button * { + min-width: 24px !important; + } + + .mat-button { + padding: 0 3px !important; + color: #bababa; + } +} diff --git a/src/app/layout/component/navbar/navbar.component.ts b/src/app/layout/component/navbar/navbar.component.ts new file mode 100755 index 0000000..52546d6 --- /dev/null +++ b/src/app/layout/component/navbar/navbar.component.ts @@ -0,0 +1,46 @@ +import { Component, ViewChild, OnInit } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { MatMenuTrigger } from '@angular/material'; +import { take } from 'rxjs/operators'; +import { Router } from '@angular/router'; +import { Store, } from '@ngrx/store'; +import { User } from '../../../../shared/user/model/user.model'; +import { AccountsUtil } from 'src/modules/accounts/util/accounts.util'; + +@Component({ + selector: 'app-navbar', + templateUrl: './navbar.component.html', + styleUrls: ['./navbar.component.scss'] +}) +export class NavBarComponent implements OnInit { + user$: Promise; + myProfileLink = ['/accounts/authentication']; + + @ViewChild('notificationMenuBtn', { read: MatMenuTrigger }) + protected notificationMenuBtn: MatMenuTrigger; + + constructor( + private translateService: TranslateService, + private router: Router, + private store: Store + ) { + } + + ngOnInit() { + AccountsUtil.getUser(this.store) + .then((user) => { + if (user) { + this.myProfileLink = ['/article', user.id]; + } + }) + .catch((reason) => { + console.log(reason); + }); + } + + changeLanguage(lang: string) { + this.translateService.use(lang).pipe(take(1)).subscribe((res: any) => { + localStorage.setItem('defaultLang', lang); + }); + } +} diff --git a/src/app/layout/component/toolbar/toolbar.component.html b/src/app/layout/component/toolbar/toolbar.component.html new file mode 100755 index 0000000..eb0fe74 --- /dev/null +++ b/src/app/layout/component/toolbar/toolbar.component.html @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + diff --git a/src/app/layout/component/toolbar/toolbar.component.scss b/src/app/layout/component/toolbar/toolbar.component.scss new file mode 100755 index 0000000..e520d12 --- /dev/null +++ b/src/app/layout/component/toolbar/toolbar.component.scss @@ -0,0 +1,30 @@ +/** No CSS for this*/ +.fill-remaining-space { + flex: 1 1 auto; +} + +mat-toolbar { + $toolbarBG: #f9f9f9; + $toolbarColor: #000; + position: fixed; + bottom: 0; + z-index: 100; + width: 100%; + background: $toolbarBG; + + .mat-button { + padding: 0 3px !important; + color: $toolbarColor; + } + + /* toolbar size responsive + */ + @media (max-width: 800px) { + padding: 0 16px; + height: 64px; + } + + @media (min-width: 801px) { + display: none; + } +} diff --git a/src/app/layout/component/toolbar/toolbar.component.ts b/src/app/layout/component/toolbar/toolbar.component.ts new file mode 100755 index 0000000..a943170 --- /dev/null +++ b/src/app/layout/component/toolbar/toolbar.component.ts @@ -0,0 +1,29 @@ +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; +import { Store, } from '@ngrx/store'; +import { User } from '../../../../shared/user/model/user.model'; +import { AccountsUtil } from 'src/modules/accounts/util/accounts.util'; + +@Component({ + selector: 'app-toolbar', + templateUrl: './toolbar.component.html', + styleUrls: ['./toolbar.component.scss'] +}) +export class ToolBarComponent implements OnInit { + user$: Promise; + myProfileLink = ['/accounts/authentication']; + + constructor(private router: Router, private store: Store) { } + + ngOnInit() { + AccountsUtil.getUser(this.store) + .then((user) => { + if (user) { + this.myProfileLink = ['/article', user.id]; + } + }) + .catch((reason) => { + console.log(reason); + }); + } +} diff --git a/src/app/layout/dialog/can-deactivate/can-deactivate.dialog.component.html b/src/app/layout/dialog/can-deactivate/can-deactivate.dialog.component.html new file mode 100755 index 0000000..3bbfeb0 --- /dev/null +++ b/src/app/layout/dialog/can-deactivate/can-deactivate.dialog.component.html @@ -0,0 +1,15 @@ + + + {{data.title}} + + + +

+ {{data.message}} +

+
+ + + + +
diff --git a/src/app/layout/dialog/can-deactivate/can-deactivate.dialog.component.scss b/src/app/layout/dialog/can-deactivate/can-deactivate.dialog.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/app/layout/dialog/can-deactivate/can-deactivate.dialog.component.ts b/src/app/layout/dialog/can-deactivate/can-deactivate.dialog.component.ts new file mode 100755 index 0000000..10816dc --- /dev/null +++ b/src/app/layout/dialog/can-deactivate/can-deactivate.dialog.component.ts @@ -0,0 +1,38 @@ +import { Component, OnInit, Input, Output, EventEmitter, Inject } from '@angular/core'; +import { FormGroupDirective, NgForm, FormControl, FormGroup, FormBuilder, Validators } from '@angular/forms'; +import { ErrorStateMatcher, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material'; +import { Observable, of } from 'rxjs'; +import { take, tap, map, catchError, switchMap, finalize } from 'rxjs/operators'; +import { Store } from '@ngrx/store'; + +export interface CanDeactivateDialogData { + title: string; + message: string; +} + +export interface CanDeactivateDialogResult { + choice: boolean; +} + +@Component({ + selector: 'app-can-deactivate-dialog', + templateUrl: './can-deactivate.dialog.component.html', + styleUrls: ['./can-deactivate.dialog.component.scss'] +}) +export class CanDeactivateDialogComponent implements OnInit { + + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) private data: CanDeactivateDialogData, + ) { + } + + ngOnInit() { + } + + onClickChoice(choice: boolean) { + this.dialogRef.close({ + choice: choice, + }); + } +} diff --git a/src/app/layout/dialog/confirm/confirm.dialog.component.html b/src/app/layout/dialog/confirm/confirm.dialog.component.html new file mode 100755 index 0000000..227dbbf --- /dev/null +++ b/src/app/layout/dialog/confirm/confirm.dialog.component.html @@ -0,0 +1,15 @@ + + + {{data.title}} + + + +

+ {{data.message}} +

+
+ + + + +
diff --git a/src/app/layout/dialog/confirm/confirm.dialog.component.scss b/src/app/layout/dialog/confirm/confirm.dialog.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/app/layout/dialog/confirm/confirm.dialog.component.spec.ts b/src/app/layout/dialog/confirm/confirm.dialog.component.spec.ts new file mode 100755 index 0000000..0a48e7e --- /dev/null +++ b/src/app/layout/dialog/confirm/confirm.dialog.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ConfirmDialogComponent } from './confirm.dialog.component'; + +describe('ConfirmDialogComponent', () => { + let component: ConfirmDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ConfirmDialogComponent] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ConfirmDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/layout/dialog/confirm/confirm.dialog.component.ts b/src/app/layout/dialog/confirm/confirm.dialog.component.ts new file mode 100755 index 0000000..674bc70 --- /dev/null +++ b/src/app/layout/dialog/confirm/confirm.dialog.component.ts @@ -0,0 +1,49 @@ +/** + * 파 일 명: confirm.dialog.component.ts + * 작성일자: 2019-01-13 + * 작 성 자: 박병준 + * 설 명: Confirm Dialog component를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Component, OnInit, Inject } from '@angular/core'; +import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material'; + +export interface ConfirmDialogData { + title: string; + message?: string; +} + +export interface ConfirmDialogResult { + choice: boolean; +} + +@Component({ + selector: 'app-confirm-dialog', + templateUrl: './confirm.dialog.component.html', + styleUrls: [ + './confirm.dialog.component.scss', + ], +}) +export class ConfirmDialogComponent implements OnInit { + + tempAgeLimits = []; + + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: ConfirmDialogData + ) { + } + + ngOnInit(): void { + } + + onClickChoice(choice: boolean): void { + this.dialogRef.close({ + choice: choice, + }); + } + +} diff --git a/src/app/layout/dialog/events/failure/failure.snackbar.component.html b/src/app/layout/dialog/events/failure/failure.snackbar.component.html new file mode 100755 index 0000000..8482edb --- /dev/null +++ b/src/app/layout/dialog/events/failure/failure.snackbar.component.html @@ -0,0 +1,12 @@ +
+ + failure {{data?.title}} + + {{data?.message}} +
+ diff --git a/src/app/layout/dialog/events/failure/failure.snackbar.component.scss b/src/app/layout/dialog/events/failure/failure.snackbar.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/app/layout/dialog/events/failure/failure.snackbar.component.ts b/src/app/layout/dialog/events/failure/failure.snackbar.component.ts new file mode 100755 index 0000000..9d41049 --- /dev/null +++ b/src/app/layout/dialog/events/failure/failure.snackbar.component.ts @@ -0,0 +1,24 @@ +import { Component, OnInit, ViewChild, ElementRef, Inject } from '@angular/core'; +import { MAT_SNACK_BAR_DATA } from '@angular/material'; + +export interface FailureSnackBarData { + title?: string; + message?: string; + error?: Error; +} + +@Component({ + selector: 'app-events-failure-snackbar', + templateUrl: './failure.snackbar.component.html', + styleUrls: ['./failure.snackbar.component.scss'] +}) +export class FailureSnackBarComponent implements OnInit { + + constructor( + @Inject(MAT_SNACK_BAR_DATA) public data: FailureSnackBarData, + ) { + + } + + ngOnInit() { } +} diff --git a/src/app/layout/dialog/events/index.ts b/src/app/layout/dialog/events/index.ts new file mode 100755 index 0000000..cc7f9ca --- /dev/null +++ b/src/app/layout/dialog/events/index.ts @@ -0,0 +1,7 @@ +import { FailureSnackBarComponent } from './failure/failure.snackbar.component'; +import { SuccessSnackBarComponent } from './success/success.snackbar.component'; + +export const EVENTS_DIALOGS = [ + FailureSnackBarComponent, + SuccessSnackBarComponent, +]; diff --git a/src/app/layout/dialog/events/success/success.snackbar.component.html b/src/app/layout/dialog/events/success/success.snackbar.component.html new file mode 100755 index 0000000..2cdf3e3 --- /dev/null +++ b/src/app/layout/dialog/events/success/success.snackbar.component.html @@ -0,0 +1,12 @@ +
+ + success {{data?.title}} + + {{data?.message}} +
+ diff --git a/src/app/layout/dialog/events/success/success.snackbar.component.scss b/src/app/layout/dialog/events/success/success.snackbar.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/app/layout/dialog/events/success/success.snackbar.component.ts b/src/app/layout/dialog/events/success/success.snackbar.component.ts new file mode 100755 index 0000000..85d0df9 --- /dev/null +++ b/src/app/layout/dialog/events/success/success.snackbar.component.ts @@ -0,0 +1,23 @@ +import { Component, OnInit, Inject } from '@angular/core'; +import { MAT_SNACK_BAR_DATA } from '@angular/material'; + +export interface SuccessSnackBarData { + title?: string; + message?: string; +} + +@Component({ + selector: 'app-events-success-snackbar', + templateUrl: './success.snackbar.component.html', + styleUrls: ['./success.snackbar.component.scss'] +}) +export class SuccessSnackBarComponent implements OnInit { + + constructor( + @Inject(MAT_SNACK_BAR_DATA) public data: SuccessSnackBarData, + ) { + + } + + ngOnInit() { } +} diff --git a/src/app/layout/dialog/index.ts b/src/app/layout/dialog/index.ts new file mode 100755 index 0000000..69b9039 --- /dev/null +++ b/src/app/layout/dialog/index.ts @@ -0,0 +1,9 @@ +import { CanDeactivateDialogComponent } from './can-deactivate/can-deactivate.dialog.component'; +import { ConfirmDialogComponent } from './confirm/confirm.dialog.component'; +import { EVENTS_DIALOGS } from './events'; + +export const DIALOGS = [ + CanDeactivateDialogComponent, + ConfirmDialogComponent, + ...EVENTS_DIALOGS, +]; diff --git a/src/app/layout/layout.module.ts b/src/app/layout/layout.module.ts new file mode 100755 index 0000000..2a14859 --- /dev/null +++ b/src/app/layout/layout.module.ts @@ -0,0 +1,57 @@ +/** + * 파 일 명: accounts.module.ts + * 작성일자: 2018-12-20 + * 작 성 자: 박병준 + * 설 명: accounts module을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { NgModule, ModuleWithProviders } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { RouterModule } from '@angular/router'; +import { TranslateModule } from '@ngx-translate/core'; + +import { SharedModule } from '../../modules/common/shared/shared.module'; +import { AccountsModule } from '../../modules/accounts/accounts.module'; +import { COMPONENTS } from './component'; +import { DIALOGS } from './dialog'; + +@NgModule({ + imports: [ + CommonModule, + RouterModule, + TranslateModule, + SharedModule, + AccountsModule, + ], + exports: [ + ...COMPONENTS, + ], + declarations: [ + ...COMPONENTS, + ...DIALOGS, + ], + entryComponents: [ + ...DIALOGS, + ], +}) +export class LayoutModule { + public static forRoot(): ModuleWithProviders { + return { + ngModule: LayoutRootModule, + providers: [ + ], + }; + } +} + +@NgModule({ + imports: [ + ], + exports: [ + ] +}) +export class LayoutRootModule { +} diff --git a/src/app/pages/accounts/accounts-routing.page.module.ts b/src/app/pages/accounts/accounts-routing.page.module.ts new file mode 100755 index 0000000..294b968 --- /dev/null +++ b/src/app/pages/accounts/accounts-routing.page.module.ts @@ -0,0 +1,45 @@ +/** + * 파 일 명: accounts-routing.module.ts + * 작성일자: 2018-12-20 + * 작 성 자: 박병준 + * 설 명: account 영역의 routing을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +import { AccountsLayoutComponent } from '../../layout/component/accounts/accounts.layout.component'; + +import { AuthenticationPageComponent } from './component/authentication/authentication.page.component'; +import { WelcomePageComponent } from './component/welcome/welcome.page.component'; +import { AuthenticationCallbackPageComponent } from './component/authentication/authentication-callback.page.component'; + +const routes: Routes = [ + { + path: '', + component: AccountsLayoutComponent, + children: [ + { + path: 'authentication', + component: AuthenticationPageComponent + }, + { + path: 'authentication/callback', + component: AuthenticationCallbackPageComponent + }, + { + path: 'welcome', + component: WelcomePageComponent + } + ] + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class AccountsRoutingPageModule {} diff --git a/src/app/pages/accounts/accounts.page.module.ts b/src/app/pages/accounts/accounts.page.module.ts new file mode 100755 index 0000000..9a54ada --- /dev/null +++ b/src/app/pages/accounts/accounts.page.module.ts @@ -0,0 +1,34 @@ +/** + * 파 일 명: accounts.module.ts + * 작성일자: 2018-12-20 + * 작 성 자: 박병준 + * 설 명: accounts page 영역의 module을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { TranslateModule } from '@ngx-translate/core'; + +import { LayoutModule } from '../../layout/layout.module'; + +import { AccountsRoutingPageModule } from './accounts-routing.page.module'; +import { AccountsModule } from '../../../modules/accounts/accounts.module'; + +import { COMPONENTS } from './component'; + +@NgModule({ + imports: [ + CommonModule, + TranslateModule, + LayoutModule, + AccountsRoutingPageModule, + AccountsModule, + ], + declarations: [ + ...COMPONENTS, + ], +}) +export class AccountsPageModule { } diff --git a/src/app/pages/accounts/component/authentication/authentication-callback.page.component.html b/src/app/pages/accounts/component/authentication/authentication-callback.page.component.html new file mode 100755 index 0000000..e69de29 diff --git a/src/app/pages/accounts/component/authentication/authentication-callback.page.component.scss b/src/app/pages/accounts/component/authentication/authentication-callback.page.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/app/pages/accounts/component/authentication/authentication-callback.page.component.ts b/src/app/pages/accounts/component/authentication/authentication-callback.page.component.ts new file mode 100755 index 0000000..26d4b2e --- /dev/null +++ b/src/app/pages/accounts/component/authentication/authentication-callback.page.component.ts @@ -0,0 +1,42 @@ +/** + * 파 일 명: authentication-callback.page.component.ts + * 작성일자: 2018-12-20 + * 작 성 자: 박병준 + * 설 명: authentication callback page component를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Component, OnInit } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { ActivatedRoute } from '@angular/router'; + +import * as AppLoginStore from '../../../../store/login'; + +@Component({ + selector: 'app-page-accounts-authentication-callback', + templateUrl: './authentication-callback.page.component.html', + styleUrls: ['./authentication-callback.page.component.scss'], +}) +export class AuthenticationCallbackPageComponent implements OnInit { + constructor( + private store: Store, + private activatedRoute: ActivatedRoute, + ) { + + } + + ngOnInit() { + this.activatedRoute.queryParams.subscribe(queryParams => { + const token = queryParams['token'] || null; + const result = queryParams['result'] || null; + let returnURL = queryParams['returnURL']; + if (!returnURL || 'null' === returnURL || '' === returnURL) { + returnURL = '/'; + } + + this.store.dispatch(new AppLoginStore.LoginToken({ token, returnURL: returnURL })); + }); + } +} diff --git a/src/app/pages/accounts/component/authentication/authentication.page.component.html b/src/app/pages/accounts/component/authentication/authentication.page.component.html new file mode 100755 index 0000000..45df5d5 --- /dev/null +++ b/src/app/pages/accounts/component/authentication/authentication.page.component.html @@ -0,0 +1,3 @@ + diff --git a/src/app/pages/accounts/component/authentication/authentication.page.component.scss b/src/app/pages/accounts/component/authentication/authentication.page.component.scss new file mode 100755 index 0000000..b29f835 --- /dev/null +++ b/src/app/pages/accounts/component/authentication/authentication.page.component.scss @@ -0,0 +1,61 @@ +@mixin calc($property, $expression) { + #{$property}: -moz-calc(#{$expression}); + #{$property}: -webkit-calc(#{$expression}); + #{$property}: calc(#{$expression}); +} + +$marginHeight: 170px; + +.login-layout { + display: block; + width: 100%; + @include calc(height, '100vh - '$marginHeight); + margin: 0 auto; + margin-top: 55px; + padding: 0; + background: url(../../../../../assets/img/login/bg.png) no-repeat right 205px; + background-size: 100% auto; + background-attachment: fixed; + + @media (max-height: 640px) { + background: url(../../../../../assets/img/login/bg.png) no-repeat right 195px; + background-size: 100% auto; + background-attachment: fixed; + } + + @media (max-height: 640px) and (min-width: 760px) { + background: url(../../../../../assets/img/login/bg.png) no-repeat right -45px; + background-size: 100% auto; + background-attachment: fixed; + } + + @media (min-height: 641px) and (max-height: 1024px) { + background: url(../../../../../assets/img/login/bg.png) no-repeat right 65px; + background-size: 100% auto; + background-attachment: fixed; + } + + @media (min-width:480px) and (max-width: 760px) and (min-height: 641px) and (max-height: 1024px) { + background: url(../../../../../assets/img/login/bg.png) no-repeat right 40px; + background-size: 100% auto; + background-attachment: fixed; + } + + @media (min-width: 761px) and (max-width:1024px) and (min-height: 641px) and (max-height: 1024px) { + background: url(../../../../../assets/img/login/bg.png) no-repeat right 0px; + background-size: 500px auto; + background-attachment: fixed; + } + + @media (min-width: 1025px) and (min-height: 641px) and (max-height: 1024px) { + background: url(../../../../../assets/img/login/bg.png) no-repeat right 0px; + background-size: 500px auto; + background-attachment: fixed; + } + + @media (min-height: 1025px) { + background: url(../../../../../assets/img/login/bg.png) no-repeat right 30px; + background-size: 100% auto; + background-attachment: fixed; + } +} diff --git a/src/app/pages/accounts/component/authentication/authentication.page.component.ts b/src/app/pages/accounts/component/authentication/authentication.page.component.ts new file mode 100755 index 0000000..6c85954 --- /dev/null +++ b/src/app/pages/accounts/component/authentication/authentication.page.component.ts @@ -0,0 +1,43 @@ +/** + * 파 일 명: authentication.page.component.ts + * 작성일자: 2018-12-20 + * 작 성 자: 박병준 + * 설 명: authentication page component를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; + +import { of, Observable } from 'rxjs'; +import { take, map, catchError } from 'rxjs/operators'; + +@Component({ + selector: 'app-page-accounts-authentication', + templateUrl: './authentication.page.component.html', + styleUrls: ['./authentication.page.component.scss'], +}) +export class AuthenticationPageComponent implements OnInit { + params$: Observable>; + + constructor( + private activatedRoute: ActivatedRoute, + ) { + } + + ngOnInit(): void { + this.params$ = this.activatedRoute.queryParamMap.pipe( + take(1), + map(params => { + const _params = new Map(); + _params.set('returnURL', params.get('returnURL')); + return _params; + }), + catchError(error => { + return of(error); + }), + ); + } +} diff --git a/src/app/pages/accounts/component/index.ts b/src/app/pages/accounts/component/index.ts new file mode 100755 index 0000000..cfaf6bd --- /dev/null +++ b/src/app/pages/accounts/component/index.ts @@ -0,0 +1,9 @@ +import { AuthenticationPageComponent } from './authentication/authentication.page.component'; +import { AuthenticationCallbackPageComponent } from './authentication/authentication-callback.page.component'; +import { WelcomePageComponent } from './welcome/welcome.page.component'; + +export const COMPONENTS = [ + AuthenticationPageComponent, + AuthenticationCallbackPageComponent, + WelcomePageComponent, +]; diff --git a/src/app/pages/accounts/component/welcome/welcome.page.component.html b/src/app/pages/accounts/component/welcome/welcome.page.component.html new file mode 100755 index 0000000..4d4762b --- /dev/null +++ b/src/app/pages/accounts/component/welcome/welcome.page.component.html @@ -0,0 +1 @@ +welcome diff --git a/src/app/pages/accounts/component/welcome/welcome.page.component.scss b/src/app/pages/accounts/component/welcome/welcome.page.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/app/pages/accounts/component/welcome/welcome.page.component.ts b/src/app/pages/accounts/component/welcome/welcome.page.component.ts new file mode 100755 index 0000000..153d1fb --- /dev/null +++ b/src/app/pages/accounts/component/welcome/welcome.page.component.ts @@ -0,0 +1,23 @@ +/** + * 파 일 명: welcome.page.component.ts + * 작성일자: 2018-12-23 + * 작 성 자: 박병준 + * 설 명: welcome page component를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-page-accounts-welcome', + templateUrl: './welcome.page.component.html', + styleUrls: ['./welcome.page.component.scss'], +}) +export class WelcomePageComponent { + + constructor() { + } + +} diff --git a/src/app/pages/admin/admin-routing.page.module.ts b/src/app/pages/admin/admin-routing.page.module.ts new file mode 100755 index 0000000..66d2b5d --- /dev/null +++ b/src/app/pages/admin/admin-routing.page.module.ts @@ -0,0 +1,42 @@ +/** + * 파 일 명: admin-routing.page.module.ts + * 작성일자: 2019-01-08 + * 작 성 자: 최지련 + * 설 명: user 영역의 routing을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; +import { DefaultLayoutComponent } from '../../layout/component/default/default.layout.component'; +import { ReportPageComponent } from './component/report/report.page.component'; +import { ErrorPageComponent } from './component/error/error.page.component'; +import { DataPageComponent } from './component/data/data.page.component'; +const routes: Routes = [ + { + path: '', + component: DefaultLayoutComponent, + children: [ + { + path: 'report', + component: ReportPageComponent + }, + { + path: 'data', + component: DataPageComponent, + }, + { + path: '404', + component: ErrorPageComponent + } + ] + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class AdminRoutingPageModule { } diff --git a/src/app/pages/admin/admin.page.module.ts b/src/app/pages/admin/admin.page.module.ts new file mode 100755 index 0000000..78dc34c --- /dev/null +++ b/src/app/pages/admin/admin.page.module.ts @@ -0,0 +1,30 @@ +/** + * 파 일 명: admin.page.module.ts + * 작성일자: 2019-01-08 + * 작 성 자: 최지련 + * 설 명: admin page 영역의 module을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { TranslateModule } from '@ngx-translate/core'; + +import { COMPONENTS } from './component'; +import { AdminRoutingPageModule } from './admin-routing.page.module'; +import { LayoutModule } from '../../layout/layout.module'; +import { SharedModule } from '../../../modules/common/shared/shared.module'; + +@NgModule({ + imports: [ + CommonModule, + TranslateModule, + AdminRoutingPageModule, + SharedModule, + LayoutModule, + ], + declarations: [...COMPONENTS] +}) +export class AdminPageModule { } diff --git a/src/app/pages/admin/component/data/data.page.component.html b/src/app/pages/admin/component/data/data.page.component.html new file mode 100755 index 0000000..21a850c --- /dev/null +++ b/src/app/pages/admin/component/data/data.page.component.html @@ -0,0 +1,50 @@ +
+
+

관리자 화면 – 기준 데이터 생성

+
+ + + + + +

Thumbnails 생성

+

Thumbnails

+ +
+ + +

Mongodb Article 생성

+

Mongodb Article

+ +
+ +

Random Article 생성

+

Random Article

+ +
+ +

Random Author 생성

+

Random Author

+ +
+
+
+
+
diff --git a/src/app/pages/admin/component/data/data.page.component.scss b/src/app/pages/admin/component/data/data.page.component.scss new file mode 100755 index 0000000..bc7066e --- /dev/null +++ b/src/app/pages/admin/component/data/data.page.component.scss @@ -0,0 +1,4 @@ +.example-button-row button, +.example-button-row a { + margin-right: 8px; +} diff --git a/src/app/pages/admin/component/data/data.page.component.ts b/src/app/pages/admin/component/data/data.page.component.ts new file mode 100755 index 0000000..bf99c01 --- /dev/null +++ b/src/app/pages/admin/component/data/data.page.component.ts @@ -0,0 +1,197 @@ +/** + * 파 일 명: data.page.component.ts + * 작성일자: 2019-01-08 + * 작 성 자: 최지련 + * 설 명: admin data page component를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Component } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; + +import { Store } from '@ngrx/store'; + +import { of } from 'rxjs'; +import { take, map, catchError, tap, finalize } from 'rxjs/operators'; + +import { ArticleTag } from '../../../../../shared/article/model/article-tag.model'; +import { UserAnalysisFavorTag } from '../../../../../shared/user-analysis/model/user-analysis-favor-tag.model'; +import { ArticleTagService } from '../../../../../modules/article/service/article-tag.service'; +import { UserAnalysisFavorTagService } from '../../../../../modules/user-analysis/service/user-analysis-favor-tag.service'; +import { ContentsService } from '../../../../../modules/contents/service/contents.service'; +import * as AppEventsStore from '../../../../store/events'; +import { MetaCommentsTag } from '../../../../../shared/meta/model/meta-comments-tag.model'; +import { MetaCommentsTagService } from '../../../../../modules/meta/service/meta-comments-tag.service'; + +@Component({ + selector: 'app-page-admin-data', + templateUrl: './data.page.component.html', + styleUrls: ['./data.page.component.scss'], +}) +export class DataPageComponent { + + constructor( + private httpClient: HttpClient, + private store: Store, + private articleTagService: ArticleTagService, + private userAnalysisFavorTagService: UserAnalysisFavorTagService, + private contentsService: ContentsService, + private metaCommentsTagService: MetaCommentsTagService, + ) { + } + + onClickArticleTagData() { + this.httpClient.get('./assets/json/article-tag.json').pipe( + take(1), + map((articleTagList: ArticleTag[]) => { + this.addAllArticleTagList(articleTagList); + }), + catchError((err) => { + console.log(err); + return of(err); + }) + ).subscribe(); + } + + private addAllArticleTagList(articleTagList: ArticleTag[]) { + if (!articleTagList || 0 === articleTagList.length) { + console.log('articleTagList is not valid'); + return; + } + + for (const articleTag of articleTagList) { + this.articleTagService.add(articleTag).pipe( + take(1), + map((_articleTag) => { + console.log('articleTag added', _articleTag); + }), + catchError((err) => { + console.log(err); + return of(err); + }) + ).subscribe(); + } + } + + onClickUserAnalysisFavorTagData() { + this.httpClient.get('./assets/json/user-analysis-favor-tag.json').pipe( + take(1), + map((userAnalysisFavorTagList: UserAnalysisFavorTag[]) => { + this.addAllUserAnalysisFavorTagList(userAnalysisFavorTagList); + }), + catchError((err) => { + console.log(err); + return of(err); + }) + ).subscribe(); + + } + + private addAllUserAnalysisFavorTagList(userAnalysisFavorTagList: UserAnalysisFavorTag[]) { + if (!userAnalysisFavorTagList || 0 === userAnalysisFavorTagList.length) { + console.log('userAnalysisFavorTagList is not valid'); + return; + } + + for (const userAnalysisFavorTag of userAnalysisFavorTagList) { + this.userAnalysisFavorTagService.add(userAnalysisFavorTag).pipe( + take(1), + map((_userAnalysisFavorTag) => { + console.log('userAnalysisFavorTag added', _userAnalysisFavorTag); + }), + catchError((err) => { + console.log(err); + return of(err); + }) + ).subscribe(); + } + } + + onClickUserSupportEventData() { + + } + + onClickMetaCommentsTagData() { + this.httpClient.get('./assets/json/meta-comments-tag.json').pipe( + take(1), + map((metaCommentsTagList: MetaCommentsTag[]) => { + this.addAllMetaCommentsTagList(metaCommentsTagList); + }), + catchError((err) => { + console.log(err); + return of(err); + }) + ).subscribe(); + } + + private addAllMetaCommentsTagList(metaCommentsTagList: MetaCommentsTag[]) { + if (!metaCommentsTagList || 0 === metaCommentsTagList.length) { + console.log('metaCommentsTagList is not valid'); + return; + } + + for (const metaCommentsTag of metaCommentsTagList) { + this.metaCommentsTagService.add(metaCommentsTag).pipe( + take(1), + map((_metaCommentsTag) => { + console.log('metaCommentsTag added', _metaCommentsTag); + }), + catchError((err) => { + console.log(err); + return of(err); + }) + ).subscribe(); + } + } + + onClickMongodbArticleData() { + this.contentsService.updateMongodbArticle().pipe( + take(1), + tap(() => { + this.store.dispatch(new AppEventsStore.ShowProgressBar()); + }), + map((_count) => { + console.log('Mongodb Article added', _count); + }), + catchError((err) => { + console.log(err); + return of(err); + }), + finalize(() => { + this.store.dispatch(new AppEventsStore.HideProgressBar()); + }), + ).subscribe(); + } + + onClickRandomArticleData(count: number) { + this.contentsService.updateRandomArticle(count).pipe( + take(1), + map((_count) => { + console.log('Random Article added', _count); + }), + catchError((err) => { + console.log(err); + return of(err); + }) + ).subscribe(); + } + + onClickRandomAuthorData(count: number) { + this.contentsService.updateRandomAuthor(count).pipe( + take(1), + map((_count) => { + console.log('Random Author added', _count); + }), + catchError((err) => { + console.log(err); + return of(err); + }) + ).subscribe(); + } + + onClickThumbnails() { + + } +} diff --git a/src/app/pages/admin/component/error/error.page.component.html b/src/app/pages/admin/component/error/error.page.component.html new file mode 100755 index 0000000..24983ef --- /dev/null +++ b/src/app/pages/admin/component/error/error.page.component.html @@ -0,0 +1,7 @@ +
+ Error +

+ 죄송합니다. +

+

페이지를 표시할 수 없습니다.

+
diff --git a/src/app/pages/admin/component/error/error.page.component.scss b/src/app/pages/admin/component/error/error.page.component.scss new file mode 100755 index 0000000..1053119 --- /dev/null +++ b/src/app/pages/admin/component/error/error.page.component.scss @@ -0,0 +1,18 @@ +.error-page { + margin: 50px auto; + text-align: center; + + .ico-error { + width: 100px; + height: 100px; + } + + .error-title { + text-align: center; + margin: 15px 0 10px 0; + } + + .error-description { + color: #a5a5a5; + } +} diff --git a/src/app/pages/admin/component/error/error.page.component.ts b/src/app/pages/admin/component/error/error.page.component.ts new file mode 100755 index 0000000..68d6455 --- /dev/null +++ b/src/app/pages/admin/component/error/error.page.component.ts @@ -0,0 +1,20 @@ +/** + * 파 일 명: error.page.component.ts + * 작성일자: 2019-01-09 + * 작 성 자: 조현정 + * 설 명: error page component를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-page-error', + templateUrl: './error.page.component.html', + styleUrls: ['./error.page.component.scss'] +}) +export class ErrorPageComponent { + constructor() {} +} diff --git a/src/app/pages/admin/component/index.ts b/src/app/pages/admin/component/index.ts new file mode 100755 index 0000000..177b23d --- /dev/null +++ b/src/app/pages/admin/component/index.ts @@ -0,0 +1,9 @@ +import { DataPageComponent } from './data/data.page.component'; +import { ReportPageComponent } from './report/report.page.component'; +import { ErrorPageComponent } from './error/error.page.component'; + +export const COMPONENTS = [ + DataPageComponent, + ReportPageComponent, + ErrorPageComponent, +]; diff --git a/src/app/pages/admin/component/report/report.page.component.html b/src/app/pages/admin/component/report/report.page.component.html new file mode 100755 index 0000000..a4fc8d2 --- /dev/null +++ b/src/app/pages/admin/component/report/report.page.component.html @@ -0,0 +1,53 @@ +
+
+

관리자 화면 – 신고 접수 및 처리

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
No. 신고 콘텐트 신고내용 신고일시 상태
1 xanNskdakNsjd98 저작권 위반 2019-01-20 신고접수 + + + + +
2 sdflksldkfdslksf 부적절 2019-01-20 블라인드 완료 + + +
+
+
+
diff --git a/src/app/pages/admin/component/report/report.page.component.scss b/src/app/pages/admin/component/report/report.page.component.scss new file mode 100755 index 0000000..4a060e0 --- /dev/null +++ b/src/app/pages/admin/component/report/report.page.component.scss @@ -0,0 +1,56 @@ +.table-admin { + width: 100%; + border-spacing: 0; + $border: #666; + font-size: 12px; + + box-shadow: 0px 5px 5px -3px rgba(0, 0, 0, 0.2), + 0px 8px 10px 1px rgba(0, 0, 0, 0.14), + 0px 3px 14px 2px rgba(0, 0, 0, 0.12); + + tr { + height: 48px; + + th { + padding: 0 6px; + border-bottom: 1px solid $border; + text-align: center; + } + + td { + padding: 3px 6px; + border-bottom: 1px solid $border; + text-align: center; + } + } + + .col-width { + .t1 { + width: 40px; + } + + .t2 { + width: 110px; + } + + .t3 { + width: 100px; + } + + .t4 { + width: 100px; + } + + .t5 { + width: 90px; + } + } +} + +button { + margin: 2px; + min-width: 24px !important; + padding: 0 5px !important; + line-height: 24px !important; + border-radius: 2px !important; +} diff --git a/src/app/pages/admin/component/report/report.page.component.ts b/src/app/pages/admin/component/report/report.page.component.ts new file mode 100755 index 0000000..41c7698 --- /dev/null +++ b/src/app/pages/admin/component/report/report.page.component.ts @@ -0,0 +1,23 @@ +/** + * 파 일 명: report.page.component.ts + * 작성일자: 2019-01-08 + * 작 성 자: 최지련 + * 설 명: admin report page component를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-page-admin-report', + templateUrl: './report.page.component.html', + styleUrls: ['./report.page.component.scss'], +}) +export class ReportPageComponent { + + constructor() { + } + +} diff --git a/src/app/pages/article/article-routing.page.module.ts b/src/app/pages/article/article-routing.page.module.ts new file mode 100755 index 0000000..644b589 --- /dev/null +++ b/src/app/pages/article/article-routing.page.module.ts @@ -0,0 +1,81 @@ +/** + * 파 일 명: accounts-routing.module.ts + * 작성일자: 2018-12-20 + * 작 성 자: 박병준 + * 설 명: account 영역의 routing을 정의한다. + * 수정일시: 2018-12-27 + * 수 정 자: 조현정 + * 수정내용: Layout change + * 참고사항: + */ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +import { DefaultLayoutComponent } from '../../layout/component/default/default.layout.component'; +import { ProfilePageComponent } from './component/profile/profile.page.component'; +import { FormPageComponent as SearchFormPageComponent } from './component/search/form/form.page.component'; +import { ResultPageComponent as SearchResultPageComponent } from './component/search/result/result.page.component'; + +import { DetailsPageComponent } from './component/details/details.page.component'; +import { WritePageComponent } from './component/write/write.page.component'; +import { EmotionsPageComponent } from './component/emotions/emotions.page.component'; +import { AuthGuard } from '../../guard/auth.guard'; +import { AuthNicknameGuard } from '../../guard/auth-nickname.guard'; +import { CanDeactivateGuard } from '../../guard/can-deactivate.guard'; +import { ArticleOwnerGuard } from '../../guard/article-owner.guard'; + +const routes: Routes = [ + { + path: '', + component: DefaultLayoutComponent, + children: [ + { + path: 'write', + component: WritePageComponent, + canActivate: [ + AuthGuard, + AuthNicknameGuard, + ], + canDeactivate: [CanDeactivateGuard], + runGuardsAndResolvers: 'always', + }, + { + path: 'write/:aid', + component: WritePageComponent, + canActivate: [ + AuthGuard, + AuthNicknameGuard, + ArticleOwnerGuard, + ], + canDeactivate: [CanDeactivateGuard], + runGuardsAndResolvers: 'always', + }, + { + path: 'search', + component: SearchFormPageComponent + }, + { + path: 'emotions', + component: EmotionsPageComponent + }, + { + path: 'p/:aid', + component: DetailsPageComponent, + }, + { + path: ':uid', + component: ProfilePageComponent, + }, + { + path: 'search/p/:tagName', + component: SearchResultPageComponent + } + ] + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class ArticleRoutingPageModule { } diff --git a/src/app/pages/article/article.page.module.ts b/src/app/pages/article/article.page.module.ts new file mode 100755 index 0000000..8c03c99 --- /dev/null +++ b/src/app/pages/article/article.page.module.ts @@ -0,0 +1,50 @@ +/** + * 파 일 명: article.page.module.ts + * 작성일자: 2018-12-20 + * 작 성 자: 박병준 + * 설 명: article page 영역의 module을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { TranslateModule } from '@ngx-translate/core'; + +import { LayoutModule } from '../../layout/layout.module'; +import { SharedModule } from '../../../modules/common/shared/shared.module'; +import { UserModule } from '../../../modules/user/user.module'; +import { UserSupportModule } from '../../../modules/user-support/user-support.module'; + +import { ArticleRoutingPageModule } from './article-routing.page.module'; +import { CartoonsModule } from '../../../modules/cartoons/cartoons.module'; +import { IllustrationsModule } from '../../../modules/illustrations/illustrations.module'; +import { NovelModule } from '../../../modules/novel/novel.module'; +import { AttachmentsModule } from '../../../modules/attachments/attachments.module'; + +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; + +import { COMPONENTS } from './component'; +import { ArticleModule } from 'src/modules/article/article.module'; + +@NgModule({ + imports: [ + CommonModule, + TranslateModule, + SharedModule, + LayoutModule, + CartoonsModule, + IllustrationsModule, + NovelModule, + ArticleRoutingPageModule, + FormsModule, + ReactiveFormsModule, + AttachmentsModule, + UserModule, + UserSupportModule, + ArticleModule + ], + declarations: [...COMPONENTS] +}) +export class ArticlePageModule { } diff --git a/src/app/pages/article/component/details/details.page.component.html b/src/app/pages/article/component/details/details.page.component.html new file mode 100755 index 0000000..069c9af --- /dev/null +++ b/src/app/pages/article/component/details/details.page.component.html @@ -0,0 +1,17 @@ +
+ + + +
diff --git a/src/app/pages/article/component/details/details.page.component.scss b/src/app/pages/article/component/details/details.page.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/app/pages/article/component/details/details.page.component.ts b/src/app/pages/article/component/details/details.page.component.ts new file mode 100755 index 0000000..dda0b93 --- /dev/null +++ b/src/app/pages/article/component/details/details.page.component.ts @@ -0,0 +1,76 @@ +/** + * 파 일 명: details.page.component.ts + * 작성일자: 2018-12-20 + * 작 성 자: 박병준 + * 설 명: details page component를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { Observable, of } from 'rxjs'; +import { + switchMap, + tap, + map, + catchError, + take, + finalize +} from 'rxjs/operators'; + +import { ArticleGetService } from '../../../../../modules/article/service/article.get.service'; +import { ArticleType } from '../../../../../shared/article/type/article-type.type'; +import { ArticleMongodb } from '../../../../../shared/article/model/article.mongodb.model'; + +@Component({ + selector: 'app-page-cartoons-details', + templateUrl: './details.page.component.html', + styleUrls: ['./details.page.component.scss'] +}) +export class DetailsPageComponent implements OnInit { + aid: string; + articleMongodb$: Observable; + + constructor( + private activatedRoute: ActivatedRoute, + private articleGetService: ArticleGetService + ) {} + + ngOnInit(): void { + this.articleMongodb$ = this.activatedRoute.paramMap.pipe( + take(1), + tap(() => { + // display progressbar + }), + switchMap(params => { + // (+) before `params.get()` turns the string into a number + // (+) before `params.get()` turns the string into a number + this.aid = params.get('aid'); + + console.log('aid >> ' + this.aid); + + if (this.aid) { + return this.articleGetService.get(this.aid).pipe( + map((articleMongodb: ArticleMongodb) => { + console.log(articleMongodb); + return articleMongodb; + }), + catchError(error => { + console.log(error); + return of(error); + }) + ); + } + }), + catchError(error => { + console.log(error); + return of(); + }), + finalize(() => { + // remove progressbar + }) + ); + } +} diff --git a/src/app/pages/article/component/emotions/emotions.page.component.html b/src/app/pages/article/component/emotions/emotions.page.component.html new file mode 100755 index 0000000..ed63485 --- /dev/null +++ b/src/app/pages/article/component/emotions/emotions.page.component.html @@ -0,0 +1 @@ +Emotions diff --git a/src/app/pages/article/component/emotions/emotions.page.component.scss b/src/app/pages/article/component/emotions/emotions.page.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/app/pages/article/component/emotions/emotions.page.component.ts b/src/app/pages/article/component/emotions/emotions.page.component.ts new file mode 100755 index 0000000..2f3b5c3 --- /dev/null +++ b/src/app/pages/article/component/emotions/emotions.page.component.ts @@ -0,0 +1,23 @@ +/** + * 파 일 명: emotions.page.component.ts + * 작성일자: 2018-12-20 + * 작 성 자: 박병준 + * 설 명: emotions page component를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-page-cartoons-emotions', + templateUrl: './emotions.page.component.html', + styleUrls: ['./emotions.page.component.scss'], +}) +export class EmotionsPageComponent { + + constructor() { + } + +} diff --git a/src/app/pages/article/component/index.ts b/src/app/pages/article/component/index.ts new file mode 100755 index 0000000..0d18009 --- /dev/null +++ b/src/app/pages/article/component/index.ts @@ -0,0 +1,13 @@ +import { DetailsPageComponent } from './details/details.page.component'; +import { EmotionsPageComponent } from './emotions/emotions.page.component'; +import { ProfilePageComponent } from './profile/profile.page.component'; +import { WritePageComponent } from './write/write.page.component'; +import { SEARCH_COMPONENTS } from './search'; + +export const COMPONENTS = [ + DetailsPageComponent, + EmotionsPageComponent, + ProfilePageComponent, + WritePageComponent, + ...SEARCH_COMPONENTS, +]; diff --git a/src/app/pages/article/component/profile/profile.page.component.html b/src/app/pages/article/component/profile/profile.page.component.html new file mode 100755 index 0000000..b158015 --- /dev/null +++ b/src/app/pages/article/component/profile/profile.page.component.html @@ -0,0 +1,82 @@ + +
+ + +
+ + + + + + + + article thumbnails + + + +
+
+ + + + + + +
+
+
+ + + article cards + + +
+
+ + + + + + + + +
+
+
+ + + + followerList + +
+ + + + +
+
+ + + + followingList + +
+ + + + +
+
+
+
+
+
diff --git a/src/app/pages/article/component/profile/profile.page.component.scss b/src/app/pages/article/component/profile/profile.page.component.scss new file mode 100755 index 0000000..e590323 --- /dev/null +++ b/src/app/pages/article/component/profile/profile.page.component.scss @@ -0,0 +1,5 @@ +:host { + .article-display-card { + height: auto; + } +} diff --git a/src/app/pages/article/component/profile/profile.page.component.ts b/src/app/pages/article/component/profile/profile.page.component.ts new file mode 100755 index 0000000..7f2d13f --- /dev/null +++ b/src/app/pages/article/component/profile/profile.page.component.ts @@ -0,0 +1,212 @@ +/** + * 파 일 명: profile.page.component.ts + * 작성일자: 2018-12-20 + * 작 성 자: 박병준 + * 설 명: profile page component를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Component, OnInit, ViewChild, HostListener } from '@angular/core'; +import { ActivatedRoute, Router, ActivatedRouteSnapshot } from '@angular/router'; + +import { VirtualScrollerComponent, ChangeEvent } from 'ngx-virtual-scroller'; + +import { Store } from '@ngrx/store'; + +import { Observable, of } from 'rxjs'; +import { switchMap, tap, map, catchError, take, finalize } from 'rxjs/operators'; + +import { ArticleGetService } from '../../../../../modules/article/service/article.get.service'; +import { ArticleMongodb } from '../../../../../shared/article/model/article.mongodb.model'; +import { MatTabChangeEvent } from '@angular/material'; + +import * as AppEventsStore from '../../../../store/events'; +import { User } from '../../../../../shared/user/model/user.model'; +import { UserService } from '../../../../../modules/user/service/user.service'; +import { UserFollowersService } from '../../../../../modules/user/service/user-followers.service'; +import { UserFollowers } from '../../../../../shared/user/model/user-followers.model'; +import { DateUtil } from 'src/modules/common/util/data/date.util'; + +import { UserFavorTag } from '../../../../../shared/user/model/user-favor-tag.model'; +import { UserFavorTagService } from '../../../../../modules/user/service/user-favor-tag.service'; +import { RouteUtil } from 'src/modules/common/util/ui/route.util'; + +@Component({ + selector: 'app-page-article-profile', + templateUrl: './profile.page.component.html', + styleUrls: ['./profile.page.component.scss'], +}) +export class ProfilePageComponent implements OnInit { + uid: string; + user: User; + timestamp = 0; + size = 20; + + followersCount: number; + followingCount: number; + followersList: UserFollowers[]; + followingList: UserFollowers[]; + + articleCount: number; + articleMongodbList: ArticleMongodb[] = []; + + favorTagList: UserFavorTag[]; + + authorFollowYn = false; + + @ViewChild(VirtualScrollerComponent) + private virtualScroller: VirtualScrollerComponent; + + @HostListener('window:resize') + onResize() { + console.log('resize'); + } + + constructor( + private activatedRoute: ActivatedRoute, + private articleGetService: ArticleGetService, + private userService: UserService, + private userFollowersService: UserFollowersService, + private store: Store, + private userFavorService: UserFavorTagService, + private router: Router + ) { + } + + ngOnInit(): void { + let uid: string; + this.activatedRoute.paramMap.pipe( + take(1), + map(params => { + // (+) before `params.get()` turns the string into a number + uid = params.get('uid'); + if (uid) { + this.uid = uid; + this.userService.get(this.uid) + .pipe( + take(1), + map((user: User) => { + this.user = user; + }), + catchError(err => { + return of(err); + }), + ).subscribe(); + this.fetchArticleList(); + this.fetchFollowersList(); + this.fetchFollowingList(); + this.getFavorTagList(); + } else { + return of({}); + } + }), + catchError(error => { + return of(error); + }), + ).subscribe(); + + RouteUtil.setShouldReuseRoute(this.router); + } + + + + fetchArticleList() { + this.articleGetService.getAllByUid(this.uid, this.timestamp, this.size).pipe( + take(1), + tap(() => { + this.store.dispatch(new AppEventsStore.ShowProgressBar()); + }), + map((articleMongodbList: ArticleMongodb[]) => { + if (articleMongodbList && 0 < articleMongodbList.length) { + this.timestamp = DateUtil.toDate(articleMongodbList[articleMongodbList.length - 1].createDate).getTime(); + this.articleMongodbList = this.articleMongodbList.concat(articleMongodbList); + this.articleCount = articleMongodbList.length; + } + }), + catchError((error) => { + return of(error); + }), + finalize(() => { + this.store.dispatch(new AppEventsStore.HideProgressBar()); + }) + ).subscribe(); + } + + fetchFollowersList() { + this.userFollowersService.getAllFollowers(this.uid).pipe( + take(1), + tap(), + map((followersList) => { + if (followersList) { + this.followersCount = Object.keys(followersList).length; + this.followersList = Object.values(followersList); + } + }), + catchError((error) => { + return of(error); + }), + finalize(() => { }) + ).subscribe(); + } + + fetchFollowingList() { + this.userFollowersService.getAllFollowing(this.uid).pipe( + take(1), + tap(), + map((followingList) => { + if (followingList) { + this.followingCount = Object.keys(followingList).length; + this.followingList = Object.values(followingList); + } + }), + catchError((error) => { + return of(error); + }), + finalize(() => { }) + ).subscribe(); + } + + getFavorTagList() { + this.userFavorService.getAllByUid(this.uid).pipe( + take(1), + tap(), + map((favors) => { + if (favors) { + this.favorTagList = Object.values(favors).map((favor) => { + if (favor) { + favor.userAnalysisFavorTag.imageName = favor.userAnalysisFavorTag.imageName.split('.').slice(0, -1).join(); + return favor; + } + }); + } + }), + catchError((error) => { + return of(error); + }), + finalize(() => { }) + ).subscribe(); + } + + protected onVSEnd(event: ChangeEvent) { + console.log('onVSEnd', event); + if (event.start === -1 || event.start === 0 || event.end !== this.articleMongodbList.length - 1) { + return; + } + this.fetchArticleList(); + } + + onSelectedTabChange(event: MatTabChangeEvent) { + this.virtualScroller.refresh(); + } + + setIsFollowing(followingYn) { + if (followingYn) { + this.authorFollowYn = true; + ++this.followersCount; + } else { + --this.followersCount; + } + } +} diff --git a/src/app/pages/article/component/search/form/form.page.component.html b/src/app/pages/article/component/search/form/form.page.component.html new file mode 100755 index 0000000..3072c71 --- /dev/null +++ b/src/app/pages/article/component/search/form/form.page.component.html @@ -0,0 +1,11 @@ +
+ + + + + + + + + +
diff --git a/src/app/pages/article/component/search/form/form.page.component.scss b/src/app/pages/article/component/search/form/form.page.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/app/pages/article/component/search/form/form.page.component.ts b/src/app/pages/article/component/search/form/form.page.component.ts new file mode 100755 index 0000000..bf277e1 --- /dev/null +++ b/src/app/pages/article/component/search/form/form.page.component.ts @@ -0,0 +1,55 @@ +/** + * 파 일 명: search.page.component.ts + * 작성일자: 2018-12-20 + * 작 성 자: 박병준 + * 설 명: search page component를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { + Component, + HostListener, + OnInit, +} from '@angular/core'; +import { FormControl } from '@angular/forms'; +import { Router } from '@angular/router'; +import { Store } from '@ngrx/store'; +import * as UserProfileStore from '../../../../../../modules/user/store/profile'; +import { ArticleTag } from '../../../../../../shared/article/model/article-tag.model'; + + +@Component({ + selector: 'app-page-article-search-form', + templateUrl: './form.page.component.html', + styleUrls: ['./form.page.component.scss'] +}) +export class FormPageComponent implements OnInit { + searchCtrl = new FormControl(); + // max_user_nickname_leng = 32; + + @HostListener('window:resize') + onResize() { + console.log('resize'); + } + + constructor( + private router: Router, + private store: Store, + ) { + } + + ngOnInit() { + } + + + onSelectedTag(tag: ArticleTag) { + // tag 검색 상세 페이지 이동 + this.router.navigate(['/article/search/p', tag.tagName]); + } + + goUserProfile(uid: string) { + this.store.dispatch(new UserProfileStore.GotoProfile({ uid: uid })); + } +} diff --git a/src/app/pages/article/component/search/index.ts b/src/app/pages/article/component/search/index.ts new file mode 100755 index 0000000..105139e --- /dev/null +++ b/src/app/pages/article/component/search/index.ts @@ -0,0 +1,7 @@ +import { FormPageComponent } from './form/form.page.component'; +import { ResultPageComponent } from './result/result.page.component'; + +export const SEARCH_COMPONENTS = [ + FormPageComponent, + ResultPageComponent, +]; diff --git a/src/app/pages/article/component/search/result/result.page.component.html b/src/app/pages/article/component/search/result/result.page.component.html new file mode 100755 index 0000000..aba283a --- /dev/null +++ b/src/app/pages/article/component/search/result/result.page.component.html @@ -0,0 +1,37 @@ +
+ +
+
+ + + + + + + + +

{{ 'main.msgWorksRecommend' | translate }}

+
+ +

{{ 'main.msgCoCoRecommend' | translate }}

+
+
+ + +
+
+
+ + +
+ Advertisements +
+ + +
+
+
+
+
diff --git a/src/app/pages/article/component/search/result/result.page.component.scss b/src/app/pages/article/component/search/result/result.page.component.scss new file mode 100755 index 0000000..d6368a5 --- /dev/null +++ b/src/app/pages/article/component/search/result/result.page.component.scss @@ -0,0 +1,3 @@ +.example-item { + height: 700px; +} diff --git a/src/app/pages/article/component/search/result/result.page.component.ts b/src/app/pages/article/component/search/result/result.page.component.ts new file mode 100755 index 0000000..3116069 --- /dev/null +++ b/src/app/pages/article/component/search/result/result.page.component.ts @@ -0,0 +1,123 @@ +import { Component, OnInit, ViewChild, HostListener } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { Store } from '@ngrx/store'; +import { VirtualScrollerComponent, ChangeEvent } from 'ngx-virtual-scroller'; +import { of } from 'rxjs'; +import { tap, map, catchError, take, finalize } from 'rxjs/operators'; +import { ArticleMongodb } from '../../../../../../shared/article/model/article.mongodb.model'; +import * as AppEventsStore from '../../../../../store/events'; +import { ContentsUtil } from '../../../../../../shared/contents/util/contents.util'; +import { ContentsSize } from '../../../../../../shared/contents/model/contents-size.model'; +import * as ContentsConfig from '../../../../../../config/contents'; +import { ContentsSearch } from '../../../../../../shared/contents/type/contents-search.type'; +import { ContentsService } from '../../../../../../modules/contents/service/contents.service'; +import { Contents } from '../../../../../../shared/contents/model/contents.model'; +import { ContentsType } from '../../../../../../shared/contents/type/contents-type.type'; +import { Article } from '../../../../../../shared/article/model/article.model'; + + +@Component({ + selector: 'app-page-article-search-result', + templateUrl: './result.page.component.html', + styleUrls: ['./result.page.component.scss'] +}) +export class ResultPageComponent implements OnInit { + tagName: string; + + contentsSearch: ContentsSearch = ContentsSearch.Tag; + contentsSizeList: ContentsSize[]; + page: number; + pageEnded: boolean; + contentsList: Contents[] = []; + + @ViewChild(VirtualScrollerComponent) + private virtualScroller: VirtualScrollerComponent; + + @HostListener('window:resize') + onResize() { + console.log('resize'); + } + + constructor( + private activatedRoute: ActivatedRoute, + private contentsService: ContentsService, + private store: Store, + ) { + } + + ngOnInit() { + this.setContentsSizeList(); + this.page = 1; + this.pageEnded = false; + + this.activatedRoute.paramMap + .pipe( + take(1), + map(params => { + // tag id parameter + const tagName = params.get('tagName'); + if (tagName) { + this.tagName = tagName; + this.fetchContentsList(); + } else { + return of({}); + } + }), + catchError(error => { + return of(error); + }) + ) + .subscribe(); + } + + setContentsSizeList() { + this.contentsSizeList = ContentsConfig.getSearchContentsSize(this.contentsSearch); + } + + fetchContentsList() { + this.contentsService.getAllBySearchArticleTag(this.tagName, this.page, this.contentsSizeList).pipe( + take(1), + tap(() => { + this.store.dispatch(new AppEventsStore.ShowProgressBar()); + }), + map((contentsList: Contents[]) => { + // console.log(contentsList); + this.contentsList = this.contentsList.concat(contentsList); + this.pageEnded = ContentsUtil.isEnded(ContentsType.Article, this.contentsSizeList, contentsList); + if (!this.pageEnded) { + this.page++; + } + }), + catchError((error) => { + return of(error); + }), + finalize(() => { + this.store.dispatch(new AppEventsStore.HideProgressBar()); + }), + ).subscribe(); + } + + protected onVSEnd(event: ChangeEvent) { + console.log('onVSEnd', event); + if (event.start === -1 || event.start === 0 || event.end !== this.contentsList.length - 1) { + return; + } + if (this.pageEnded) { + console.log('Page ended'); + return; + } + this.fetchContentsList(); + } + + converToArticleList(mongodbArticleList: ArticleMongodb[]): Article[] { + if (!mongodbArticleList || 0 === mongodbArticleList.length) { + return []; + } + const articleList: Article[] = []; + for (const mongodbArticle of mongodbArticleList) { + articleList.push(mongodbArticle.article); + } + return articleList; + } + +} diff --git a/src/app/pages/article/component/write/write.page.component.html b/src/app/pages/article/component/write/write.page.component.html new file mode 100755 index 0000000..4ca2efb --- /dev/null +++ b/src/app/pages/article/component/write/write.page.component.html @@ -0,0 +1,48 @@ +
+
+ + + + Illustrations + {{ 'myPage.illust' | translate}} + + + + + + + + Cartoons + {{ 'myPage.comic' | translate}} + + + + + + + + Novel + {{ 'myPage.novel' | translate}} + + + + + + +
+
+
    +
  • + +
  • +
  • + +
  • +
+
+
diff --git a/src/app/pages/article/component/write/write.page.component.scss b/src/app/pages/article/component/write/write.page.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/app/pages/article/component/write/write.page.component.ts b/src/app/pages/article/component/write/write.page.component.ts new file mode 100755 index 0000000..22c8230 --- /dev/null +++ b/src/app/pages/article/component/write/write.page.component.ts @@ -0,0 +1,238 @@ +/** + * 파 일 명: write.page.component.ts + * 작성일자: 2018-12-20 + * 작 성 자: 박병준 + * 설 명: write page component를 정의한다. + * 수정일시: 2019-01-24 + * 수 정 자: 윤대훈 + * 수정내용: 등록 / 수정 과정에서 이탈시 경고 문구 표시 + * 참고사항: 저장/수정 완료 후 상태값 변경 + */ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; + +import { MatDialog, MatSnackBar } from '@angular/material'; + +import { Store } from '@ngrx/store'; +import { TranslateService } from '@ngx-translate/core'; + +import { Observable, of } from 'rxjs'; +import { + switchMap, + tap, + map, + catchError, + take, + finalize +} from 'rxjs/operators'; + +import { ArticleGetService } from '../../../../../modules/article/service/article.get.service'; +import { ArticleType } from '../../../../../shared/article/type/article-type.type'; +import { environment } from '../../../../../environments/environment'; +import { ArticleMongodb } from '../../../../../shared/article/model/article.mongodb.model'; +import { CanDeactivate } from '../../../../guard/can-deactivate.guard'; +import { AppService } from '../../../../service/app.service'; +import { Article } from '../../../../../shared/article/model/article.model'; +import { UIUtil } from '../../../../../modules/common/util/ui/dialog.util'; +import { + ConfirmDialogComponent, + ConfirmDialogData, + ConfirmDialogResult +} from '../../../../../app/layout/dialog/confirm/confirm.dialog.component'; + +import * as AppEventsStore from '../../../../store/events'; +import { + SuccessSnackBarComponent, + SuccessSnackBarData +} from '../../../../layout/dialog/events/success/success.snackbar.component'; +import { + FailureSnackBarComponent, + FailureSnackBarData +} from '../../../../layout/dialog/events/failure/failure.snackbar.component'; +import { User } from 'src/shared/user/model/user.model'; +import { AccountsUtil } from 'src/modules/accounts/util/accounts.util'; + +@Component({ + selector: 'app-page-cartoons-write', + templateUrl: './write.page.component.html', + styleUrls: ['./write.page.component.scss'] +}) +export class WritePageComponent implements OnInit, OnDestroy, CanDeactivate { + aid: string; + articleMongodb$: Observable; + tabIndex: Map; + + user: User; + save$: Observable
; + saveDirty = false; + + saveLabel: string; + saveConfirmMsg: string; + resultSuccess: string; + saveSuccessMsg: string; + resultFail: string; + saveFailMsg: string; + cancelConfirmMsg: string; + cancelConfirmDetailMsg: string; + + constructor( + private activatedRoute: ActivatedRoute, + private matDialog: MatDialog, + private matSnackBar: MatSnackBar, + private store: Store, + private router: Router, + private articleGetService: ArticleGetService, + private appService: AppService, + private translateService: TranslateService, + ) { + this.tabIndex = new Map(); + this.tabIndex.set(ArticleType.NONE, -1); + this.tabIndex.set(ArticleType.Illustrations, 0); + this.tabIndex.set(ArticleType.Cartoons, 1); + this.tabIndex.set(ArticleType.Novel, 2); + } + + ngOnInit(): void { + + AccountsUtil.getUser(this.store) + .then((user: User) => { + if (user) { + this.user = user; + } + }) + .catch(error => { + return of(error); + }); + + this.articleMongodb$ = this.activatedRoute.paramMap.pipe( + take(1), + switchMap(params => { + // (+) before `params.get()` turns the string into a number + this.aid = params.get('aid'); + + if (this.aid) { + return this.articleGetService.get(this.aid).pipe( + map((articleMongodb: ArticleMongodb) => { + return articleMongodb; + }), + catchError(error => { + return of(error); + }) + ); + } + return of({ + type: ArticleType.NONE + }); + }), + catchError(error => { + return of(error); + }) + ); + + this.translateService.get([ + 'common.save', + 'common.saveConfirmMsg', + 'common.resultSuccess', + 'common.saveSuccessMsg', + 'common.resultFail', + 'common.saveFailMsg', + 'common.cancelConfirmMsg', + 'common.cancelConfirmDetailMsg']) + .subscribe(translations => { + this.saveLabel = translations['common.save']; + this.saveConfirmMsg = translations['common.saveConfirmMsg']; + this.resultSuccess = translations['common.resultSuccess']; + this.saveSuccessMsg = translations['common.saveSuccessMsg']; + this.resultFail = translations['common.resultFail']; + this.saveFailMsg = translations['common.saveFailMsg']; + this.cancelConfirmMsg = translations['common.cancelConfirmMsg']; + this.cancelConfirmDetailMsg = translations['common.cancelConfirmDetailMsg']; + }); + } + + ngOnDestroy(): void { } + + selectedIndex(articleType: ArticleType): number { + if (articleType === ArticleType.NONE) { + articleType = environment.article.default as ArticleType; + } + return this.tabIndex.get(articleType); + } + + canDeactivate(): Observable | Promise | boolean { + if (!this.saveDirty) { + return true; + } + + return this.appService.canDeactivate( + this.cancelConfirmMsg, + this.cancelConfirmDetailMsg + ); + } + + onClickCancel() { + if (this.user) { + this.router.navigateByUrl(`/article/${this.user.id}`); + } + } + + async onClickSave() { + if (!this.save$) { + return; + } + + const result = await UIUtil.dialogOpen< + ConfirmDialogComponent, + ConfirmDialogData, + ConfirmDialogResult + >(this.matDialog, ConfirmDialogComponent, { + width: '220px', + data: { + title: this.saveLabel, + message: this.saveConfirmMsg + } + }); + if (!result.choice) { + return; + } + + this.store.dispatch(new AppEventsStore.ShowProgressBar()); + this.save$ + .pipe( + take(1), + tap(() => { }), + map(article => { + this.matSnackBar.openFromComponent( + SuccessSnackBarComponent, + { + duration: 3000, + data: { + title: this.resultSuccess, + message: this.saveSuccessMsg + } as SuccessSnackBarData + } + ); + this.saveDirty = false; + this.router.navigateByUrl(`/article/${article.user.id}`); + }), + catchError(error => { + this.matSnackBar.openFromComponent( + FailureSnackBarComponent, + { + duration: 3000, + data: { + title: this.resultFail, + message: this.saveFailMsg, + error: error + } as FailureSnackBarData + } + ); + return of(); + }), + finalize(() => { + this.store.dispatch(new AppEventsStore.HideProgressBar()); + }) + ) + .subscribe(); + } +} diff --git a/src/app/pages/main/component/index.ts b/src/app/pages/main/component/index.ts new file mode 100755 index 0000000..0ca28c9 --- /dev/null +++ b/src/app/pages/main/component/index.ts @@ -0,0 +1,5 @@ +import { MainPageComponent } from './main/main.page.component'; + +export const COMPONENTS = [ + MainPageComponent, +]; diff --git a/src/app/pages/main/component/main/main.page.component.html b/src/app/pages/main/component/main/main.page.component.html new file mode 100755 index 0000000..2f45ec0 --- /dev/null +++ b/src/app/pages/main/component/main/main.page.component.html @@ -0,0 +1,54 @@ +
+ + +
+ + +
+
+ + + + + + + + +

{{ 'main.msgWorksRecommend' | translate }}

+
+ +

{{ 'main.msgCoCoRecommend' | translate }}

+
+
+ + +
+
+
+ + +
+ Advertisements +
+ + +
+
+
+
+ + + + + + + + + + +
+
+
+
diff --git a/src/app/pages/main/component/main/main.page.component.scss b/src/app/pages/main/component/main/main.page.component.scss new file mode 100755 index 0000000..8647858 --- /dev/null +++ b/src/app/pages/main/component/main/main.page.component.scss @@ -0,0 +1,3 @@ +.article-display-grid { + height: 150px; +} diff --git a/src/app/pages/main/component/main/main.page.component.ts b/src/app/pages/main/component/main/main.page.component.ts new file mode 100755 index 0000000..83d27a4 --- /dev/null +++ b/src/app/pages/main/component/main/main.page.component.ts @@ -0,0 +1,129 @@ +/** + * 파 일 명: main.page.component.ts + * 작성일자: 2018-12-20 + * 작 성 자: 박병준 + * 설 명: main page component를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Component, OnInit } from '@angular/core'; + +import { Store } from '@ngrx/store'; + +import { Observable, of } from 'rxjs'; +import { switchMap, tap, map, catchError, take, finalize } from 'rxjs/operators'; + +import { ChangeEvent } from 'ngx-virtual-scroller'; + +import * as AppEventsStore from '../../../../store/events'; +import { ContentsService } from '../../../../../modules/contents/service/contents.service'; +import { Contents } from '../../../../../shared/contents/model/contents.model'; +import { ContentsRequest } from '../../../../../shared/contents/type/contents-request.type'; +import { ContentsRequestUser } from '../../../../../shared/contents/type/contents-request-user.type'; +import { ContentsSize } from '../../../../../shared/contents/model/contents-size.model'; +import { AccountsUtil } from '../../../../../modules/accounts/util/accounts.util'; +import { User } from '../../../../../shared/user/model/user.model'; +import { ArticleType } from '../../../../../shared/article/type/article-type.type'; +import { ArticleMongodb } from '../../../../../shared/article/model/article.mongodb.model'; +import { Article } from '../../../../../shared/article/model/article.model'; + +import * as ContentsConfig from '../../../../../config/contents'; +import { ContentsUtil } from '../../../../../shared/contents/util/contents.util'; +import { ContentsType } from '../../../../../shared/contents/type/contents-type.type'; + +@Component({ + selector: 'app-page-main', + templateUrl: './main.page.component.html', + styleUrls: ['./main.page.component.scss'], +}) +export class MainPageComponent implements OnInit { + articleType: ArticleType = ArticleType.Cartoons; + contentsRequest: ContentsRequest = ContentsRequest.Recommendations; + contentsSizeList: ContentsSize[]; + page: number; + user: User; + pageEnded: boolean; + + contentsList: Contents[] = []; + + constructor( + private store: Store, + private contentsService: ContentsService, + ) { + } + + ngOnInit(): void { + this.setContentsSizeList(); + this.page = 1; + this.pageEnded = false; + this.articleType = ArticleType.Cartoons; + AccountsUtil.getUser(this.store) + .then((user) => { + if (user) { + this.user = user; + this.setContentsSizeList(); + } + this.fetchContentsList(); + }) + .catch((reason) => { + console.log(reason); + }); + } + + fetchContentsList() { + this.contentsService.getAllByOptions(this.contentsRequest, this.articleType, this.user, this.page, this.contentsSizeList).pipe( + take(1), + tap(() => { + this.store.dispatch(new AppEventsStore.ShowProgressBar()); + }), + map((contentsList: Contents[]) => { + // console.log(contentsList); + this.contentsList = this.contentsList.concat(contentsList); + this.pageEnded = ContentsUtil.isEnded(ContentsType.Article, this.contentsSizeList, contentsList); + if (!this.pageEnded) { + this.page++; + } + }), + catchError((error) => { + return of(error); + }), + finalize(() => { + this.store.dispatch(new AppEventsStore.HideProgressBar()); + }), + ).subscribe(); + } + + setContentsSizeList() { + this.contentsSizeList = ContentsConfig.getArticleContentsSize( + this.articleType, + this.contentsRequest, + this.user ? ContentsRequestUser.User : ContentsRequestUser.Guest + ); + } + + protected onVSEnd(event: ChangeEvent) { + console.log('onVSEnd', event); + if (event.start === -1 || event.start === 0 || event.end !== this.contentsList.length - 1) { + return; + } + if (this.pageEnded) { + console.log('Page ended'); + return; + } + this.fetchContentsList(); + } + + converToArticleList(mongodbArticleList: ArticleMongodb[]): Article[] { + if (!mongodbArticleList || 0 === mongodbArticleList.length) { + return []; + } + const articleList: Article[] = []; + for (const mongodbArticle of mongodbArticleList) { + articleList.push(mongodbArticle.article); + } + return articleList; + } + +} diff --git a/src/app/pages/main/main-routing.page.module.ts b/src/app/pages/main/main-routing.page.module.ts new file mode 100755 index 0000000..ae7840d --- /dev/null +++ b/src/app/pages/main/main-routing.page.module.ts @@ -0,0 +1,33 @@ +/** + * 파 일 명: accounts-routing.module.ts + * 작성일자: 2018-12-20 + * 작 성 자: 박병준 + * 설 명: account 영역의 routing을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; +import { MainLayoutComponent } from '../../layout/component/main/main.layout.component'; +import { MainPageComponent } from './component/main/main.page.component'; + +const routes: Routes = [ + { + path: '', + component: MainLayoutComponent, + children: [ + { + path: '', + component: MainPageComponent + } + ] + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class MainRoutingPageModule { } diff --git a/src/app/pages/main/main.page.module.ts b/src/app/pages/main/main.page.module.ts new file mode 100755 index 0000000..e18f135 --- /dev/null +++ b/src/app/pages/main/main.page.module.ts @@ -0,0 +1,44 @@ +/** + * 파 일 명: accounts.module.ts + * 작성일자: 2018-12-20 + * 작 성 자: 박병준 + * 설 명: accounts page 영역의 module을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { TranslateModule } from '@ngx-translate/core'; + +import { COMPONENTS } from './component'; +import { MainRoutingPageModule } from './main-routing.page.module'; +import { LayoutModule } from '../../layout/layout.module'; +import { SharedModule } from '../../../modules/common/shared/shared.module'; +import { CartoonsModule } from '../../../modules/cartoons/cartoons.module'; +import { IllustrationsModule } from '../../../modules/illustrations/illustrations.module'; +import { NovelModule } from '../../../modules/novel/novel.module'; +import { AttachmentsModule } from '../../../modules/attachments/attachments.module'; +import { UserModule } from '../../../modules/user/user.module'; +import { UserSupportModule } from '../../../modules/user-support/user-support.module'; + +@NgModule({ + imports: [ + CommonModule, + TranslateModule, + MainRoutingPageModule, + SharedModule, + LayoutModule, + CartoonsModule, + IllustrationsModule, + NovelModule, + AttachmentsModule, + UserModule, + UserSupportModule, + ], + declarations: [ + ...COMPONENTS, + ], +}) +export class MainPageModule { } diff --git a/src/app/pages/user/component/bookmarks/bookmarks.page.component.html b/src/app/pages/user/component/bookmarks/bookmarks.page.component.html new file mode 100755 index 0000000..1ce6d25 --- /dev/null +++ b/src/app/pages/user/component/bookmarks/bookmarks.page.component.html @@ -0,0 +1,13 @@ + +
+
+ + + + + + + + +
+
diff --git a/src/app/pages/user/component/bookmarks/bookmarks.page.component.scss b/src/app/pages/user/component/bookmarks/bookmarks.page.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/app/pages/user/component/bookmarks/bookmarks.page.component.ts b/src/app/pages/user/component/bookmarks/bookmarks.page.component.ts new file mode 100755 index 0000000..c328aed --- /dev/null +++ b/src/app/pages/user/component/bookmarks/bookmarks.page.component.ts @@ -0,0 +1,78 @@ +/** + * 파 일 명: bookmarks.page.component.ts + * 작성일자: 2018-12-20 + * 작 성 자: 박병준 + * 설 명: bookmarks page component를 정의한다. + * 수정일시: 2019-01-22 + * 수 정 자: 윤대훈 + * 수정내용: bookmarks list 출력 + * 참고사항: + */ +import { Component, AfterContentInit, OnInit } from '@angular/core'; +import { ArticleBookmarks } from 'src/shared/article/model/article-bookmarks.model'; +import { AccountsUtil } from 'src/modules/accounts/util/accounts.util'; +import { Store } from '@ngrx/store'; +import { of } from 'rxjs'; +import { map, take, catchError } from 'rxjs/operators'; +import { ArticleMongodb } from 'src/shared/article/model/article.mongodb.model'; +import { ArticleBookmarksService } from 'src/modules/article/service/article-bookmarks.service'; + + + +@Component({ + selector: 'app-page-user-bookmarks', + templateUrl: './bookmarks.page.component.html', + styleUrls: ['./bookmarks.page.component.scss'], +}) +export class BookmarksPageComponent implements OnInit, AfterContentInit { + + bookmarksList: ArticleBookmarks[] = []; + articleMongodbList: ArticleMongodb[] = []; + + authorFollowYn: boolean; + + constructor(private store: Store, + private articleBookmarksService: ArticleBookmarksService) { + } + + ngOnInit() { + this.authorFollowYn = false; + this.processBookmarksList(); + } + + processBookmarksList() { + + AccountsUtil.getUser(this.store) + .then((user) => { + if (user) { + this.fetchBookmarksList(user.id); + } + }) + .catch((reason) => { + console.log(reason); + }); + + } + + + fetchBookmarksList(uid: string) { + + this.articleBookmarksService.getAllArticleByUid(uid).pipe( + take(1), + map((articleMongodbList: ArticleMongodb[]) => { + if (articleMongodbList && 0 < articleMongodbList.length) { + this.articleMongodbList = articleMongodbList; + } + }), + catchError(error => { + return of(error); + }), + + ).subscribe(); + + } + + ngAfterContentInit(): void { + + } +} diff --git a/src/app/pages/user/component/edit/edit.page.component.html b/src/app/pages/user/component/edit/edit.page.component.html new file mode 100755 index 0000000..4207739 --- /dev/null +++ b/src/app/pages/user/component/edit/edit.page.component.html @@ -0,0 +1,16 @@ + + +
+
+
    +
  • + +
  • +
  • + +
  • +
+
+
diff --git a/src/app/pages/user/component/edit/edit.page.component.scss b/src/app/pages/user/component/edit/edit.page.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/app/pages/user/component/edit/edit.page.component.ts b/src/app/pages/user/component/edit/edit.page.component.ts new file mode 100755 index 0000000..0158486 --- /dev/null +++ b/src/app/pages/user/component/edit/edit.page.component.ts @@ -0,0 +1,162 @@ +/** + * 파 일 명: edit.page.component.ts + * 작성일자: 2018-12-20 + * 작 성 자: 박병준 + * 설 명: edit page component를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Component, OnInit, ViewChild } from '@angular/core'; +import { Router } from '@angular/router'; +import { MatDialog, MatSnackBar } from '@angular/material'; + +import { Store } from '@ngrx/store'; +import { TranslateService } from '@ngx-translate/core'; + +import { Observable, of } from 'rxjs'; +import { switchMap, tap, map, catchError, take, finalize, } from 'rxjs/operators'; + +import { CanDeactivate } from '../../../../guard/can-deactivate.guard'; +import { AppService } from '../../../../service/app.service'; +import { User } from '../../../../../shared/user/model/user.model'; +import { UIUtil } from '../../../../../modules/common/util/ui/dialog.util'; +import { + ConfirmDialogComponent, + ConfirmDialogData, + ConfirmDialogResult, +} from '../../../../layout/dialog/confirm/confirm.dialog.component'; + +import * as AppEventsStore from '../../../../store/events'; +import { SuccessSnackBarComponent, SuccessSnackBarData } from '../../../../layout/dialog/events/success/success.snackbar.component'; +import { FailureSnackBarComponent, FailureSnackBarData } from '../../../../layout/dialog/events/failure/failure.snackbar.component'; +import { AccountsUtil } from '../../../../../modules/accounts/util/accounts.util'; + +@Component({ + selector: 'app-page-user-edit', + templateUrl: './edit.page.component.html', + styleUrls: ['./edit.page.component.scss'], +}) +export class EditPageComponent implements OnInit, CanDeactivate { + save$: Observable; + saveDirty = false; + + user: User; + + saveLabel: string; + saveConfirmMsg: string; + resultSuccess: string; + saveSuccessMsg: string; + resultFail: string; + saveFailMsg: string; + cancelConfirmMsg: string; + cancelConfirmDetailMsg: string; + + constructor( + private matDialog: MatDialog, + private matSnackBar: MatSnackBar, + private store: Store, + private router: Router, + private appService: AppService, + private translateService: TranslateService, + ) { + } + + ngOnInit(): void { + AccountsUtil.getUser(this.store) + .then((user) => { + this.user = user; + }) + .catch((reason) => { + console.log(reason); + }); + + this.translateService.get([ + 'common.save', + 'common.saveConfirmMsg', + 'common.resultSuccess', + 'common.saveSuccessMsg', + 'common.resultFail', + 'common.saveFailMsg', + 'common.cancelConfirmMsg', + 'common.cancelConfirmDetailMsg']) + .subscribe(translations => { + this.saveLabel = translations['common.save']; + this.saveConfirmMsg = translations['common.saveConfirmMsg']; + this.resultSuccess = translations['common.resultSuccess']; + this.saveSuccessMsg = translations['common.saveSuccessMsg']; + this.resultFail = translations['common.resultFail']; + this.saveFailMsg = translations['common.saveFailMsg']; + this.cancelConfirmMsg = translations['common.cancelConfirmMsg']; + this.cancelConfirmDetailMsg = translations['common.cancelConfirmDetailMsg']; + }); + } + + canDeactivate(): Observable | Promise | boolean { + if (!this.saveDirty) { + return true; + } + + return this.appService.canDeactivate( + this.cancelConfirmMsg, + this.cancelConfirmDetailMsg + ); + } + + onClickCancel() { + this.router.navigateByUrl(`/article/${this.user.id}`); + } + + async onClickSave() { + if (!this.save$) { + return; + } + + const result = await UIUtil.dialogOpen( + this.matDialog, + ConfirmDialogComponent, + { + width: '220px', + data: { + title: this.saveLabel, + message: this.saveConfirmMsg + } + } + ); + if (!result.choice) { + return; + } + + this.store.dispatch(new AppEventsStore.ShowProgressBar()); + this.save$.pipe( + take(1), + tap(() => { + }), + map((user) => { + this.matSnackBar.openFromComponent(SuccessSnackBarComponent, { + duration: 3000, + data: { + title: this.resultSuccess, + message: this.saveSuccessMsg + } as SuccessSnackBarData, + }); + this.router.navigateByUrl(`/article/${user.id}`); + }), + catchError((error) => { + this.matSnackBar.openFromComponent(FailureSnackBarComponent, { + duration: 3000, + data: { + title: this.resultFail, + message: this.saveFailMsg, + error: error, + } as FailureSnackBarData, + }); + return of(); + }), + finalize(() => { + this.store.dispatch(new AppEventsStore.HideProgressBar()); + }), + ).subscribe(); + } +} diff --git a/src/app/pages/user/component/favor/favor.page.component.html b/src/app/pages/user/component/favor/favor.page.component.html new file mode 100755 index 0000000..c1d82a3 --- /dev/null +++ b/src/app/pages/user/component/favor/favor.page.component.html @@ -0,0 +1,4 @@ + + + diff --git a/src/app/pages/user/component/favor/favor.page.component.scss b/src/app/pages/user/component/favor/favor.page.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/app/pages/user/component/favor/favor.page.component.ts b/src/app/pages/user/component/favor/favor.page.component.ts new file mode 100755 index 0000000..32be137 --- /dev/null +++ b/src/app/pages/user/component/favor/favor.page.component.ts @@ -0,0 +1,155 @@ +/** + * 파 일 명: favor.page.component.ts + * 작성일자: 2018-12-20 + * 작 성 자: 박병준 + * 설 명: favor page component를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; +import { MatSnackBar, MatDialog } from '@angular/material'; + +import { Store } from '@ngrx/store'; +import { TranslateService } from '@ngx-translate/core'; + +import { Observable, of } from 'rxjs'; +import { tap, map, catchError, take, finalize, } from 'rxjs/operators'; + +import { User } from '../../../../../shared/user/model/user.model'; + +import * as AppEventsStore from '../../../../store/events'; +import { + ConfirmDialogComponent, + ConfirmDialogData, + ConfirmDialogResult, +} from '../../../../layout/dialog/confirm/confirm.dialog.component'; +import { UIUtil } from '../../../../../modules/common/util/ui/dialog.util'; + +import { SuccessSnackBarComponent, SuccessSnackBarData } from '../../../../layout/dialog/events/success/success.snackbar.component'; +import { FailureSnackBarComponent, FailureSnackBarData } from '../../../../layout/dialog/events/failure/failure.snackbar.component'; +import { AppService } from '../../../../service/app.service'; + +import { environment } from '../../../../../environments/environment'; + +@Component({ + selector: 'app-page-user-favor', + templateUrl: './favor.page.component.html', + styleUrls: ['./favor.page.component.scss'] +}) +export class FavorPageComponent implements OnInit { + // selectMinCount = environment.userAnalysis.favorTag.selectMinCount; + save$: Observable; + saveDirty = false; + selectMinCount = 1; + + saveLabel: string; + saveConfirmMsg: string; + resultSuccess: string; + saveSuccessMsg: string; + resultFail: string; + saveFailMsg: string; + cancelConfirmMsg: string; + cancelConfirmDetailMsg: string; + + constructor( + private matSnackBar: MatSnackBar, + private matDialog: MatDialog, + private store: Store, + private router: Router, + private appService: AppService, + private translateService: TranslateService, + ) { + } + + ngOnInit(): void { + this.translateService.get([ + 'common.save', + 'common.saveConfirmMsg', + 'common.resultSuccess', + 'common.saveSuccessMsg', + 'common.resultFail', + 'common.saveFailMsg', + 'common.cancelConfirmMsg', + 'common.cancelConfirmDetailMsg']) + .subscribe(translations => { + this.saveLabel = translations['common.save']; + this.saveConfirmMsg = translations['common.saveConfirmMsg']; + this.resultSuccess = translations['common.resultSuccess']; + this.saveSuccessMsg = translations['common.saveSuccessMsg']; + this.resultFail = translations['common.resultFail']; + this.saveFailMsg = translations['common.saveFailMsg']; + this.cancelConfirmMsg = translations['common.cancelConfirmMsg']; + this.cancelConfirmDetailMsg = translations['common.cancelConfirmDetailMsg']; + }); + } + + canDeactivate(): Observable | Promise | boolean { + if (!this.saveDirty) { + return true; + } + + return this.appService.canDeactivate( + this.cancelConfirmMsg, + this.cancelConfirmDetailMsg + ); + } + + async onSave() { + if (!this.save$) { + return; + } + + const result = await UIUtil.dialogOpen( + this.matDialog, + ConfirmDialogComponent, + { + width: '220px', + data: { + title: this.saveLabel, + message: this.saveConfirmMsg + } + } + ); + if (!result.choice) { + return; + } + + this.store.dispatch(new AppEventsStore.ShowProgressBar()); + this.save$.pipe( + take(1), + tap(() => { + }), + map((user) => { + this.matSnackBar.openFromComponent(SuccessSnackBarComponent, { + duration: 3000, + data: { + title: this.resultSuccess, + message: this.saveSuccessMsg + } as SuccessSnackBarData, + }); + this.router.navigateByUrl(`/article/${user.id}`); + }), + catchError((error) => { + this.matSnackBar.openFromComponent(FailureSnackBarComponent, { + duration: 3000, + data: { + title: this.resultFail, + message: this.saveFailMsg, + error: error, + } as FailureSnackBarData, + }); + return of(); + }), + finalize(() => { + this.store.dispatch(new AppEventsStore.HideProgressBar()); + }), + ).subscribe(); + } + + onCancel(user: User) { + this.router.navigateByUrl(`/article/${user.id}`); + } +} diff --git a/src/app/pages/user/component/index.ts b/src/app/pages/user/component/index.ts new file mode 100755 index 0000000..c7ac25d --- /dev/null +++ b/src/app/pages/user/component/index.ts @@ -0,0 +1,13 @@ +import { BookmarksPageComponent } from './bookmarks/bookmarks.page.component'; +import { EditPageComponent } from './edit/edit.page.component'; +import { FavorPageComponent } from './favor/favor.page.component'; +import { SettingsPageComponent } from './settings/settings.page.component'; +import { RecommendationsPageComponent } from './recommendations/recommendations.page.component'; + +export const COMPONENTS = [ + BookmarksPageComponent, + EditPageComponent, + FavorPageComponent, + SettingsPageComponent, + RecommendationsPageComponent, +]; diff --git a/src/app/pages/user/component/recommendations/recommendations.page.component.html b/src/app/pages/user/component/recommendations/recommendations.page.component.html new file mode 100755 index 0000000..2bcc355 --- /dev/null +++ b/src/app/pages/user/component/recommendations/recommendations.page.component.html @@ -0,0 +1,2 @@ + + diff --git a/src/app/pages/user/component/recommendations/recommendations.page.component.scss b/src/app/pages/user/component/recommendations/recommendations.page.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/app/pages/user/component/recommendations/recommendations.page.component.ts b/src/app/pages/user/component/recommendations/recommendations.page.component.ts new file mode 100755 index 0000000..43c463d --- /dev/null +++ b/src/app/pages/user/component/recommendations/recommendations.page.component.ts @@ -0,0 +1,22 @@ +import { Component } from '@angular/core'; +import { Router } from '@angular/router'; +import { Store } from '@ngrx/store'; + +import { User } from '../../../../../shared/user/model/user.model'; +import * as UserProfileStore from '../../../../../modules/user/store/profile'; + +@Component({ + selector: 'app-page-user-recommendations', + templateUrl: './recommendations.page.component.html', + styleUrls: ['./recommendations.page.component.scss'] +}) +export class RecommendationsPageComponent { + constructor( + private router: Router, + private store: Store, + ) { } + + userSelected(user: User) { + this.store.dispatch(new UserProfileStore.GotoProfile({ uid: user.id })); + } +} diff --git a/src/app/pages/user/component/settings/settings.page.component.html b/src/app/pages/user/component/settings/settings.page.component.html new file mode 100755 index 0000000..fdd68fd --- /dev/null +++ b/src/app/pages/user/component/settings/settings.page.component.html @@ -0,0 +1 @@ + diff --git a/src/app/pages/user/component/settings/settings.page.component.scss b/src/app/pages/user/component/settings/settings.page.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/app/pages/user/component/settings/settings.page.component.ts b/src/app/pages/user/component/settings/settings.page.component.ts new file mode 100755 index 0000000..45bd805 --- /dev/null +++ b/src/app/pages/user/component/settings/settings.page.component.ts @@ -0,0 +1,100 @@ +/** + * 파 일 명: settings.page.component.ts + * 작성일자: 2018-12-20 + * 작 성 자: 박병준 + * 설 명: settings page component를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Component } from '@angular/core'; +import { MatSnackBar } from '@angular/material'; + +import { Store } from '@ngrx/store'; +import { TranslateService } from '@ngx-translate/core'; + +import { Observable, of } from 'rxjs'; +import { tap, map, catchError, take, finalize, } from 'rxjs/operators'; + +import { CanDeactivate } from '../../../../guard/can-deactivate.guard'; +import { AppService } from '../../../../service/app.service'; + +import { User } from '../../../../../shared/user/model/user.model'; + +import * as AppEventsStore from '../../../../store/events'; +import { SuccessSnackBarComponent, SuccessSnackBarData } from 'src/app/layout/dialog/events/success/success.snackbar.component'; +import { FailureSnackBarComponent, FailureSnackBarData } from 'src/app/layout/dialog/events/failure/failure.snackbar.component'; + +@Component({ + selector: 'app-page-user-settings', + templateUrl: './settings.page.component.html', + styleUrls: ['./settings.page.component.scss'], +}) +export class SettingsPageComponent implements CanDeactivate { + resultSuccess: string; + saveSuccessMsg: string; + resultFail: string; + saveFailMsg: string; + + constructor( + private matSnackBar: MatSnackBar, + private store: Store, + private appService: AppService, + private translateService: TranslateService, + ) { + } + + canDeactivate(): Observable | Promise | boolean { + return true; + } + + onClickCancel() { + } + + async onChangedSaveObservable(save$: Observable) { + if (!save$) { + return; + } + + this.store.dispatch(new AppEventsStore.ShowProgressBar()); + save$.pipe( + take(1), + tap(() => { + }), + map((user) => { + this.translateService.get(['common.resultSuccess', 'common.saveSuccessMsg']) + .subscribe(translations => { + this.resultSuccess = translations['common.resultSuccess']; + this.saveSuccessMsg = translations['common.saveSuccessMsg']; + }); + this.matSnackBar.openFromComponent(SuccessSnackBarComponent, { + duration: 3000, + data: { + title: this.resultSuccess, + message: this.saveSuccessMsg + } as SuccessSnackBarData, + }); + }), + catchError((error) => { + this.translateService.get(['common.resultFail', 'common.saveFailMsg']) + .subscribe(translations => { + this.resultFail = translations['common.resultFail']; + this.saveFailMsg = translations['common.saveFailMsg']; + }); + this.matSnackBar.openFromComponent(FailureSnackBarComponent, { + duration: 3000, + data: { + title: this.resultFail, + message: this.saveFailMsg, + error: error, + } as FailureSnackBarData, + }); + return of(); + }), + finalize(() => { + this.store.dispatch(new AppEventsStore.HideProgressBar()); + }), + ).subscribe(); + } +} diff --git a/src/app/pages/user/user-routing.page.module.ts b/src/app/pages/user/user-routing.page.module.ts new file mode 100755 index 0000000..febce03 --- /dev/null +++ b/src/app/pages/user/user-routing.page.module.ts @@ -0,0 +1,77 @@ +/** + * 파 일 명: user-routing.module.ts + * 작성일자: 2018-12-20 + * 작 성 자: 박병준 + * 설 명: user 영역의 routing을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; +import { AccountsLayoutComponent } from '../../layout/component/accounts/accounts.layout.component'; +import { DefaultLayoutComponent } from '../../layout/component/default/default.layout.component'; +import { FavorPageComponent } from './component/favor/favor.page.component'; +import { EditPageComponent } from './component/edit/edit.page.component'; +import { BookmarksPageComponent } from './component/bookmarks/bookmarks.page.component'; +import { SettingsPageComponent } from './component/settings/settings.page.component'; +import { RecommendationsPageComponent } from './component/recommendations/recommendations.page.component'; +import { AuthGuard } from '../../guard/auth.guard'; +import { CanDeactivateGuard } from '../../guard/can-deactivate.guard'; + +const routes1: Routes = [ + { + path: '', + component: DefaultLayoutComponent, + children: [ + { + path: 'edit', + component: EditPageComponent, + canActivate: [AuthGuard], + canDeactivate: [CanDeactivateGuard], + runGuardsAndResolvers: 'always', + }, + { + path: 'bookmarks', + component: BookmarksPageComponent, + canActivate: [AuthGuard], + runGuardsAndResolvers: 'always', + }, + { + path: 'settings', + component: SettingsPageComponent, + canActivate: [AuthGuard], + canDeactivate: [CanDeactivateGuard], + runGuardsAndResolvers: 'always', + }, + { + path: 'recommendations', + component: RecommendationsPageComponent, + canActivate: [AuthGuard], + runGuardsAndResolvers: 'always', + } + ] + } +]; + +const route2: Routes = [ + { + path: '', + component: AccountsLayoutComponent, + children: [ + { + path: 'favor', + component: FavorPageComponent, + canActivate: [AuthGuard], + runGuardsAndResolvers: 'always', + } + ] + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes1), RouterModule.forChild(route2)], + exports: [RouterModule] +}) +export class UserRoutingPageModule { } diff --git a/src/app/pages/user/user.page.module.ts b/src/app/pages/user/user.page.module.ts new file mode 100755 index 0000000..0f8b5e2 --- /dev/null +++ b/src/app/pages/user/user.page.module.ts @@ -0,0 +1,42 @@ +/** + * 파 일 명: user.module.ts + * 작성일자: 2018-12-21 + * 작 성 자: 박병준 + * 설 명: user page 영역의 module을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { TranslateModule } from '@ngx-translate/core'; + +import { LayoutModule } from '../../layout/layout.module'; +import { SharedModule } from '../../../modules/common/shared/shared.module'; + +import { UserRoutingPageModule } from './user-routing.page.module'; +import { UserModule } from '../../../modules/user/user.module'; +import { UserAnalysisModule } from '../../../modules/user-analysis/user-analysis.module'; +import { UserSupportModule } from '../../../modules/user-support/user-support.module'; +import { COMPONENTS } from './component'; +import { CartoonsModule } from '../../../modules/cartoons/cartoons.module'; +import { IllustrationsModule } from 'src/modules/illustrations/illustrations.module'; +import { NovelModule } from 'src/modules/novel/novel.module'; +@NgModule({ + imports: [ + CommonModule, + TranslateModule, + LayoutModule, + UserRoutingPageModule, + UserModule, + UserAnalysisModule, + UserSupportModule, + SharedModule, + CartoonsModule, + IllustrationsModule, + NovelModule + ], + declarations: [...COMPONENTS] +}) +export class UserPageModule { } diff --git a/src/app/service/app.service.ts b/src/app/service/app.service.ts new file mode 100755 index 0000000..67264f3 --- /dev/null +++ b/src/app/service/app.service.ts @@ -0,0 +1,135 @@ +import { Injectable } from '@angular/core'; +import { Store, select } from '@ngrx/store'; +import { MatDialog } from '@angular/material'; +import { EventManager } from '@angular/platform-browser'; +import { CookieService } from 'ngx-cookie-service'; +import { TranslateService } from '@ngx-translate/core'; + +import { of } from 'rxjs'; +import { exhaustMap, map, take, catchError } from 'rxjs/operators'; + +import * as AppLayoutStore from '../store/layout'; +import * as AppLoginStore from '../store/login'; + +import * as AccountsStore from '../../modules/accounts/store'; +import * as AccountsAuthStore from '../../modules/accounts/store/auth'; +import * as MetaCommentsTagStore from '../../modules/meta/store/comments-tag'; + +import { Environment } from '../type/environment.type'; +import { User } from '../../shared/user/model/user.model'; +import { LangType } from '../../shared/common/type/lang.type'; +import { + CanDeactivateDialogComponent, + CanDeactivateDialogData, + CanDeactivateDialogResult +} from '../layout/dialog/can-deactivate/can-deactivate.dialog.component'; +import { AccountsService } from '../../modules/accounts/service/accounts.service'; +import { AccountsUtil } from 'src/modules/accounts/util/accounts.util'; + + +@Injectable() +export class AppService { + + constructor( + private store: Store, + private eventManager: EventManager, + private cookieService: CookieService, + private tanslateService: TranslateService, + private matDialog: MatDialog, + private accountsService: AccountsService, + ) { } + + initApp(): Promise { + return new Promise((resolve, reject) => { + // 지원하는 언어, 일본의 경우 브라운저 언어는 "ja" 로 되어 있음. environment는 변수명 중복으로 인해 소스에 넣음. + const supportLanguages = ['ko', 'en', 'ja']; + // 브라우저 언어 체크 + const browserLang = this.tanslateService.getBrowserCultureLang(); + let currentLang = 'en'; + for (let i = 0; i < supportLanguages.length; i++) { + if (browserLang.startsWith(supportLanguages[i])) { + currentLang = supportLanguages[i]; + if (supportLanguages[i] === 'ja') { + currentLang = 'jp'; + } + } + } + + // if (null === currentLang) { + // currentLang = 'en'; + // } + this.tanslateService.setDefaultLang(currentLang); + + this.eventManager.addGlobalEventListener('window', 'resize', (event) => { + const width = event.target.innerWidth; + let environment: Environment; + if (width <= 800) { + environment = Environment.Mobile; + } else { + environment = Environment.Desktop; + } + this.store.dispatch(new AppLayoutStore.ChangeEnvironment({ environment })); + }); + + this.store.dispatch(new MetaCommentsTagStore.Load()); + + // AccountsUtil.getUser(this.store) + // .then((user) => { + // if (user) { + // this.tanslateService.use(user.displayLanguage.toLocaleLowerCase()); + // } + // }) + // .catch((reason) => { + // console.log(reason); + // }); + + if (this.cookieService.check('jwt')) { + this.accountsService.login().pipe( + take(1), + map((user: User) => { + if (user) { + this.tanslateService.use(user.displayLanguage.toLocaleLowerCase()); + } + this.store.dispatch(new AccountsAuthStore.LoginSuccess({ + user: user, + returnURL: null, + })); + return resolve(); + }), + catchError((error) => { + this.store.dispatch(new AppLoginStore.LoginFailure({ err: error })); + resolve(); + return of(); + }), + ).subscribe(); + } else { + return resolve(); + } + }); + } + + canDeactivate(title: string, message: string): Promise { + return new Promise((resolve, reject) => { + const mediaDialogRef = this.matDialog.open( + CanDeactivateDialogComponent, + { + width: '200px', + data: { + title: title, + message: message, + } + }); + + mediaDialogRef.afterClosed().pipe( + take(1), + map((result) => { + return resolve(result.choice); + }), + catchError((err) => { + reject(err); + return of(err); + }) + ).subscribe(); + }); + } +} diff --git a/src/app/service/auth.http.interceptor.ts b/src/app/service/auth.http.interceptor.ts new file mode 100755 index 0000000..ddc607d --- /dev/null +++ b/src/app/service/auth.http.interceptor.ts @@ -0,0 +1,31 @@ +import { Injectable } from '@angular/core'; +import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { CookieService } from 'ngx-cookie-service'; + +@Injectable() +export class AuthHttpInterceptor implements HttpInterceptor { + constructor( + private cookieService: CookieService, + ) { + + } + + intercept( + req: HttpRequest, + next: HttpHandler + ): Observable> { + + const jwtToken = this.cookieService.get('jwt'); + + if (jwtToken) { + const cloned = req.clone({ + headers: req.headers.set('Authorization', 'Bearer ' + jwtToken), + }); + + return next.handle(cloned); + } else { + return next.handle(req); + } + } +} diff --git a/src/app/store/events/app-events.action.ts b/src/app/store/events/app-events.action.ts new file mode 100755 index 0000000..201d40a --- /dev/null +++ b/src/app/store/events/app-events.action.ts @@ -0,0 +1,23 @@ +import { Action } from '@ngrx/store'; + +export enum ActionType { + ShowProgressBar = '[app.events] ShowProgressBar', + HideProgressBar = '[app.events] HideProgressBar', +} + +export class ShowProgressBar implements Action { + readonly type = ActionType.ShowProgressBar; + + constructor() { } +} + +export class HideProgressBar implements Action { + readonly type = ActionType.HideProgressBar; + + constructor() { } +} + +export type Actions = + | ShowProgressBar + | HideProgressBar + ; diff --git a/src/app/store/events/app-events.effect.ts b/src/app/store/events/app-events.effect.ts new file mode 100755 index 0000000..932f299 --- /dev/null +++ b/src/app/store/events/app-events.effect.ts @@ -0,0 +1,103 @@ +import { Injectable } from '@angular/core'; +import { MatSnackBar } from '@angular/material'; +import { Location } from '@angular/common'; +import { Router } from '@angular/router'; + +import { Effect, Actions, ofType } from '@ngrx/effects'; +import { map, tap } from 'rxjs/operators'; + +import { + ExecutionParam, + Execution, + ExecutionFailure, + ExecutionSuccess, + ExecutionComplete, + ActionType as EventsActionType +} from '../../../modules/common/shared/store/events'; + +import { ShowProgressBar, HideProgressBar } from './app-events.action'; + +import { environment } from '../../../environments/environment'; +import { + SuccessSnackBarComponent, + SuccessSnackBarData +} from '../../layout/dialog/events/success/success.snackbar.component'; +import { + FailureSnackBarComponent, + FailureSnackBarData +} from '../../layout/dialog/events/failure/failure.snackbar.component'; +import { Article } from '../../../shared/article/model/article.model'; + +@Injectable() +export class Effects { + constructor( + private actions$: Actions, + private router: Router, + private location: Location, + private matSnackBar: MatSnackBar + ) { } + + @Effect() + execution$ = this.actions$.pipe( + ofType(EventsActionType.Execution), + map(() => { + return new ShowProgressBar(); + }) + ); + + @Effect({ dispatch: false }) + executionFailure$ = this.actions$.pipe( + ofType(EventsActionType.ExecutionFailure), + map((action: ExecutionFailure) => action.payload), + tap((executionParam: ExecutionParam) => { + this.matSnackBar.openFromComponent( + FailureSnackBarComponent, + { + duration: 3000, + data: { + title: executionParam.title, + message: executionParam.message, + error: executionParam.error + } as FailureSnackBarData + } + ); + }) + ); + + @Effect({ dispatch: false }) + executionSuccess$ = this.actions$.pipe( + ofType(EventsActionType.ExecutionSuccess), + map((action: ExecutionSuccess) => action.payload), + tap((executionParam: ExecutionParam) => { + this.matSnackBar.openFromComponent( + SuccessSnackBarComponent, + { + duration: 3000, + data: { + title: executionParam.title, + message: executionParam.message, + error: executionParam.error + } as SuccessSnackBarData + } + ); + + if (executionParam.routing) { + const path = this.location.path(); + const article: Article = executionParam.routing.query; + if (article) { + if (0 === path.indexOf('/article/write')) { + this.router.navigateByUrl(`/article/${article.user.id}`); + } + } + } + }) + ); + + @Effect() + executionComplete$ = this.actions$.pipe( + ofType(EventsActionType.ExecutionComplete), + map(() => { + return new HideProgressBar(); + }) + ); +} diff --git a/src/app/store/events/app-events.reducer.ts b/src/app/store/events/app-events.reducer.ts new file mode 100755 index 0000000..430a947 --- /dev/null +++ b/src/app/store/events/app-events.reducer.ts @@ -0,0 +1,25 @@ +import { Actions, ActionType } from './app-events.action'; + +import { State, initialState } from './app-events.state'; + +export function reducer(state: State = initialState, action: Actions): State { + switch (action.type) { + case ActionType.ShowProgressBar: { + return { + ...state, + showProgressBar: true, + }; + } + + case ActionType.HideProgressBar: { + return { + ...state, + showProgressBar: false, + }; + } + + default: { + return state; + } + } +} diff --git a/src/app/store/events/app-events.state.ts b/src/app/store/events/app-events.state.ts new file mode 100755 index 0000000..67b93a9 --- /dev/null +++ b/src/app/store/events/app-events.state.ts @@ -0,0 +1,15 @@ +import { Selector, createSelector } from '@ngrx/store'; + +export interface State { + showProgressBar: boolean; +} + +export const initialState: State = { + showProgressBar: false, +}; + +export function getSelectors(selector: Selector) { + return { + selectShowProgressBar: createSelector(selector, (state: State) => state.showProgressBar), + }; +} diff --git a/src/app/store/events/index.ts b/src/app/store/events/index.ts new file mode 100755 index 0000000..bf689ba --- /dev/null +++ b/src/app/store/events/index.ts @@ -0,0 +1,4 @@ +export * from './app-events.action'; +export * from './app-events.effect'; +export * from './app-events.reducer'; +export * from './app-events.state'; diff --git a/src/app/store/index.ts b/src/app/store/index.ts new file mode 100755 index 0000000..3a8def1 --- /dev/null +++ b/src/app/store/index.ts @@ -0,0 +1,76 @@ +/** + * 파 일 명: index.ts + * 작성일자: 2018-12-17 + * 작 성 자: 박병준 + * 설 명: ngrx 정의를 한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Type } from '@angular/core'; +import { + ActionReducer, + ActionReducerMap, + MetaReducer, + createSelector, +} from '@ngrx/store'; + +import * as fromRouter from '@ngrx/router-store'; + +import { environment } from '../../environments/environment'; + +import * as AppEventsStore from './events'; +import * as AppLayoutStore from './layout'; +import * as AppLoginStore from './login'; +import * as AppNicknameStore from './nickname'; +import * as AppRouterStore from './router'; + +export const EFFECTS: Type[] = [ + AppLoginStore.Effects, + AppEventsStore.Effects, + AppNicknameStore.Effects, + AppRouterStore.Effects, +]; + +export const REDUCERS: ActionReducerMap = { + router: fromRouter.routerReducer, + layout: AppLayoutStore.reducer, + events: AppEventsStore.reducer, +}; + +// console.log all actions +export function logger(reducer: ActionReducer): ActionReducer { + return function (state: State, action: any): State { + const result = reducer(state, action); + console.groupCollapsed(action.type); + console.log('prev state', state); + console.log('action', action); + console.log('next state', result); + console.groupEnd(); + + return result; + }; +} + +export const META_REDUCERS: MetaReducer[] = !environment.production + ? [logger] + : []; + +export interface State { + router: fromRouter.RouterReducerState; + layout: AppLayoutStore.State; + events: AppEventsStore.State; +} + +export const AppRouterSelector = AppRouterStore.getSelectors( + (state: State) => state.router, +); + +export const AppLayoutSelector = AppLayoutStore.getSelectors( + (state: State) => state.layout, +); + +export const AppEventsSelector = AppEventsStore.getSelectors( + (state: State) => state.events, +); diff --git a/src/app/store/layout/app-layout.action.ts b/src/app/store/layout/app-layout.action.ts new file mode 100755 index 0000000..06d08a5 --- /dev/null +++ b/src/app/store/layout/app-layout.action.ts @@ -0,0 +1,16 @@ +import { Action } from '@ngrx/store'; +import { Environment } from '../../type/environment.type'; + +export enum ActionType { + ChangeEnvironment = '[app.layout] ChangeEnvironment', +} + +export class ChangeEnvironment implements Action { + readonly type = ActionType.ChangeEnvironment; + + constructor(public payload: { environment: Environment }) { } +} + +export type Actions = + | ChangeEnvironment + ; diff --git a/src/app/store/layout/app-layout.reducer.ts b/src/app/store/layout/app-layout.reducer.ts new file mode 100755 index 0000000..3227d5c --- /dev/null +++ b/src/app/store/layout/app-layout.reducer.ts @@ -0,0 +1,23 @@ +import { + Actions, + ActionType, +} from './app-layout.action'; + +import { + State, + initialState, +} from './app-layout.state'; + +export function reducer(state: State = initialState, action: Actions): State { + switch (action.type) { + case ActionType.ChangeEnvironment: { + return { + environment: action.payload.environment, + }; + } + + default: { + return state; + } + } +} diff --git a/src/app/store/layout/app-layout.state.ts b/src/app/store/layout/app-layout.state.ts new file mode 100755 index 0000000..99c825d --- /dev/null +++ b/src/app/store/layout/app-layout.state.ts @@ -0,0 +1,16 @@ +import { Selector, createSelector } from '@ngrx/store'; +import { Environment } from '../../type/environment.type'; + +export interface State { + environment: Environment; +} + +export const initialState: State = { + environment: Environment.Mobile, +}; + +export function getSelectors(selector: Selector) { + return { + selectEnvironment: createSelector(selector, (state: State) => state.environment), + }; +} diff --git a/src/app/store/layout/index.ts b/src/app/store/layout/index.ts new file mode 100755 index 0000000..550a232 --- /dev/null +++ b/src/app/store/layout/index.ts @@ -0,0 +1,3 @@ +export * from './app-layout.action'; +export * from './app-layout.reducer'; +export * from './app-layout.state'; diff --git a/src/app/store/login/app-login.action.ts b/src/app/store/login/app-login.action.ts new file mode 100755 index 0000000..4f08840 --- /dev/null +++ b/src/app/store/login/app-login.action.ts @@ -0,0 +1,39 @@ +import { Action } from '@ngrx/store'; + +export enum ActionType { + LoginRedirect = '[app.login] LoginRedirect', + Login = '[app.login] Login', + LoginToken = '[app.login] LoginToken', + LoginFailure = '[app.login] LoginFailure', +} + +export class LoginRedirect implements Action { + readonly type = ActionType.LoginRedirect; + + constructor(public payload: { returnURL: string }) { } +} + +export class Login implements Action { + readonly type = ActionType.Login; + + constructor(public payload: { returnURL: string }) { } +} + +export class LoginToken implements Action { + readonly type = ActionType.LoginToken; + + constructor(public payload: { token: string, returnURL: string }) { } +} + +export class LoginFailure implements Action { + readonly type = ActionType.LoginFailure; + + constructor(public payload: { err: Error }) { } +} + +export type Actions = + | Login + | LoginRedirect + | LoginToken + | LoginFailure + ; diff --git a/src/app/store/login/app-login.effect.ts b/src/app/store/login/app-login.effect.ts new file mode 100755 index 0000000..2a0e8a6 --- /dev/null +++ b/src/app/store/login/app-login.effect.ts @@ -0,0 +1,160 @@ +import { Injectable } from '@angular/core'; +import { Router } from '@angular/router'; +import { MatDialog } from '@angular/material'; + +import { Store, select } from '@ngrx/store'; +import { Effect, Actions, ofType } from '@ngrx/effects'; + +import { RouterReducerState } from '@ngrx/router-store'; + +import { of } from 'rxjs'; +import { map, tap, exhaustMap, catchError, withLatestFrom } from 'rxjs/operators'; + +import { CookieService } from 'ngx-cookie-service'; + +import { + LoginSuccess, + LoginRequired, + ActionType as AuthActionType +} from '../../../modules/accounts/store/auth'; + +import { + LoginRedirect, + LoginToken, + LoginFailure, + ActionType, + Login +} from './app-login.action'; + +import { AccountsService } from '../../../modules/accounts/service/accounts.service'; +import { User } from '../../../shared/user/model/user.model'; +import { UIUtil } from '../../../modules/common/util/ui/dialog.util'; +import { + ConfirmDialogComponent, + ConfirmDialogData, + ConfirmDialogResult, +} from '../../layout/dialog/confirm/confirm.dialog.component'; + +import { TranslateService } from '@ngx-translate/core'; + +@Injectable() +export class Effects { + constructor( + private actions$: Actions, + private store: Store, + private router: Router, + private cookieService: CookieService, + private accountsService: AccountsService, + private matDialog: MatDialog, + private tanslateService: TranslateService, + ) { } + + @Effect() + login$ = this.actions$.pipe( + ofType(ActionType.Login), + map((action: Login) => { + return action.payload; + }), + exhaustMap((loginInfo: { returnURL: string }) => { + return this.accountsService.login().pipe( + map((user: User) => { + return new LoginSuccess({ + user: user, + returnURL: (!user.favorShowedYN) ? '/user/favor' : loginInfo.returnURL + }); + }), + catchError(error => of(new LoginFailure({ err: error }))) + ); + }) + ); + + @Effect() + loginToken$ = this.actions$.pipe( + ofType(ActionType.LoginToken), + map((action: LoginToken) => { + const expires = new Date(); + expires.setDate(expires.getDate() + 1); + this.cookieService.set('jwt', action.payload.token, 0, '/'); + return action.payload; + }), + exhaustMap((loginInfo: { token: string; returnURL: string }) => { + return this.accountsService.login().pipe( + map((user: User) => { + if (user) { + this.tanslateService.use(user.displayLanguage.toLocaleLowerCase()); + } + return new LoginSuccess({ + user: user, + returnURL: (!user.favorShowedYN) ? '/user/favor' : loginInfo.returnURL + }); + }), + catchError(error => of(new LoginFailure({ err: error }))) + ); + }) + ); + + @Effect({ dispatch: false }) + loginTokenSuccess$ = this.actions$.pipe( + ofType(AuthActionType.LoginSuccess), + map((action: LoginSuccess) => action.payload), + tap((info: { user: User; returnURL: string }) => { + if (info.returnURL) { + this.router.navigateByUrl(info.returnURL); + } + }) + ); + + @Effect({ dispatch: false }) + loginRedirect$ = this.actions$.pipe( + ofType(ActionType.LoginRedirect), + map((action: LoginRedirect) => action.payload), + tap((info: { returnURL: string }) => { + this.router.navigate(['/accounts/authentication'], { + queryParams: { returnURL: info.returnURL } + }); + }) + ); + + @Effect({ dispatch: false }) + loginFailure$ = this.actions$.pipe( + ofType(ActionType.LoginFailure), + map((action: LoginFailure) => { + this.cookieService.delete('jwt', '/'); + + return action.payload; + }), + tap((info: { err: Error }) => { + console.log(info.err); + + this.router.navigate(['/accounts/authentication'], { + queryParams: { returnURL: '/' } + }); + }) + ); + + @Effect({ dispatch: false }) + loginRequired$ = this.actions$.pipe( + ofType(AuthActionType.LoginRequired), + withLatestFrom(this.store.pipe(select((state) => state.router))), + tap(async (actionAndStoreState) => { + const url = actionAndStoreState[1].state.url; + const result = await UIUtil.dialogOpen( + this.matDialog, ConfirmDialogComponent, + { + width: '220px', + data: { + title: 'Login required', + message: 'Sign in?' + } + } + ); + if (!result.choice) { + return; + } + this.matDialog.closeAll(); + this.router.navigate(['/accounts/authentication'], { + queryParams: { returnURL: url } + }); + }) + ); +} diff --git a/src/app/store/login/index.ts b/src/app/store/login/index.ts new file mode 100755 index 0000000..d5b5333 --- /dev/null +++ b/src/app/store/login/index.ts @@ -0,0 +1,2 @@ +export * from './app-login.action'; +export * from './app-login.effect'; diff --git a/src/app/store/nickname/app-nickname.action.ts b/src/app/store/nickname/app-nickname.action.ts new file mode 100755 index 0000000..a4e5221 --- /dev/null +++ b/src/app/store/nickname/app-nickname.action.ts @@ -0,0 +1,15 @@ +import { Action } from '@ngrx/store'; + +export enum ActionType { + EditRedirect = '[app.nickname] EditRedirect', +} + +export class EditRedirect implements Action { + readonly type = ActionType.EditRedirect; + + constructor(public payload: { returnURL: string }) { } +} + +export type Actions = + | EditRedirect + ; diff --git a/src/app/store/nickname/app-nickname.effect.ts b/src/app/store/nickname/app-nickname.effect.ts new file mode 100755 index 0000000..b479cd6 --- /dev/null +++ b/src/app/store/nickname/app-nickname.effect.ts @@ -0,0 +1,30 @@ +import { Injectable } from '@angular/core'; +import { Router } from '@angular/router'; + +import { Effect, Actions, ofType } from '@ngrx/effects'; +import { map, tap, } from 'rxjs/operators'; + +import { + EditRedirect, + ActionType, +} from './app-nickname.action'; + +@Injectable() +export class Effects { + constructor( + private actions$: Actions, + private router: Router, + ) { } + + @Effect({ dispatch: false }) + editRedirect$ = this.actions$.pipe( + ofType(ActionType.EditRedirect), + map((action: EditRedirect) => action.payload), + tap((info: { returnURL: string }) => { + this.router.navigate(['/user/edit'], { + queryParams: { returnURL: info.returnURL } + }); + }), + ); + +} diff --git a/src/app/store/nickname/index.ts b/src/app/store/nickname/index.ts new file mode 100755 index 0000000..04ef26a --- /dev/null +++ b/src/app/store/nickname/index.ts @@ -0,0 +1,2 @@ +export * from './app-nickname.action'; +export * from './app-nickname.effect'; diff --git a/src/app/store/router/app-router.effect.ts b/src/app/store/router/app-router.effect.ts new file mode 100755 index 0000000..22f4602 --- /dev/null +++ b/src/app/store/router/app-router.effect.ts @@ -0,0 +1,60 @@ +import { Injectable } from '@angular/core'; +import { MatSnackBar, MatDialog } from '@angular/material'; +import { Location } from '@angular/common'; +import { Router } from '@angular/router'; + +import { Effect, Actions, ofType } from '@ngrx/effects'; +import { map, tap } from 'rxjs/operators'; + +import * as UserProfileStore from '../../../modules/user/store/profile'; +import { User } from '../../../shared/user/model/user.model'; +import * as ArticleRouterStore from '../../../modules/article/store/router'; +import { Article } from '../../../shared/article/model/article.model'; + +@Injectable() +export class Effects { + constructor( + private actions$: Actions, + private router: Router, + private location: Location, + private matSnackBar: MatSnackBar, + private matDialog: MatDialog, + ) { } + + @Effect({ dispatch: false }) + userProfileGotoProfile$ = this.actions$.pipe( + ofType(UserProfileStore.ActionType.GotoProfile), + map((action: UserProfileStore.GotoProfile) => action.payload), + tap((payload: { uid: string }) => { + this.matDialog.closeAll(); + this.router.navigateByUrl(`/article/${payload.uid}`); + }), + ); + + @Effect({ dispatch: false }) + userProfileGotoProfileEdit$ = this.actions$.pipe( + ofType(UserProfileStore.ActionType.GotoProfileEdit), + tap(() => { + this.router.navigateByUrl(`/user/edit`); + }), + ); + + @Effect({ dispatch: false }) + userProfileGotoSettings$ = this.actions$.pipe( + ofType(UserProfileStore.ActionType.GotoSettings), + tap(() => { + this.router.navigateByUrl(`/user/settings`); + }), + ); + + @Effect({ dispatch: false }) + articleRouterGotoSave$ = this.actions$.pipe( + ofType(ArticleRouterStore.ActionType.GotoSave), + map((action: ArticleRouterStore.GotoSave) => action.payload), + tap((payload: { aid: string }) => { + this.matDialog.closeAll(); + this.router.navigateByUrl(`/article/write/${payload.aid}`); + }), + ); + +} diff --git a/src/app/store/router/app-router.state.ts b/src/app/store/router/app-router.state.ts new file mode 100755 index 0000000..875310b --- /dev/null +++ b/src/app/store/router/app-router.state.ts @@ -0,0 +1,10 @@ +import { Selector, createSelector } from '@ngrx/store'; +import * as fromRouter from '@ngrx/router-store'; + +export type State = fromRouter.RouterReducerState; + +export function getSelectors(selector: Selector) { + return { + selectState: createSelector(selector, (state: State) => state.state), + }; +} diff --git a/src/app/store/router/index.ts b/src/app/store/router/index.ts new file mode 100755 index 0000000..eb538a5 --- /dev/null +++ b/src/app/store/router/index.ts @@ -0,0 +1,2 @@ +export * from './app-router.effect'; +export * from './app-router.state'; diff --git a/src/app/type/environment.type.ts b/src/app/type/environment.type.ts new file mode 100755 index 0000000..cd56438 --- /dev/null +++ b/src/app/type/environment.type.ts @@ -0,0 +1,14 @@ +/** + * 파 일 명: environment.type.ts + * 작성일자: 2018-12-24 + * 작 성 자: 박병준 + * 설 명: Article의 이미지 유형을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +export enum Environment { + Mobile = 'Mobile', + Desktop = 'Desktop', +} diff --git a/src/assets/.gitkeep b/src/assets/.gitkeep new file mode 100755 index 0000000..e69de29 diff --git a/src/assets/_app-theme.scss b/src/assets/_app-theme.scss new file mode 100755 index 0000000..165c062 --- /dev/null +++ b/src/assets/_app-theme.scss @@ -0,0 +1,39 @@ +@import '../../node_modules/@angular/material/theming'; + +@import './styles/api-theme'; +@import './styles/markdown-theme'; +@import './styles/svg-theme'; +@import './styles/tables-theme'; + + +// Styles for the docs app that are based on the current theme. +@mixin app-root-theme($theme) { + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + $next-theme: mat-palette($mat-red); + + .docs-app-background { + background: mat-color($background, background); + } + + .docs-primary-header { + background: mat-color($primary); + + h1 { + color: mat-color($primary, default-contrast); + } + } + + .docs-footer { + background: mat-color($primary); + color: mat-color($primary, default-contrast); + } + + .is-next-version { + background: mat-color($next-theme, 900) !important; + } + +} diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json new file mode 100755 index 0000000..d94c0b9 --- /dev/null +++ b/src/assets/i18n/en.json @@ -0,0 +1,241 @@ +{ + "common": { + "toHome": "홈으로", + "search": "Search", + "bookmarkList": "북마크 목록", + "myPage": "My Page", + "recommendCreator": "작가 추천", + "register": "Register", + "complete": "Complete", + "edit": "Edit", + "delete": "Delete", + "cancel": "Cancel", + "report": "Report", + "objection": "이의 신청", + "unfollow": "Unfollow", + "msgWantToUnfallow": "님의 팔로우를 취소하시겠어요?", + "msgReportingProfile": "이 프로필을 신고하는 이유를 선택하세요.", + "msgReportingArticle": "이 작품를 신고하는 이유를 선택하세요.", + "msgCopyrightViolation": "저작권 위반입니다.", + "msgSpam": "it is spam.", + "msgInappropriate": "It is inappropriate.", + "duplicationCheck": "중복검사", + "add": "add", + "share": "share", + "msgShareWith": "{{service}}에 공유", + "sendByEmail": "이메일로 공유", + "copyLink": "링크 복사", + "confirm": "Confirm", + "save": "Save", + "saveConfirmMsg": "Are you sure save?", + "resultSuccess": "Success", + "saveSuccessMsg": "Saving succeeded.", + "resultFail": "Fail", + "saveFailMsg": "Saving failed.", + "cancelConfirmMsg": "Are you sure?", + "cancelConfirmDetailMsg": "You will lose your changes if you go." + }, + "main": { + "msgWorksRecommend": "What do you think of this kind of work?", + "msgCoCoRecommend": "Hidden treasure recommended by CoCo" + }, + "login": { + "companyName": "회사명", + "msgWelcome": "에 오신 것을 환영합니다.", + "login": "Login", + "naver": "Naver", + "line": "Line", + "facebook": "Facebook", + "twitter": "Twitter", + "instagram": "Instagram", + "google": "Google", + "googlePlus": "Google Plus", + "weibo": "Weibo", + "kakao": "Kakao", + "requireLoginMsg": "Login required", + "askSignin": "Sign in?", + "signinTroubleMsg": "Do you have trouble signing in?" + }, + "favorite": { + "msgFirstTime1": "응! 어서와,", + "msgFirstTime2": "'{{name}}'는 처음이지?", + "msgFirstTime3": "COCO", + "msgSelectMyFavorites": "Please choose my favorite webtoon.", + "tag1": "드라마", + "tag2": "연애/사랑", + "tag3": "BL", + "tag4": "백합/GL", + "tag5": "배틀/액션", + "tag6": "모험/판타지", + "tag7": "일상", + "tag8": "개그/코미디", + "tag9": "호러/미스터리", + "tag10": "역사/시대극", + "tag11": "스포츠", + "tag12": "미식/술", + "tag13": "심쿵", + "tag14": "SF", + "tag15": "도박", + "tag16": "아이돌", + "tag17": "비즈니스", + "tag18": "동물", + "tag19": "이세계", + "tag20": "세기말/종말" + }, + "search": { + "searchTag": "Search tag", + "searchCreatorName": "Search Author", + "msgNotFound": "No search results" + }, + "article": { + "follow": "Follow", + "commentsCount": "Comments", + "likeCount": "Likes", + "viewCount": "Views", + "sharing": "Share", + "writeComment": "댓글 달기", + "bookmark": "Bookmark", + "like": "Like", + "registRelatedArticle": "관련 작품 등록", + "msgDetailAgeLimit": "Details of the age limit.", + "uploadFiles": "Upload Files", + "msgCreateGIF": "Create a moving GIF.", + "cartoonsSeries": "Cartoons Series", + "cartoonsSeriesSaveSuccessMsg": "Creating of Cartoons Series has been succeed.", + "cartoonsSeriesSaveFailMsg": "Creating of Cartoons Series has been failed.", + "cartoonsSeriesUpdateSuccessMsg": "Updating of Cartoons Series has been succeed.", + "cartoonsSeriesUpdateFailMsg": "Updating of Cartoons Series has been failed.", + "deletePictureMsg": "Delete this picture!", + "duplicateTitleMsg": "Please enter a different title.", + "addFile": "파일 선택" + }, + "event": { + "ongoingEvent": "진행중인 이벤트", + "recruitment": "모집중", + "soon": "곧 마감!", + "msgUntil": "{{마감일}} 까지" + }, + "myPage": { + "article": "Article", + "follower": "Follower", + "follow": "Follow", + "following": "Following", + "shareProfile": "Share profile", + "copyProfileLink": "Copy profile link", + "editProfile": "Edit profile", + "userName": "User name", + "msgUserNameAlreadyRegistered": "User name already registered.", + "msgUserNameCanBeRegistered": "This is the user name that can be registered.", + "introduction": "Introduction", + "emailAddress": "Email Address", + "representativeArticle": "대표 작품", + "registerArticle": "작품 등록", + "illust": "일러스트", + "comic": "만화", + "novel": "소설", + "text": "본문", + "isOriginalArticle": "오리지널 작품 여부", + "msgOriginalArticleInfo": "시나리오, 등장인물, 설정 등을 포함하여 모두 스스로 창작한 작품(1차 창작)의 경우 선택해주세요.", + "msgRegistOriginalArticleTag": "오리지널 작품을 표현할 수 있는 나만의 Tag를 등록해 주세요.", + "msgTagAlreadyRegistered": "이미 등록된 Tag 입니다.", + "msgTagCanBeRegistered": "등록 할 수 있는 Tag 입니다.", + "ageRestriction": "Age limit", + "msgAgeRestrictionInfo": "연령제한에 관한 상세 내용은 여기를 참조하세요.", + "allAges": "All ages", + "expressMildSexual": "가벼운 성적 묘사", + "expressViolenceAndCrotes": "폭력, 잔인, 크로테스 묘사", + "basicInfo": "기본 정보", + "title": "Title", + "description": "Description", + "series": "Series", + "addSeries": "Add series", + "seriesInfo": "Series Info", + "episode": "화" + }, + "config": { + "settings": "Settings", + "accountSettings": "Account settings", + "helpNGuid": "Help/Guidance", + "myFavorite": "My taste", + "ageLimit": "Age limit", + "changeLanguage": "표시 언어 변경", + "korean": "Korean", + "english": "English", + "japanese": "Japanese", + "setPageVisibility": "페이지 공개 여부", + "laboratory": "Laboratory", + "help": "Help", + "reportDamage": "Report damage", + "logout": "Logout", + "legalNotices": "Legal Notices", + "terms": "Terms", + "privacyPolicy": "Privacy Policy" + }, + "report": { + "reportArticle": "Report article", + "reportContent": "Report content", + "copyrightViolation": "저작권 위반", + "inappropriate": "Inappropriate", + "dateReport": "Report date", + "status": "Status", + "receiptReport": "신고 접수", + "completeBlind": "블라인드 완료", + "commandPalette": "명령 팔레트", + "lift": "해제", + "dismissal": "이의 기각" + }, + "comments": { + "tag": { + "TAG_1": "안타까워요", + "TAG_2": "귀여워", + "TAG_3": "심쿵", + "TAG_4": "멋있어요", + "TAG_5": "소름돋아요", + "TAG_6": "개꿀잼이에요", + "TAG_7": "여자여자하네요", + "TAG_8": "충격적이네요", + "TAG_9": "최고야", + "TAG_10": "이뻐요", + "TAG_11": "좋겠다", + "TAG_12": "불상해요", + "TAG_13": "참신하네요", + "TAG_14": "부럽다", + "TAG_15": "풉", + "TAG_16": "ㅠㅠ", + "TAG_17": "다행이다", + "TAG_18": "무섭네", + "TAG_19": "실화냐 ?", + "TAG_20": "너무 슬퍼요", + "TAG_21": "잘 생겼다", + "TAG_22": "당황하셨어요 ?", + "TAG_23": "헐퀴", + "TAG_24": "대박인데", + "TAG_25": "눈에띄네", + "TAG_26": "좋아요", + "TAG_27": "축하드려요", + "TAG_28": "화이팅하세요", + "TAG_29": "개궁금하다", + "TAG_30": "응원할께요", + "TAG_31": "기대됩니다", + "TAG_32": "성공하세요", + "TAG_33": "잘 보고 갑니다", + "TAG_34": "감사합니다.", + "TAG_35": "작가님 믿을께요", + "TAG_36": "다시봤음", + "TAG_37": "꽃길만 걸어라", + "TAG_38": "어려워요", + "TAG_39": "하트 눌렀어요", + "TAG_40": "북마크 필수", + "TAG_41": "ㅋㅋㅋ", + "TAG_42": "대단합니다", + "TAG_43": "사랑해요", + "TAG_44": "쵝오", + "TAG_45": "10 점만점에 10 점", + "TAG_46": "작가님 천재", + "TAG_47": "침착해 침착해", + "TAG_48": "새해 복 많이 받으세요", + "TAG_49": "메리 크리스마스", + "TAG_50": "생일축하합니다" + } + } +} diff --git a/src/assets/i18n/jp.json b/src/assets/i18n/jp.json new file mode 100755 index 0000000..03e1eae --- /dev/null +++ b/src/assets/i18n/jp.json @@ -0,0 +1,241 @@ +{ + "common": { + "toHome": "ホーム", + "search": "検索", + "bookmarkList": "お気に入り", + "myPage": "マイページ", + "recommendCreator": "おすすめ作家", + "register": "登録", + "complete": "完了", + "edit": "修正", + "delete": "削除", + "cancel": "キャンセル", + "report": "申告", + "objection": "異議申請", + "unfollow": "フォローを外す", + "msgWantToUnfallow": "様のフォローを取消しますか?", + "msgReportingProfile": "このユーザを", + "msgReportingArticle": "この作品を", + "msgCopyrightViolation": "著作権に違反しています", + "msgSpam": "スパムです。", + "msgInappropriate": "不適切ます。", + "duplicationCheck": "重複検査", + "add": "追加", + "share": "シェア", + "msgShareWith": "{{service}}へシェア", + "sendByEmail": "メールでシェア", + "copyLink": "リンクコピー", + "confirm": "確認", + "save": "貯蔵", + "saveConfirmMsg": "保存しますか?", + "resultSuccess": "成功", + "saveSuccessMsg": "保存に成功しました。", + "resultFail": "失敗", + "saveFailMsg": "保存に失敗しました。", + "cancelConfirmMsg": "キャンセルしますか?", + "cancelConfirmDetailMsg": "入力中の内容が失われます。" + }, + "main": { + "msgWorksRecommend": "こんな作品どうですか。", + "msgCoCoRecommend": "CoCoのおすすめは隠された宝物" + }, + "login": { + "companyName": "会社名", + "msgWelcome": "へようこそ!", + "login": "ログイン", + "naver": "NAVER", + "line": "LINE", + "facebook": "Facebook", + "twitter": "Twitter", + "instagram": "Instagram", + "google": "Google", + "googlePlus": "Google Plus", + "weibo": "微博", + "kakao": "Kakao", + "requireLoginMsg": "ログインが必要です", + "askSignin": "ログイン?", + "signinTroubleMsg": "ログインに問題がありますか?" + }, + "favorite": { + "msgFirstTime1": "ようこそ!", + "msgFirstTime2": "'{{name}}'は初めてでしょう?", + "msgFirstTime3": "COCO", + "msgSelectMyFavorites": "私の好みであるウェプトゥンを選択してください。", + "tag1": "ドラマ", + "tag2": "恋愛", + "tag3": "BL", + "tag4": "GL・百合", + "tag5": "バトル・アクション", + "tag6": "冒険・ファンタジー", + "tag7": "日常", + "tag8": "ギャグ・コメディ", + "tag9": "ホラー・ミステリー", + "tag10": "歴史・戦記", + "tag11": "スポーツ", + "tag12": "グルメ・酒", + "tag13": "胸キュン", + "tag14": "SF", + "tag15": "ギャンブル", + "tag16": "アイドル", + "tag17": "お仕事", + "tag18": "動物", + "tag19": "異世界", + "tag20": "終末" + }, + "search": { + "searchTag": "タグ検索", + "searchCreatorName": "作家名検索", + "msgNotFound": "検索結果なし" + }, + "article": { + "follow": "フォロー", + "commentsCount": "コメントカウント", + "likeCount": "いいねカウント", + "viewCount": "閲覧カウント", + "sharing": "シェア", + "writeComment": "コメントする", + "bookmark": "お気に入り", + "like": "いいね", + "registRelatedArticle": "関連作品登録", + "msgDetailAgeLimit": "年齢制限に関する詳細です。", + "uploadFiles": "ファイルアップロード", + "msgCreateGIF": "動くGIFを生成します。", + "cartoonsSeries": "漫画シリーズ", + "cartoonsSeriesSaveSuccessMsg": "漫画シリーズの作成を成功しました。", + "cartoonsSeriesSaveFailMsg": "漫画シリーズの作成に失敗しました。", + "cartoonsSeriesUpdateSuccessMsg": "漫画シリーズの修正を成功しました。", + "cartoonsSeriesUpdateFailMsg": "漫画シリーズの修正に失敗しました。", + "deletePictureMsg": "この画像を削除します!", + "duplicateTitleMsg": "別のタイトルを入力してください。", + "addFile": "ファイルを選択" + }, + "event": { + "ongoingEvent": "やっているイベント", + "recruitment": "募集中", + "soon": "まもなく終了!", + "msgUntil": "{{締切}}まで" + }, + "myPage": { + "article": "掲示", + "follower": "フォロワー", + "follow": "フォローする", + "following": "フォロイング", + "shareProfile": "プロフィールシェア", + "copyProfileLink": "プロフィールコピー", + "editProfile": "プロフィール修正", + "userName": "ユーザー名", + "msgUserNameAlreadyRegistered": "既に登録されているユーザー名です。", + "msgUserNameCanBeRegistered": "登録できるユーザー名です。", + "introduction": "自己紹介", + "emailAddress": "メールアドレス", + "representativeArticle": "代表作品", + "registerArticle": "作品投稿", + "illust": "イラスト", + "comic": "マンガ", + "novel": "小説", + "text": "本文", + "isOriginalArticle": "オリジナル作品", + "msgOriginalArticleInfo": "シナリオ・登録人物・設定を含め、全てが自身の創作である作品(1次創作)の場合は選択してください。", + "msgRegistOriginalArticleTag": "オリジナル作品を表現するユニックなタグを登録してください。", + "msgTagAlreadyRegistered": "既に登録されているタグです。", + "msgTagCanBeRegistered": "登録できるタグです。", + "ageRestriction": "閲覧制限", + "msgAgeRestrictionInfo": "年齢制限についての詳細はここを参照してください。", + "allAges": "全年齢", + "expressMildSexual": "軽度な性的な描写", + "expressViolenceAndCrotes": "軽度な暴力、", + "basicInfo": "基本情報", + "title": "タイトル", + "description": "あらすじ", + "series": "シリーズ", + "addSeries": "シリーズを追加", + "seriesInfo": "シリーズ情報", + "episode": "話" + }, + "config": { + "settings": "設定", + "accountSettings": "勘定設定", + "helpNGuid": "助言/案内", + "myFavorite": "マイスタイル", + "ageLimit": "閲覧制限", + "changeLanguage": "使用する言語", + "korean": "韓国語", + "english": "英語", + "japanese": "日本語", + "setPageVisibility": "ページ公開", + "laboratory": "研究所", + "help": "ヘルプ", + "reportDamage": "被害申告", + "logout": "ログアウト", + "legalNotices": "ガイドライン", + "terms": "利用約款", + "privacyPolicy": "プライバシーポリシー" + }, + "report": { + "reportArticle": "報告された作品", + "reportContent": "報告内容", + "copyrightViolation": "著作権違反", + "inappropriate": "不適切な表現", + "dateReport": "報告日時", + "status": "ステータス", + "receiptReport": "報告受付", + "completeBlind": "ブラインド完了", + "commandPalette": "機能パレート", + "lift": "解除", + "dismissal": "異議棄却" + }, + "comments": { + "tag": { + "TAG_1": "残念ですね", + "TAG_2": "可愛い", + "TAG_3": "胸キュン", + "TAG_4": "かっこい", + "TAG_5": "鳥肌立つ", + "TAG_6": "面白い", + "TAG_7": "美しいね", + "TAG_8": "これは衝撃", + "TAG_9": "最高だぜ", + "TAG_10": "キレイ", + "TAG_11": "いいな", + "TAG_12": "かわいそう", + "TAG_13": "新鮮だね", + "TAG_14": "うらやましい", + "TAG_15": "(笑)", + "TAG_16": "(涙)", + "TAG_17": "よかった", + "TAG_18": "怖い", + "TAG_19": "本当?", + "TAG_20": "悲しい", + "TAG_21": "ハンサム", + "TAG_22": "慌てたよね", + "TAG_23": "え!?", + "TAG_24": "すご~ い", + "TAG_25": "目に立つよね", + "TAG_26": "好きです", + "TAG_27": "おめでとう", + "TAG_28": "ファイト!", + "TAG_29": "気になるな", + "TAG_30": "応援します", + "TAG_31": "期待してます", + "TAG_32": "成功してね", + "TAG_33": "よく見ました", + "TAG_34": "ありがとう", + "TAG_35": "信じています", + "TAG_36": "見直しました", + "TAG_37": "花道だけ歩いて", + "TAG_38": "難しい", + "TAG_39": "ハートしました", + "TAG_40": "お気に入り", + "TAG_41": "ククク", + "TAG_42": "すごい", + "TAG_43": "愛してます", + "TAG_44": "最高!", + "TAG_45": "これは100点", + "TAG_46": "あなたは天才", + "TAG_47": "大丈夫だよ", + "TAG_48": "明けましておめでとうございます", + "TAG_49": "メリークリスマス", + "TAG_50": "お誕生日おめでとう" + } + } +} diff --git a/src/assets/i18n/ko.json b/src/assets/i18n/ko.json new file mode 100755 index 0000000..30dbce0 --- /dev/null +++ b/src/assets/i18n/ko.json @@ -0,0 +1,241 @@ +{ + "common": { + "toHome": "홈으로", + "search": "검색", + "bookmarkList": "북마크 목록", + "myPage": "마이 페이지", + "recommendCreator": "작가 추천", + "register": "등록", + "complete": "완료", + "edit": "수정", + "delete": "삭제", + "cancel": "취소", + "report": "신고", + "objection": "이의 신청", + "unfollow": "언팔로우", + "msgWantToUnfallow": "님의 팔로우를
취소하시겠어요?", + "msgReportingProfile": "이 프로필을 신고하는 이유를 선택하세요.", + "msgReportingArticle": "이 작품를 신고하는 이유를 선택하세요.", + "msgCopyrightViolation": "저작권 위반입니다.", + "msgSpam": "스팸입니다.", + "msgInappropriate": "부적절합니다.", + "duplicationCheck": "중복검사", + "add": "추가", + "share": "공유", + "msgShareWith": "{{service}}에 공유", + "sendByEmail": "이메일로 공유", + "copyLink": "링크 복사", + "confirm": "확인", + "save": "저장", + "saveConfirmMsg": "저장 하시겠습니까?", + "resultSuccess": "성공", + "saveSuccessMsg": "저장 성공했습니다.", + "resultFail": "실패", + "saveFailMsg": "저장 실패했습니다.", + "cancelConfirmMsg": "취소하시겠습니까?", + "cancelConfirmDetailMsg": "입력중인 내용이 없어집니다." + }, + "main": { + "msgWorksRecommend": "이런 작품 어때요?", + "msgCoCoRecommend": "CoCo가 추천드리는 숨겨진 보물" + }, + "login": { + "companyName": "회사명", + "msgWelcome": "에 오신 것을 환영합니다.", + "login": "로그인", + "naver": "네이버", + "line": "라인", + "facebook": "페이스북", + "twitter": "트위터", + "instagram": "인스타그램", + "google": "구글", + "googlePlus": "구글플러스", + "weibo": "웨이보", + "kakao": "카카오", + "requireLoginMsg": "로그인 필요", + "askSignin": "로그인?", + "signinTroubleMsg": "로그인에 문제가 있으신가요?" + }, + "favorite": { + "msgFirstTime1": "응! 어서와,", + "msgFirstTime2": "'{{name}}'는 처음이지?", + "msgFirstTime3": "코코", + "msgSelectMyFavorites": "내 취향인 웹툰을 선택해주세요.", + "tag1": "드라마", + "tag2": "연애/사랑", + "tag3": "BL", + "tag4": "백합/GL", + "tag5": "배틀/액션", + "tag6": "모험/판타지", + "tag7": "일상", + "tag8": "개그/코미디", + "tag9": "호러/미스터리", + "tag10": "역사/시대극", + "tag11": "스포츠", + "tag12": "미식/술", + "tag13": "심쿵", + "tag14": "SF", + "tag15": "도박", + "tag16": "아이돌", + "tag17": "비즈니스", + "tag18": "동물", + "tag19": "이세계", + "tag20": "세기말/종말" + }, + "search": { + "searchTag": "태그 검색", + "searchCreatorName": "작가명 검색", + "msgNotFound": "검색결과 없음" + }, + "article": { + "follow": "팔로우", + "commentsCount": "댓글 수", + "likeCount": "좋아요 수", + "viewCount": "뷰 수", + "sharing": "공유하기", + "writeComment": "댓글 달기", + "bookmark": "북마크", + "like": "좋아요", + "registRelatedArticle": "관련 작품 등록", + "msgDetailAgeLimit": "연령제한에 관한 상세 내용입니다.", + "uploadFiles": "파일 업로드", + "msgCreateGIF": "움직이는 GIF를 생성합니다.", + "cartoonsSeries": "만화 시리즈", + "cartoonsSeriesSaveSuccessMsg": "만화 시리즈 생성을 성공했습니다.", + "cartoonsSeriesSaveFailMsg": "만화 시리즈 생성을 실패했습니다.", + "cartoonsSeriesUpdateSuccessMsg": "만화 시리즈 수정을 성공했습니다.", + "cartoonsSeriesUpdateFailMsg": "만화 시리즈 수정을 실패했습니다.", + "deletePictureMsg": "이 그림을 삭제합니다!", + "duplicateTitleMsg": "다른 제목을 입력하십시오.", + "addFile": "파일 선택" + }, + "event": { + "ongoingEvent": "진행중인 이벤트", + "recruitment": "모집중", + "soon": "곧 마감!", + "msgUntil": "{{마감일}} 까지" + }, + "myPage": { + "article": "게시물", + "follower": "팔로워", + "follow": "팔로우", + "following": "팔로잉", + "shareProfile": "프로필 공유하기", + "copyProfileLink": "프로필 링크 복사", + "editProfile": "프로필 수정", + "userName": "사용자 이름", + "msgUserNameAlreadyRegistered": "이미 등록된 사용자 이름입니다.", + "msgUserNameCanBeRegistered": "등록 할 수 있는 사용자 이름입니다.", + "introduction": "자기소개", + "emailAddress": "메일 주소", + "representativeArticle": "대표 작품", + "registerArticle": "작품 등록", + "illust": "일러스트", + "comic": "만화", + "novel": "소설", + "text": "본문", + "isOriginalArticle": "오리지널 작품 여부", + "msgOriginalArticleInfo": "시나리오, 등장인물, 설정 등을 포함하여 모두 스스로 창작한 작품(1차 창작)의 경우 선택해주세요.", + "msgRegistOriginalArticleTag": "오리지널 작품을 표현할 수 있는 나만의 Tag를 등록해 주세요.", + "msgTagAlreadyRegistered": "이미 등록된 Tag 입니다.", + "msgTagCanBeRegistered": "등록 할 수 있는 Tag 입니다.", + "ageRestriction": "연령제한", + "msgAgeRestrictionInfo": "연령제한에 관한 상세 내용은 여기를 참조하세요.", + "allAges": "전연령", + "expressMildSexual": "가벼운 성적 묘사", + "expressViolenceAndCrotes": "폭력, 잔인, 크로테스 묘사", + "basicInfo": "기본 정보", + "title": "타이틀", + "description": "설명", + "series": "시리즈", + "addSeries": "시리즈 추가", + "seriesInfo": "시리즈 정보", + "episode": "화" + }, + "config": { + "settings": "설정", + "accountSettings": "계정 설정", + "helpNGuid": "도움말/안내", + "myFavorite": "나의 취향", + "ageLimit": "연령제한", + "changeLanguage": "표시 언어 변경", + "korean": "한국어", + "english": "영어", + "japanese": "일본어", + "setPageVisibility": "페이지 공개 여부", + "laboratory": "실험실", + "help": "도와줘요", + "reportDamage": "피해 신고", + "logout": "로그아웃", + "legalNotices": "법적 고지", + "terms": "약관", + "privacyPolicy": "개인정보보호방침" + }, + "report": { + "reportArticle": "신고 작품", + "reportContent": "신고 내용", + "copyrightViolation": "저작권 위반", + "inappropriate": "부적절", + "dateReport": "신고 일시", + "status": "스테이터스", + "receiptReport": "신고 접수", + "completeBlind": "블라인드 완료", + "commandPalette": "명령 팔레트", + "lift": "해제", + "dismissal": "이의 기각" + }, + "comments": { + "tag": { + "TAG_1": "안타까워요", + "TAG_2": "귀여워", + "TAG_3": "심쿵", + "TAG_4": "멋있어요", + "TAG_5": "소름돋아요", + "TAG_6": "개꿀잼이에요", + "TAG_7": "여자여자하네요", + "TAG_8": "충격적이네요", + "TAG_9": "최고야", + "TAG_10": "이뻐요", + "TAG_11": "좋겠다", + "TAG_12": "불상해요", + "TAG_13": "참신하네요", + "TAG_14": "부럽다", + "TAG_15": "풉", + "TAG_16": "ㅠㅠ", + "TAG_17": "다행이다", + "TAG_18": "무섭네", + "TAG_19": "실화냐 ?", + "TAG_20": "너무 슬퍼요", + "TAG_21": "잘 생겼다", + "TAG_22": "당황하셨어요 ?", + "TAG_23": "헐퀴", + "TAG_24": "대박인데", + "TAG_25": "눈에띄네", + "TAG_26": "좋아요", + "TAG_27": "축하드려요", + "TAG_28": "화이팅하세요", + "TAG_29": "개궁금하다", + "TAG_30": "응원할께요", + "TAG_31": "기대됩니다", + "TAG_32": "성공하세요", + "TAG_33": "잘 보고 갑니다", + "TAG_34": "감사합니다.", + "TAG_35": "작가님 믿을께요", + "TAG_36": "다시봤음", + "TAG_37": "꽃길만 걸어라", + "TAG_38": "어려워요", + "TAG_39": "하트 눌렀어요", + "TAG_40": "북마크 필수", + "TAG_41": "ㅋㅋㅋ", + "TAG_42": "대단합니다", + "TAG_43": "사랑해요", + "TAG_44": "쵝오", + "TAG_45": "10 점만점에 10 점", + "TAG_46": "작가님 천재", + "TAG_47": "침착해 침착해", + "TAG_48": "새해 복 많이 받으세요", + "TAG_49": "메리 크리스마스", + "TAG_50": "생일축하합니다" + } + } +} diff --git a/src/assets/img/article/S/1.jpg b/src/assets/img/article/S/1.jpg new file mode 100755 index 0000000..1da5ae5 Binary files /dev/null and b/src/assets/img/article/S/1.jpg differ diff --git a/src/assets/img/article/S/10.jpg b/src/assets/img/article/S/10.jpg new file mode 100755 index 0000000..c3368a0 Binary files /dev/null and b/src/assets/img/article/S/10.jpg differ diff --git a/src/assets/img/article/S/11.jpg b/src/assets/img/article/S/11.jpg new file mode 100755 index 0000000..3882a1b Binary files /dev/null and b/src/assets/img/article/S/11.jpg differ diff --git a/src/assets/img/article/S/12.jpg b/src/assets/img/article/S/12.jpg new file mode 100755 index 0000000..1e50c35 Binary files /dev/null and b/src/assets/img/article/S/12.jpg differ diff --git a/src/assets/img/article/S/13.jpg b/src/assets/img/article/S/13.jpg new file mode 100755 index 0000000..fbbd2a0 Binary files /dev/null and b/src/assets/img/article/S/13.jpg differ diff --git a/src/assets/img/article/S/14.jpg b/src/assets/img/article/S/14.jpg new file mode 100755 index 0000000..eed8a99 Binary files /dev/null and b/src/assets/img/article/S/14.jpg differ diff --git a/src/assets/img/article/S/15.jpg b/src/assets/img/article/S/15.jpg new file mode 100755 index 0000000..00c3bd2 Binary files /dev/null and b/src/assets/img/article/S/15.jpg differ diff --git a/src/assets/img/article/S/16.jpg b/src/assets/img/article/S/16.jpg new file mode 100755 index 0000000..9840ef6 Binary files /dev/null and b/src/assets/img/article/S/16.jpg differ diff --git a/src/assets/img/article/S/17.jpg b/src/assets/img/article/S/17.jpg new file mode 100755 index 0000000..44ddfd2 Binary files /dev/null and b/src/assets/img/article/S/17.jpg differ diff --git a/src/assets/img/article/S/18.jpg b/src/assets/img/article/S/18.jpg new file mode 100755 index 0000000..149ea36 Binary files /dev/null and b/src/assets/img/article/S/18.jpg differ diff --git a/src/assets/img/article/S/19.jpg b/src/assets/img/article/S/19.jpg new file mode 100755 index 0000000..0cca8c2 Binary files /dev/null and b/src/assets/img/article/S/19.jpg differ diff --git a/src/assets/img/article/S/2.jpg b/src/assets/img/article/S/2.jpg new file mode 100755 index 0000000..f2555c4 Binary files /dev/null and b/src/assets/img/article/S/2.jpg differ diff --git a/src/assets/img/article/S/20.jpg b/src/assets/img/article/S/20.jpg new file mode 100755 index 0000000..b688d41 Binary files /dev/null and b/src/assets/img/article/S/20.jpg differ diff --git a/src/assets/img/article/S/3.jpg b/src/assets/img/article/S/3.jpg new file mode 100755 index 0000000..91ecd1b Binary files /dev/null and b/src/assets/img/article/S/3.jpg differ diff --git a/src/assets/img/article/S/4.jpg b/src/assets/img/article/S/4.jpg new file mode 100755 index 0000000..d0a79d7 Binary files /dev/null and b/src/assets/img/article/S/4.jpg differ diff --git a/src/assets/img/article/S/5.jpg b/src/assets/img/article/S/5.jpg new file mode 100755 index 0000000..dec4d8b Binary files /dev/null and b/src/assets/img/article/S/5.jpg differ diff --git a/src/assets/img/article/S/6.jpg b/src/assets/img/article/S/6.jpg new file mode 100755 index 0000000..8cf32a5 Binary files /dev/null and b/src/assets/img/article/S/6.jpg differ diff --git a/src/assets/img/article/S/7.jpg b/src/assets/img/article/S/7.jpg new file mode 100755 index 0000000..7bfe712 Binary files /dev/null and b/src/assets/img/article/S/7.jpg differ diff --git a/src/assets/img/article/S/8.jpg b/src/assets/img/article/S/8.jpg new file mode 100755 index 0000000..154326d Binary files /dev/null and b/src/assets/img/article/S/8.jpg differ diff --git a/src/assets/img/article/S/9.jpg b/src/assets/img/article/S/9.jpg new file mode 100755 index 0000000..5082afc Binary files /dev/null and b/src/assets/img/article/S/9.jpg differ diff --git a/src/assets/img/background/cropper-background.png b/src/assets/img/background/cropper-background.png new file mode 100755 index 0000000..3c7056b Binary files /dev/null and b/src/assets/img/background/cropper-background.png differ diff --git a/src/assets/img/background/pound.svg b/src/assets/img/background/pound.svg new file mode 100755 index 0000000..8d034e6 --- /dev/null +++ b/src/assets/img/background/pound.svg @@ -0,0 +1 @@ + diff --git a/src/assets/img/component-categories/buttons.svg b/src/assets/img/component-categories/buttons.svg new file mode 100755 index 0000000..07d8704 --- /dev/null +++ b/src/assets/img/component-categories/buttons.svg @@ -0,0 +1 @@ +buttons-category+ diff --git a/src/assets/img/component-categories/forms.svg b/src/assets/img/component-categories/forms.svg new file mode 100755 index 0000000..8efd850 --- /dev/null +++ b/src/assets/img/component-categories/forms.svg @@ -0,0 +1 @@ + form-components diff --git a/src/assets/img/component-categories/layout.svg b/src/assets/img/component-categories/layout.svg new file mode 100755 index 0000000..3d15a2a --- /dev/null +++ b/src/assets/img/component-categories/layout.svg @@ -0,0 +1 @@ +layout-category diff --git a/src/assets/img/component-categories/modals.svg b/src/assets/img/component-categories/modals.svg new file mode 100755 index 0000000..0934902 --- /dev/null +++ b/src/assets/img/component-categories/modals.svg @@ -0,0 +1 @@ + modals-category diff --git a/src/assets/img/component-categories/nav.svg b/src/assets/img/component-categories/nav.svg new file mode 100755 index 0000000..a5c06bd --- /dev/null +++ b/src/assets/img/component-categories/nav.svg @@ -0,0 +1 @@ +nav-category diff --git a/src/assets/img/components/Facebook_Social_Icon_Circle_Color.png b/src/assets/img/components/Facebook_Social_Icon_Circle_Color.png new file mode 100755 index 0000000..5140474 Binary files /dev/null and b/src/assets/img/components/Facebook_Social_Icon_Circle_Color.png differ diff --git a/src/assets/img/components/Twitter_Social_Icon_Circle_Color.png b/src/assets/img/components/Twitter_Social_Icon_Circle_Color.png new file mode 100755 index 0000000..af44ca5 Binary files /dev/null and b/src/assets/img/components/Twitter_Social_Icon_Circle_Color.png differ diff --git a/src/assets/img/components/autocomplete.svg b/src/assets/img/components/autocomplete.svg new file mode 100755 index 0000000..d7ce01a --- /dev/null +++ b/src/assets/img/components/autocomplete.svg @@ -0,0 +1 @@ + autocomplete diff --git a/src/assets/img/components/button-toggle.svg b/src/assets/img/components/button-toggle.svg new file mode 100755 index 0000000..effaa56 --- /dev/null +++ b/src/assets/img/components/button-toggle.svg @@ -0,0 +1 @@ + diff --git a/src/assets/img/components/button.svg b/src/assets/img/components/button.svg new file mode 100755 index 0000000..23a3d93 --- /dev/null +++ b/src/assets/img/components/button.svg @@ -0,0 +1 @@ ++ diff --git a/src/assets/img/components/card.svg b/src/assets/img/components/card.svg new file mode 100755 index 0000000..3e0196b --- /dev/null +++ b/src/assets/img/components/card.svg @@ -0,0 +1 @@ +card diff --git a/src/assets/img/components/checkbox.svg b/src/assets/img/components/checkbox.svg new file mode 100755 index 0000000..c305af8 --- /dev/null +++ b/src/assets/img/components/checkbox.svg @@ -0,0 +1 @@ + diff --git a/src/assets/img/components/chips.svg b/src/assets/img/components/chips.svg new file mode 100755 index 0000000..8b7aa7b --- /dev/null +++ b/src/assets/img/components/chips.svg @@ -0,0 +1 @@ +chip diff --git a/src/assets/img/components/datepicker.svg b/src/assets/img/components/datepicker.svg new file mode 100755 index 0000000..11205a1 --- /dev/null +++ b/src/assets/img/components/datepicker.svg @@ -0,0 +1 @@ +datepicker diff --git a/src/assets/img/components/dialog.svg b/src/assets/img/components/dialog.svg new file mode 100755 index 0000000..373c9ba --- /dev/null +++ b/src/assets/img/components/dialog.svg @@ -0,0 +1 @@ + diff --git a/src/assets/img/components/grid-list.svg b/src/assets/img/components/grid-list.svg new file mode 100755 index 0000000..5599b34 --- /dev/null +++ b/src/assets/img/components/grid-list.svg @@ -0,0 +1 @@ + diff --git a/src/assets/img/components/icon.svg b/src/assets/img/components/icon.svg new file mode 100755 index 0000000..afc48fd --- /dev/null +++ b/src/assets/img/components/icon.svg @@ -0,0 +1 @@ +icon diff --git a/src/assets/img/components/input.svg b/src/assets/img/components/input.svg new file mode 100755 index 0000000..94af104 --- /dev/null +++ b/src/assets/img/components/input.svg @@ -0,0 +1 @@ + diff --git a/src/assets/img/components/list.svg b/src/assets/img/components/list.svg new file mode 100755 index 0000000..a63bc59 --- /dev/null +++ b/src/assets/img/components/list.svg @@ -0,0 +1 @@ + diff --git a/src/assets/img/components/menu.svg b/src/assets/img/components/menu.svg new file mode 100755 index 0000000..d6e937b --- /dev/null +++ b/src/assets/img/components/menu.svg @@ -0,0 +1 @@ +menu diff --git a/src/assets/img/components/progress-bar.svg b/src/assets/img/components/progress-bar.svg new file mode 100755 index 0000000..6861b1f --- /dev/null +++ b/src/assets/img/components/progress-bar.svg @@ -0,0 +1 @@ +progress-bar diff --git a/src/assets/img/components/progress-spinner.svg b/src/assets/img/components/progress-spinner.svg new file mode 100755 index 0000000..5960d01 --- /dev/null +++ b/src/assets/img/components/progress-spinner.svg @@ -0,0 +1 @@ + diff --git a/src/assets/img/components/radio.svg b/src/assets/img/components/radio.svg new file mode 100755 index 0000000..b6b349e --- /dev/null +++ b/src/assets/img/components/radio.svg @@ -0,0 +1 @@ + diff --git a/src/assets/img/components/select.svg b/src/assets/img/components/select.svg new file mode 100755 index 0000000..28d94c6 --- /dev/null +++ b/src/assets/img/components/select.svg @@ -0,0 +1 @@ + diff --git a/src/assets/img/components/sidenav.svg b/src/assets/img/components/sidenav.svg new file mode 100755 index 0000000..d266ed4 --- /dev/null +++ b/src/assets/img/components/sidenav.svg @@ -0,0 +1 @@ +sidenav diff --git a/src/assets/img/components/slide-toggle.svg b/src/assets/img/components/slide-toggle.svg new file mode 100755 index 0000000..f987248 --- /dev/null +++ b/src/assets/img/components/slide-toggle.svg @@ -0,0 +1 @@ + diff --git a/src/assets/img/components/slider.svg b/src/assets/img/components/slider.svg new file mode 100755 index 0000000..a6c68f0 --- /dev/null +++ b/src/assets/img/components/slider.svg @@ -0,0 +1 @@ + diff --git a/src/assets/img/components/snack-bar.svg b/src/assets/img/components/snack-bar.svg new file mode 100755 index 0000000..ec21d18 --- /dev/null +++ b/src/assets/img/components/snack-bar.svg @@ -0,0 +1 @@ + diff --git a/src/assets/img/components/tabs.svg b/src/assets/img/components/tabs.svg new file mode 100755 index 0000000..97d104d --- /dev/null +++ b/src/assets/img/components/tabs.svg @@ -0,0 +1 @@ +tab diff --git a/src/assets/img/components/textarea.svg b/src/assets/img/components/textarea.svg new file mode 100755 index 0000000..c6ad355 --- /dev/null +++ b/src/assets/img/components/textarea.svg @@ -0,0 +1 @@ + diff --git a/src/assets/img/components/toolbar.svg b/src/assets/img/components/toolbar.svg new file mode 100755 index 0000000..85d64ee --- /dev/null +++ b/src/assets/img/components/toolbar.svg @@ -0,0 +1 @@ +Toolbar diff --git a/src/assets/img/components/tooltip.svg b/src/assets/img/components/tooltip.svg new file mode 100755 index 0000000..7060291 --- /dev/null +++ b/src/assets/img/components/tooltip.svg @@ -0,0 +1 @@ ++ diff --git a/src/assets/img/error/ico_error.svg b/src/assets/img/error/ico_error.svg new file mode 100755 index 0000000..810627c --- /dev/null +++ b/src/assets/img/error/ico_error.svg @@ -0,0 +1,20 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + diff --git a/src/assets/img/examples/avata-01.jpg b/src/assets/img/examples/avata-01.jpg new file mode 100755 index 0000000..ec10c0d Binary files /dev/null and b/src/assets/img/examples/avata-01.jpg differ diff --git a/src/assets/img/examples/avata-02.png b/src/assets/img/examples/avata-02.png new file mode 100755 index 0000000..d821018 Binary files /dev/null and b/src/assets/img/examples/avata-02.png differ diff --git a/src/assets/img/examples/avata.jpg b/src/assets/img/examples/avata.jpg new file mode 100755 index 0000000..7a8a843 Binary files /dev/null and b/src/assets/img/examples/avata.jpg differ diff --git a/src/assets/img/examples/avatar/1.png b/src/assets/img/examples/avatar/1.png new file mode 100755 index 0000000..99deddc Binary files /dev/null and b/src/assets/img/examples/avatar/1.png differ diff --git a/src/assets/img/examples/avatar/2.png b/src/assets/img/examples/avatar/2.png new file mode 100755 index 0000000..155d565 Binary files /dev/null and b/src/assets/img/examples/avatar/2.png differ diff --git a/src/assets/img/examples/avatar/3.png b/src/assets/img/examples/avatar/3.png new file mode 100755 index 0000000..1f270a6 Binary files /dev/null and b/src/assets/img/examples/avatar/3.png differ diff --git a/src/assets/img/examples/avatar/4.png b/src/assets/img/examples/avatar/4.png new file mode 100755 index 0000000..7658289 Binary files /dev/null and b/src/assets/img/examples/avatar/4.png differ diff --git a/src/assets/img/examples/avatar/5.png b/src/assets/img/examples/avatar/5.png new file mode 100755 index 0000000..d95a066 Binary files /dev/null and b/src/assets/img/examples/avatar/5.png differ diff --git a/src/assets/img/examples/avatar/6.png b/src/assets/img/examples/avatar/6.png new file mode 100755 index 0000000..a94c667 Binary files /dev/null and b/src/assets/img/examples/avatar/6.png differ diff --git a/src/assets/img/examples/avatar/7.png b/src/assets/img/examples/avatar/7.png new file mode 100755 index 0000000..e79a81c Binary files /dev/null and b/src/assets/img/examples/avatar/7.png differ diff --git a/src/assets/img/examples/avatar/8.png b/src/assets/img/examples/avatar/8.png new file mode 100755 index 0000000..7669d81 Binary files /dev/null and b/src/assets/img/examples/avatar/8.png differ diff --git a/src/assets/img/examples/avatar/9.png b/src/assets/img/examples/avatar/9.png new file mode 100755 index 0000000..311908d Binary files /dev/null and b/src/assets/img/examples/avatar/9.png differ diff --git a/src/assets/img/examples/defaultProfile.png b/src/assets/img/examples/defaultProfile.png new file mode 100755 index 0000000..5c3b9c4 Binary files /dev/null and b/src/assets/img/examples/defaultProfile.png differ diff --git a/src/assets/img/examples/default_image.jpg b/src/assets/img/examples/default_image.jpg new file mode 100755 index 0000000..98957b6 Binary files /dev/null and b/src/assets/img/examples/default_image.jpg differ diff --git a/src/assets/img/examples/demo_image.jpg b/src/assets/img/examples/demo_image.jpg new file mode 100755 index 0000000..b8db284 Binary files /dev/null and b/src/assets/img/examples/demo_image.jpg differ diff --git a/src/assets/img/examples/portfolio1.jpg b/src/assets/img/examples/portfolio1.jpg new file mode 100755 index 0000000..63f55cb Binary files /dev/null and b/src/assets/img/examples/portfolio1.jpg differ diff --git a/src/assets/img/examples/portfolio2.png b/src/assets/img/examples/portfolio2.png new file mode 100755 index 0000000..70a9884 Binary files /dev/null and b/src/assets/img/examples/portfolio2.png differ diff --git a/src/assets/img/examples/shiba1.jpg b/src/assets/img/examples/shiba1.jpg new file mode 100755 index 0000000..86c2fbe Binary files /dev/null and b/src/assets/img/examples/shiba1.jpg differ diff --git a/src/assets/img/examples/shiba2.jpg b/src/assets/img/examples/shiba2.jpg new file mode 100755 index 0000000..ace9d40 Binary files /dev/null and b/src/assets/img/examples/shiba2.jpg differ diff --git a/src/assets/img/examples/thumbup-icon.svg b/src/assets/img/examples/thumbup-icon.svg new file mode 100755 index 0000000..1f571c8 --- /dev/null +++ b/src/assets/img/examples/thumbup-icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/img/favicons/android-chrome-144x144.png b/src/assets/img/favicons/android-chrome-144x144.png new file mode 100755 index 0000000..a64dd11 Binary files /dev/null and b/src/assets/img/favicons/android-chrome-144x144.png differ diff --git a/src/assets/img/favicons/android-chrome-192x192.png b/src/assets/img/favicons/android-chrome-192x192.png new file mode 100755 index 0000000..4ea5680 Binary files /dev/null and b/src/assets/img/favicons/android-chrome-192x192.png differ diff --git a/src/assets/img/favicons/android-chrome-36x36.png b/src/assets/img/favicons/android-chrome-36x36.png new file mode 100755 index 0000000..0f8ba0b Binary files /dev/null and b/src/assets/img/favicons/android-chrome-36x36.png differ diff --git a/src/assets/img/favicons/android-chrome-48x48.png b/src/assets/img/favicons/android-chrome-48x48.png new file mode 100755 index 0000000..8258575 Binary files /dev/null and b/src/assets/img/favicons/android-chrome-48x48.png differ diff --git a/src/assets/img/favicons/android-chrome-72x72.png b/src/assets/img/favicons/android-chrome-72x72.png new file mode 100755 index 0000000..f5f4daa Binary files /dev/null and b/src/assets/img/favicons/android-chrome-72x72.png differ diff --git a/src/assets/img/favicons/android-chrome-96x96.png b/src/assets/img/favicons/android-chrome-96x96.png new file mode 100755 index 0000000..36b43ad Binary files /dev/null and b/src/assets/img/favicons/android-chrome-96x96.png differ diff --git a/src/assets/img/favicons/apple-touch-icon-144x144.png b/src/assets/img/favicons/apple-touch-icon-144x144.png new file mode 100755 index 0000000..fb45359 Binary files /dev/null and b/src/assets/img/favicons/apple-touch-icon-144x144.png differ diff --git a/src/assets/img/favicons/apple-touch-icon-152x152.png b/src/assets/img/favicons/apple-touch-icon-152x152.png new file mode 100755 index 0000000..2bf6efa Binary files /dev/null and b/src/assets/img/favicons/apple-touch-icon-152x152.png differ diff --git a/src/assets/img/favicons/apple-touch-icon-180x180.png b/src/assets/img/favicons/apple-touch-icon-180x180.png new file mode 100755 index 0000000..1e87fd9 Binary files /dev/null and b/src/assets/img/favicons/apple-touch-icon-180x180.png differ diff --git a/src/assets/img/favicons/apple-touch-icon-precomposed.png b/src/assets/img/favicons/apple-touch-icon-precomposed.png new file mode 100755 index 0000000..2b2644a Binary files /dev/null and b/src/assets/img/favicons/apple-touch-icon-precomposed.png differ diff --git a/src/assets/img/favicons/apple-touch-icon.png b/src/assets/img/favicons/apple-touch-icon.png new file mode 100755 index 0000000..1e87fd9 Binary files /dev/null and b/src/assets/img/favicons/apple-touch-icon.png differ diff --git a/src/assets/img/favicons/favicon-16x16.png b/src/assets/img/favicons/favicon-16x16.png new file mode 100755 index 0000000..17a7043 Binary files /dev/null and b/src/assets/img/favicons/favicon-16x16.png differ diff --git a/src/assets/img/favicons/favicon-194x194.png b/src/assets/img/favicons/favicon-194x194.png new file mode 100755 index 0000000..2365b31 Binary files /dev/null and b/src/assets/img/favicons/favicon-194x194.png differ diff --git a/src/assets/img/favicons/favicon-32x32.png b/src/assets/img/favicons/favicon-32x32.png new file mode 100755 index 0000000..cb365b8 Binary files /dev/null and b/src/assets/img/favicons/favicon-32x32.png differ diff --git a/src/assets/img/favicons/favicon-96x96.png b/src/assets/img/favicons/favicon-96x96.png new file mode 100755 index 0000000..409257e Binary files /dev/null and b/src/assets/img/favicons/favicon-96x96.png differ diff --git a/src/assets/img/favicons/favicon.ico b/src/assets/img/favicons/favicon.ico new file mode 100755 index 0000000..cebb174 Binary files /dev/null and b/src/assets/img/favicons/favicon.ico differ diff --git a/src/assets/img/favicons/manifest.json b/src/assets/img/favicons/manifest.json new file mode 100755 index 0000000..43b2f57 --- /dev/null +++ b/src/assets/img/favicons/manifest.json @@ -0,0 +1,47 @@ +{ + "name": "Angular Material", + "short_name": "Angular Material", + "theme_color": "#FFFFFF", + "background_color": "#3F51B5", + "start_url": "/", + "display": "standalone", + "orientation": "portrait", + "icons": [ + { + "src": "\/android-chrome-36x36.png", + "sizes": "36x36", + "type": "image\/png", + "density": "0.75" + }, + { + "src": "\/android-chrome-48x48.png", + "sizes": "48x48", + "type": "image\/png", + "density": "1.0" + }, + { + "src": "\/android-chrome-72x72.png", + "sizes": "72x72", + "type": "image\/png", + "density": "1.5" + }, + { + "src": "\/android-chrome-96x96.png", + "sizes": "96x96", + "type": "image\/png", + "density": "2.0" + }, + { + "src": "\/android-chrome-144x144.png", + "sizes": "144x144", + "type": "image\/png", + "density": "3.0" + }, + { + "src": "\/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image\/png", + "density": "4.0" + } + ] +} diff --git a/src/assets/img/homepage/angular-white-transparent.svg b/src/assets/img/homepage/angular-white-transparent.svg new file mode 100755 index 0000000..1a7b5b3 --- /dev/null +++ b/src/assets/img/homepage/angular-white-transparent.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + diff --git a/src/assets/img/homepage/fastandconsistent.svg b/src/assets/img/homepage/fastandconsistent.svg new file mode 100755 index 0000000..64bac7a --- /dev/null +++ b/src/assets/img/homepage/fastandconsistent.svg @@ -0,0 +1 @@ + diff --git a/src/assets/img/homepage/github-circle-white-transparent.svg b/src/assets/img/homepage/github-circle-white-transparent.svg new file mode 100755 index 0000000..715c08e --- /dev/null +++ b/src/assets/img/homepage/github-circle-white-transparent.svg @@ -0,0 +1 @@ +github-circle-white-transparent \ No newline at end of file diff --git a/src/assets/img/homepage/material_splash.svg b/src/assets/img/homepage/material_splash.svg new file mode 100755 index 0000000..082d19d --- /dev/null +++ b/src/assets/img/homepage/material_splash.svg @@ -0,0 +1 @@ + Material Splash Homepage Hero Background + + + + diff --git a/src/assets/img/homepage/optimized.svg b/src/assets/img/homepage/optimized.svg new file mode 100755 index 0000000..4f3423f --- /dev/null +++ b/src/assets/img/homepage/optimized.svg @@ -0,0 +1 @@ + diff --git a/src/assets/img/homepage/sprintzerotoapp.svg b/src/assets/img/homepage/sprintzerotoapp.svg new file mode 100755 index 0000000..21ce298 --- /dev/null +++ b/src/assets/img/homepage/sprintzerotoapp.svg @@ -0,0 +1 @@ + + diff --git a/src/assets/img/homepage/versatile.svg b/src/assets/img/homepage/versatile.svg new file mode 100755 index 0000000..9ed7351 --- /dev/null +++ b/src/assets/img/homepage/versatile.svg @@ -0,0 +1 @@ + diff --git a/src/assets/img/icons/account-box-multiple.svg b/src/assets/img/icons/account-box-multiple.svg new file mode 100755 index 0000000..0c815cc --- /dev/null +++ b/src/assets/img/icons/account-box-multiple.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/img/icons/alert-octagram.svg b/src/assets/img/icons/alert-octagram.svg new file mode 100755 index 0000000..ea2428d --- /dev/null +++ b/src/assets/img/icons/alert-octagram.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/img/icons/bookmark-check.svg b/src/assets/img/icons/bookmark-check.svg new file mode 100755 index 0000000..505d060 --- /dev/null +++ b/src/assets/img/icons/bookmark-check.svg @@ -0,0 +1,13 @@ + + + + + + + diff --git a/src/assets/img/icons/btn_bookmark.svg b/src/assets/img/icons/btn_bookmark.svg new file mode 100755 index 0000000..4f36a08 --- /dev/null +++ b/src/assets/img/icons/btn_bookmark.svg @@ -0,0 +1,20 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + diff --git a/src/assets/img/icons/btn_camera.svg b/src/assets/img/icons/btn_camera.svg new file mode 100755 index 0000000..7c657d1 --- /dev/null +++ b/src/assets/img/icons/btn_camera.svg @@ -0,0 +1,26 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + + diff --git a/src/assets/img/icons/btn_cartoon.svg b/src/assets/img/icons/btn_cartoon.svg new file mode 100755 index 0000000..694e551 --- /dev/null +++ b/src/assets/img/icons/btn_cartoon.svg @@ -0,0 +1,21 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + + + diff --git a/src/assets/img/icons/btn_close.svg b/src/assets/img/icons/btn_close.svg new file mode 100755 index 0000000..a36dd44 --- /dev/null +++ b/src/assets/img/icons/btn_close.svg @@ -0,0 +1,21 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + diff --git a/src/assets/img/icons/btn_illustration.svg b/src/assets/img/icons/btn_illustration.svg new file mode 100755 index 0000000..f210c0a --- /dev/null +++ b/src/assets/img/icons/btn_illustration.svg @@ -0,0 +1,22 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + + diff --git a/src/assets/img/icons/btn_more.svg b/src/assets/img/icons/btn_more.svg new file mode 100755 index 0000000..35e5a0a --- /dev/null +++ b/src/assets/img/icons/btn_more.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/img/icons/btn_novel.svg b/src/assets/img/icons/btn_novel.svg new file mode 100755 index 0000000..2b2cf71 --- /dev/null +++ b/src/assets/img/icons/btn_novel.svg @@ -0,0 +1,18 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + diff --git a/src/assets/img/icons/btn_settings.svg b/src/assets/img/icons/btn_settings.svg new file mode 100755 index 0000000..5e7c3df --- /dev/null +++ b/src/assets/img/icons/btn_settings.svg @@ -0,0 +1,31 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + + diff --git a/src/assets/img/icons/btn_share.svg b/src/assets/img/icons/btn_share.svg new file mode 100755 index 0000000..69e553b --- /dev/null +++ b/src/assets/img/icons/btn_share.svg @@ -0,0 +1,24 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + diff --git a/src/assets/img/icons/btn_tracing.svg b/src/assets/img/icons/btn_tracing.svg new file mode 100755 index 0000000..c94fcd2 --- /dev/null +++ b/src/assets/img/icons/btn_tracing.svg @@ -0,0 +1,32 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + diff --git a/src/assets/img/icons/cards-heart.svg b/src/assets/img/icons/cards-heart.svg new file mode 100755 index 0000000..47eeff0 --- /dev/null +++ b/src/assets/img/icons/cards-heart.svg @@ -0,0 +1,15 @@ + + + + + + + diff --git a/src/assets/img/icons/close-box.svg b/src/assets/img/icons/close-box.svg new file mode 100755 index 0000000..28ca200 --- /dev/null +++ b/src/assets/img/icons/close-box.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/img/icons/close-circle.svg b/src/assets/img/icons/close-circle.svg new file mode 100755 index 0000000..5a37396 --- /dev/null +++ b/src/assets/img/icons/close-circle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/img/icons/comment-check.svg b/src/assets/img/icons/comment-check.svg new file mode 100755 index 0000000..b41c94f --- /dev/null +++ b/src/assets/img/icons/comment-check.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/img/icons/ico_01.svg b/src/assets/img/icons/ico_01.svg new file mode 100755 index 0000000..8227282 --- /dev/null +++ b/src/assets/img/icons/ico_01.svg @@ -0,0 +1,30 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + + + + diff --git a/src/assets/img/icons/ico_02.svg b/src/assets/img/icons/ico_02.svg new file mode 100755 index 0000000..74d10ae --- /dev/null +++ b/src/assets/img/icons/ico_02.svg @@ -0,0 +1,22 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + diff --git a/src/assets/img/icons/ico_03.svg b/src/assets/img/icons/ico_03.svg new file mode 100755 index 0000000..03baf5b --- /dev/null +++ b/src/assets/img/icons/ico_03.svg @@ -0,0 +1,26 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + + diff --git a/src/assets/img/icons/ico_check2.svg b/src/assets/img/icons/ico_check2.svg new file mode 100755 index 0000000..c0cc944 --- /dev/null +++ b/src/assets/img/icons/ico_check2.svg @@ -0,0 +1,18 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + diff --git a/src/assets/img/icons/ico_set_01.svg b/src/assets/img/icons/ico_set_01.svg new file mode 100755 index 0000000..97c7cfd --- /dev/null +++ b/src/assets/img/icons/ico_set_01.svg @@ -0,0 +1,19 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + diff --git a/src/assets/img/icons/ico_set_02.svg b/src/assets/img/icons/ico_set_02.svg new file mode 100755 index 0000000..1cdd787 --- /dev/null +++ b/src/assets/img/icons/ico_set_02.svg @@ -0,0 +1,20 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + diff --git a/src/assets/img/icons/ico_set_03.svg b/src/assets/img/icons/ico_set_03.svg new file mode 100755 index 0000000..755657d --- /dev/null +++ b/src/assets/img/icons/ico_set_03.svg @@ -0,0 +1,17 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + diff --git a/src/assets/img/icons/ico_set_04.svg b/src/assets/img/icons/ico_set_04.svg new file mode 100755 index 0000000..6e24dfd --- /dev/null +++ b/src/assets/img/icons/ico_set_04.svg @@ -0,0 +1,22 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + + diff --git a/src/assets/img/icons/ico_set_05.svg b/src/assets/img/icons/ico_set_05.svg new file mode 100755 index 0000000..5492456 --- /dev/null +++ b/src/assets/img/icons/ico_set_05.svg @@ -0,0 +1,19 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + diff --git a/src/assets/img/icons/ico_set_06.svg b/src/assets/img/icons/ico_set_06.svg new file mode 100755 index 0000000..b688f7c --- /dev/null +++ b/src/assets/img/icons/ico_set_06.svg @@ -0,0 +1,24 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + diff --git a/src/assets/img/icons/ico_set_07.svg b/src/assets/img/icons/ico_set_07.svg new file mode 100755 index 0000000..614e563 --- /dev/null +++ b/src/assets/img/icons/ico_set_07.svg @@ -0,0 +1,20 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + diff --git a/src/assets/img/icons/ico_set_08.svg b/src/assets/img/icons/ico_set_08.svg new file mode 100755 index 0000000..eb3025f --- /dev/null +++ b/src/assets/img/icons/ico_set_08.svg @@ -0,0 +1,22 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + + diff --git a/src/assets/img/icons/ico_share_01.svg b/src/assets/img/icons/ico_share_01.svg new file mode 100755 index 0000000..1b301ac --- /dev/null +++ b/src/assets/img/icons/ico_share_01.svg @@ -0,0 +1,17 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + diff --git a/src/assets/img/icons/ico_share_02.svg b/src/assets/img/icons/ico_share_02.svg new file mode 100755 index 0000000..2c1fc73 --- /dev/null +++ b/src/assets/img/icons/ico_share_02.svg @@ -0,0 +1,24 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + diff --git a/src/assets/img/icons/ico_share_03.svg b/src/assets/img/icons/ico_share_03.svg new file mode 100755 index 0000000..0f88a45 --- /dev/null +++ b/src/assets/img/icons/ico_share_03.svg @@ -0,0 +1,23 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + + + + diff --git a/src/assets/img/icons/ico_share_04.svg b/src/assets/img/icons/ico_share_04.svg new file mode 100755 index 0000000..c777ba7 --- /dev/null +++ b/src/assets/img/icons/ico_share_04.svg @@ -0,0 +1,29 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + + diff --git a/src/assets/img/icons/ico_share_05.svg b/src/assets/img/icons/ico_share_05.svg new file mode 100755 index 0000000..1ab48fd --- /dev/null +++ b/src/assets/img/icons/ico_share_05.svg @@ -0,0 +1,19 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + diff --git a/src/assets/img/icons/message-alert.svg b/src/assets/img/icons/message-alert.svg new file mode 100755 index 0000000..b01aa8b --- /dev/null +++ b/src/assets/img/icons/message-alert.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/img/icons/message-bulleted-off.svg b/src/assets/img/icons/message-bulleted-off.svg new file mode 100755 index 0000000..358504a --- /dev/null +++ b/src/assets/img/icons/message-bulleted-off.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/img/icons/my_ico_01.svg b/src/assets/img/icons/my_ico_01.svg new file mode 100755 index 0000000..2c67127 --- /dev/null +++ b/src/assets/img/icons/my_ico_01.svg @@ -0,0 +1,25 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + + + + + + + + + diff --git a/src/assets/img/icons/my_ico_02.svg b/src/assets/img/icons/my_ico_02.svg new file mode 100755 index 0000000..3448833 --- /dev/null +++ b/src/assets/img/icons/my_ico_02.svg @@ -0,0 +1,20 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + + + diff --git a/src/assets/img/icons/my_ico_03.svg b/src/assets/img/icons/my_ico_03.svg new file mode 100755 index 0000000..448a1d9 --- /dev/null +++ b/src/assets/img/icons/my_ico_03.svg @@ -0,0 +1,26 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + + diff --git a/src/assets/img/icons/my_ico_04.svg b/src/assets/img/icons/my_ico_04.svg new file mode 100755 index 0000000..e21256e --- /dev/null +++ b/src/assets/img/icons/my_ico_04.svg @@ -0,0 +1,22 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + diff --git a/src/assets/img/icons/pound.svg b/src/assets/img/icons/pound.svg new file mode 100755 index 0000000..d124981 --- /dev/null +++ b/src/assets/img/icons/pound.svg @@ -0,0 +1 @@ + diff --git a/src/assets/img/icons/step-backward.svg b/src/assets/img/icons/step-backward.svg new file mode 100755 index 0000000..10995cf --- /dev/null +++ b/src/assets/img/icons/step-backward.svg @@ -0,0 +1,16 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + diff --git a/src/assets/img/icons/top_recommend.svg b/src/assets/img/icons/top_recommend.svg new file mode 100755 index 0000000..ea09d41 --- /dev/null +++ b/src/assets/img/icons/top_recommend.svg @@ -0,0 +1,24 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + + + diff --git a/src/assets/img/icons/top_upload.svg b/src/assets/img/icons/top_upload.svg new file mode 100755 index 0000000..e810950 --- /dev/null +++ b/src/assets/img/icons/top_upload.svg @@ -0,0 +1,18 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + diff --git a/src/assets/img/login/bg.png b/src/assets/img/login/bg.png new file mode 100755 index 0000000..c468303 Binary files /dev/null and b/src/assets/img/login/bg.png differ diff --git a/src/assets/img/login/ico_check2.svg b/src/assets/img/login/ico_check2.svg new file mode 100755 index 0000000..fdef1cb --- /dev/null +++ b/src/assets/img/login/ico_check2.svg @@ -0,0 +1,18 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + diff --git a/src/assets/img/login/login_ico_01.svg b/src/assets/img/login/login_ico_01.svg new file mode 100755 index 0000000..afded93 --- /dev/null +++ b/src/assets/img/login/login_ico_01.svg @@ -0,0 +1,23 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + diff --git a/src/assets/img/login/login_logo.svg b/src/assets/img/login/login_logo.svg new file mode 100755 index 0000000..58abc66 --- /dev/null +++ b/src/assets/img/login/login_logo.svg @@ -0,0 +1,35 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + + + + diff --git a/src/assets/img/login/login_logo_facebook.svg b/src/assets/img/login/login_logo_facebook.svg new file mode 100755 index 0000000..4ad1443 --- /dev/null +++ b/src/assets/img/login/login_logo_facebook.svg @@ -0,0 +1,18 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + diff --git a/src/assets/img/login/login_logo_google.svg b/src/assets/img/login/login_logo_google.svg new file mode 100755 index 0000000..9e36f66 --- /dev/null +++ b/src/assets/img/login/login_logo_google.svg @@ -0,0 +1,20 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + diff --git a/src/assets/img/login/login_logo_gplus.svg b/src/assets/img/login/login_logo_gplus.svg new file mode 100755 index 0000000..088c027 --- /dev/null +++ b/src/assets/img/login/login_logo_gplus.svg @@ -0,0 +1,24 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + + diff --git a/src/assets/img/login/login_logo_insta.svg b/src/assets/img/login/login_logo_insta.svg new file mode 100755 index 0000000..9fe2d40 --- /dev/null +++ b/src/assets/img/login/login_logo_insta.svg @@ -0,0 +1,26 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + + + diff --git a/src/assets/img/login/login_logo_kakao.svg b/src/assets/img/login/login_logo_kakao.svg new file mode 100755 index 0000000..7ad9d63 --- /dev/null +++ b/src/assets/img/login/login_logo_kakao.svg @@ -0,0 +1,32 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + + diff --git a/src/assets/img/login/login_logo_line.svg b/src/assets/img/login/login_logo_line.svg new file mode 100755 index 0000000..1ddee9a --- /dev/null +++ b/src/assets/img/login/login_logo_line.svg @@ -0,0 +1,29 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + diff --git a/src/assets/img/login/login_logo_naver.svg b/src/assets/img/login/login_logo_naver.svg new file mode 100755 index 0000000..0f6c296 --- /dev/null +++ b/src/assets/img/login/login_logo_naver.svg @@ -0,0 +1,17 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + diff --git a/src/assets/img/login/login_logo_twitter.svg b/src/assets/img/login/login_logo_twitter.svg new file mode 100755 index 0000000..62ce7d7 --- /dev/null +++ b/src/assets/img/login/login_logo_twitter.svg @@ -0,0 +1,24 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + diff --git a/src/assets/img/login/login_logo_weibo.svg b/src/assets/img/login/login_logo_weibo.svg new file mode 100755 index 0000000..3ca9ab2 --- /dev/null +++ b/src/assets/img/login/login_logo_weibo.svg @@ -0,0 +1,32 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + + + + diff --git a/src/assets/img/logo/logo.svg b/src/assets/img/logo/logo.svg new file mode 100755 index 0000000..11425a9 --- /dev/null +++ b/src/assets/img/logo/logo.svg @@ -0,0 +1,31 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + + + + diff --git a/src/assets/img/oauth/facebook/facebook_account_login_btn_medium_narrow.png b/src/assets/img/oauth/facebook/facebook_account_login_btn_medium_narrow.png new file mode 100755 index 0000000..8d0d202 Binary files /dev/null and b/src/assets/img/oauth/facebook/facebook_account_login_btn_medium_narrow.png differ diff --git a/src/assets/img/oauth/facebook/facebook_account_login_btn_medium_short.png b/src/assets/img/oauth/facebook/facebook_account_login_btn_medium_short.png new file mode 100755 index 0000000..a4dd2ff Binary files /dev/null and b/src/assets/img/oauth/facebook/facebook_account_login_btn_medium_short.png differ diff --git a/src/assets/img/oauth/facebook/facebook_account_login_btn_medium_wide.png b/src/assets/img/oauth/facebook/facebook_account_login_btn_medium_wide.png new file mode 100755 index 0000000..a9e2d57 Binary files /dev/null and b/src/assets/img/oauth/facebook/facebook_account_login_btn_medium_wide.png differ diff --git a/src/assets/img/oauth/google/btn_google_signin_dark_disabled_web.png b/src/assets/img/oauth/google/btn_google_signin_dark_disabled_web.png new file mode 100755 index 0000000..735d17e Binary files /dev/null and b/src/assets/img/oauth/google/btn_google_signin_dark_disabled_web.png differ diff --git a/src/assets/img/oauth/google/btn_google_signin_dark_disabled_web@2x.png b/src/assets/img/oauth/google/btn_google_signin_dark_disabled_web@2x.png new file mode 100755 index 0000000..485757f Binary files /dev/null and b/src/assets/img/oauth/google/btn_google_signin_dark_disabled_web@2x.png differ diff --git a/src/assets/img/oauth/google/btn_google_signin_dark_focus_web.png b/src/assets/img/oauth/google/btn_google_signin_dark_focus_web.png new file mode 100755 index 0000000..c4cb615 Binary files /dev/null and b/src/assets/img/oauth/google/btn_google_signin_dark_focus_web.png differ diff --git a/src/assets/img/oauth/google/btn_google_signin_dark_focus_web@2x.png b/src/assets/img/oauth/google/btn_google_signin_dark_focus_web@2x.png new file mode 100755 index 0000000..369a6ca Binary files /dev/null and b/src/assets/img/oauth/google/btn_google_signin_dark_focus_web@2x.png differ diff --git a/src/assets/img/oauth/google/btn_google_signin_dark_normal_web.png b/src/assets/img/oauth/google/btn_google_signin_dark_normal_web.png new file mode 100755 index 0000000..b1327b4 Binary files /dev/null and b/src/assets/img/oauth/google/btn_google_signin_dark_normal_web.png differ diff --git a/src/assets/img/oauth/google/btn_google_signin_dark_normal_web@2x.png b/src/assets/img/oauth/google/btn_google_signin_dark_normal_web@2x.png new file mode 100755 index 0000000..f27bb24 Binary files /dev/null and b/src/assets/img/oauth/google/btn_google_signin_dark_normal_web@2x.png differ diff --git a/src/assets/img/oauth/google/btn_google_signin_dark_pressed_web.png b/src/assets/img/oauth/google/btn_google_signin_dark_pressed_web.png new file mode 100755 index 0000000..04ed9c9 Binary files /dev/null and b/src/assets/img/oauth/google/btn_google_signin_dark_pressed_web.png differ diff --git a/src/assets/img/oauth/google/btn_google_signin_dark_pressed_web@2x.png b/src/assets/img/oauth/google/btn_google_signin_dark_pressed_web@2x.png new file mode 100755 index 0000000..4cb85e9 Binary files /dev/null and b/src/assets/img/oauth/google/btn_google_signin_dark_pressed_web@2x.png differ diff --git a/src/assets/img/oauth/google/btn_google_signin_light_disabled_web.png b/src/assets/img/oauth/google/btn_google_signin_light_disabled_web.png new file mode 100755 index 0000000..735d17e Binary files /dev/null and b/src/assets/img/oauth/google/btn_google_signin_light_disabled_web.png differ diff --git a/src/assets/img/oauth/google/btn_google_signin_light_disabled_web@2x.png b/src/assets/img/oauth/google/btn_google_signin_light_disabled_web@2x.png new file mode 100755 index 0000000..485757f Binary files /dev/null and b/src/assets/img/oauth/google/btn_google_signin_light_disabled_web@2x.png differ diff --git a/src/assets/img/oauth/google/btn_google_signin_light_focus_web.png b/src/assets/img/oauth/google/btn_google_signin_light_focus_web.png new file mode 100755 index 0000000..1bf1006 Binary files /dev/null and b/src/assets/img/oauth/google/btn_google_signin_light_focus_web.png differ diff --git a/src/assets/img/oauth/google/btn_google_signin_light_focus_web@2x.png b/src/assets/img/oauth/google/btn_google_signin_light_focus_web@2x.png new file mode 100755 index 0000000..510e619 Binary files /dev/null and b/src/assets/img/oauth/google/btn_google_signin_light_focus_web@2x.png differ diff --git a/src/assets/img/oauth/google/btn_google_signin_light_normal_web.png b/src/assets/img/oauth/google/btn_google_signin_light_normal_web.png new file mode 100755 index 0000000..29ab511 Binary files /dev/null and b/src/assets/img/oauth/google/btn_google_signin_light_normal_web.png differ diff --git a/src/assets/img/oauth/google/btn_google_signin_light_normal_web@2x.png b/src/assets/img/oauth/google/btn_google_signin_light_normal_web@2x.png new file mode 100755 index 0000000..c1e2c5c Binary files /dev/null and b/src/assets/img/oauth/google/btn_google_signin_light_normal_web@2x.png differ diff --git a/src/assets/img/oauth/google/btn_google_signin_light_pressed_web.png b/src/assets/img/oauth/google/btn_google_signin_light_pressed_web.png new file mode 100755 index 0000000..e286c89 Binary files /dev/null and b/src/assets/img/oauth/google/btn_google_signin_light_pressed_web.png differ diff --git a/src/assets/img/oauth/google/btn_google_signin_light_pressed_web@2x.png b/src/assets/img/oauth/google/btn_google_signin_light_pressed_web@2x.png new file mode 100755 index 0000000..d01521e Binary files /dev/null and b/src/assets/img/oauth/google/btn_google_signin_light_pressed_web@2x.png differ diff --git a/src/assets/img/oauth/kakao/en/kakao_account_login_btn_large_narrow.png b/src/assets/img/oauth/kakao/en/kakao_account_login_btn_large_narrow.png new file mode 100755 index 0000000..ae8dc5b Binary files /dev/null and b/src/assets/img/oauth/kakao/en/kakao_account_login_btn_large_narrow.png differ diff --git a/src/assets/img/oauth/kakao/en/kakao_account_login_btn_large_narrow.zip b/src/assets/img/oauth/kakao/en/kakao_account_login_btn_large_narrow.zip new file mode 100755 index 0000000..c526cbe Binary files /dev/null and b/src/assets/img/oauth/kakao/en/kakao_account_login_btn_large_narrow.zip differ diff --git a/src/assets/img/oauth/kakao/en/kakao_account_login_btn_large_narrow_ov.png b/src/assets/img/oauth/kakao/en/kakao_account_login_btn_large_narrow_ov.png new file mode 100755 index 0000000..1442ca2 Binary files /dev/null and b/src/assets/img/oauth/kakao/en/kakao_account_login_btn_large_narrow_ov.png differ diff --git a/src/assets/img/oauth/kakao/en/kakao_account_login_btn_large_wide.png b/src/assets/img/oauth/kakao/en/kakao_account_login_btn_large_wide.png new file mode 100755 index 0000000..2c79bc6 Binary files /dev/null and b/src/assets/img/oauth/kakao/en/kakao_account_login_btn_large_wide.png differ diff --git a/src/assets/img/oauth/kakao/en/kakao_account_login_btn_large_wide.zip b/src/assets/img/oauth/kakao/en/kakao_account_login_btn_large_wide.zip new file mode 100755 index 0000000..a98cbfa Binary files /dev/null and b/src/assets/img/oauth/kakao/en/kakao_account_login_btn_large_wide.zip differ diff --git a/src/assets/img/oauth/kakao/en/kakao_account_login_btn_large_wide_ov.png b/src/assets/img/oauth/kakao/en/kakao_account_login_btn_large_wide_ov.png new file mode 100755 index 0000000..ea3e9af Binary files /dev/null and b/src/assets/img/oauth/kakao/en/kakao_account_login_btn_large_wide_ov.png differ diff --git a/src/assets/img/oauth/kakao/en/kakao_account_login_btn_medium_narrow.png b/src/assets/img/oauth/kakao/en/kakao_account_login_btn_medium_narrow.png new file mode 100755 index 0000000..93eed39 Binary files /dev/null and b/src/assets/img/oauth/kakao/en/kakao_account_login_btn_medium_narrow.png differ diff --git a/src/assets/img/oauth/kakao/en/kakao_account_login_btn_medium_narrow.zip b/src/assets/img/oauth/kakao/en/kakao_account_login_btn_medium_narrow.zip new file mode 100755 index 0000000..4d08e91 Binary files /dev/null and b/src/assets/img/oauth/kakao/en/kakao_account_login_btn_medium_narrow.zip differ diff --git a/src/assets/img/oauth/kakao/en/kakao_account_login_btn_medium_narrow_ov.png b/src/assets/img/oauth/kakao/en/kakao_account_login_btn_medium_narrow_ov.png new file mode 100755 index 0000000..f534104 Binary files /dev/null and b/src/assets/img/oauth/kakao/en/kakao_account_login_btn_medium_narrow_ov.png differ diff --git a/src/assets/img/oauth/kakao/en/kakao_account_login_btn_medium_wide.png b/src/assets/img/oauth/kakao/en/kakao_account_login_btn_medium_wide.png new file mode 100755 index 0000000..61df903 Binary files /dev/null and b/src/assets/img/oauth/kakao/en/kakao_account_login_btn_medium_wide.png differ diff --git a/src/assets/img/oauth/kakao/en/kakao_account_login_btn_medium_wide.zip b/src/assets/img/oauth/kakao/en/kakao_account_login_btn_medium_wide.zip new file mode 100755 index 0000000..effcb25 Binary files /dev/null and b/src/assets/img/oauth/kakao/en/kakao_account_login_btn_medium_wide.zip differ diff --git a/src/assets/img/oauth/kakao/en/kakao_account_login_btn_medium_wide_ov.png b/src/assets/img/oauth/kakao/en/kakao_account_login_btn_medium_wide_ov.png new file mode 100755 index 0000000..735ac26 Binary files /dev/null and b/src/assets/img/oauth/kakao/en/kakao_account_login_btn_medium_wide_ov.png differ diff --git a/src/assets/img/oauth/kakao/en/kakao_login_btn_large.png b/src/assets/img/oauth/kakao/en/kakao_login_btn_large.png new file mode 100755 index 0000000..4340964 Binary files /dev/null and b/src/assets/img/oauth/kakao/en/kakao_login_btn_large.png differ diff --git a/src/assets/img/oauth/kakao/en/kakao_login_btn_large.zip b/src/assets/img/oauth/kakao/en/kakao_login_btn_large.zip new file mode 100755 index 0000000..6c750eb Binary files /dev/null and b/src/assets/img/oauth/kakao/en/kakao_login_btn_large.zip differ diff --git a/src/assets/img/oauth/kakao/en/kakao_login_btn_large_ov.png b/src/assets/img/oauth/kakao/en/kakao_login_btn_large_ov.png new file mode 100755 index 0000000..f4c1761 Binary files /dev/null and b/src/assets/img/oauth/kakao/en/kakao_login_btn_large_ov.png differ diff --git a/src/assets/img/oauth/kakao/en/kakao_login_btn_medium.png b/src/assets/img/oauth/kakao/en/kakao_login_btn_medium.png new file mode 100755 index 0000000..9250182 Binary files /dev/null and b/src/assets/img/oauth/kakao/en/kakao_login_btn_medium.png differ diff --git a/src/assets/img/oauth/kakao/en/kakao_login_btn_medium.zip b/src/assets/img/oauth/kakao/en/kakao_login_btn_medium.zip new file mode 100755 index 0000000..d56c0e5 Binary files /dev/null and b/src/assets/img/oauth/kakao/en/kakao_login_btn_medium.zip differ diff --git a/src/assets/img/oauth/kakao/en/kakao_login_btn_medium_ov.png b/src/assets/img/oauth/kakao/en/kakao_login_btn_medium_ov.png new file mode 100755 index 0000000..691ad50 Binary files /dev/null and b/src/assets/img/oauth/kakao/en/kakao_login_btn_medium_ov.png differ diff --git a/src/assets/img/oauth/kakao/en/kakao_login_btn_small.png b/src/assets/img/oauth/kakao/en/kakao_login_btn_small.png new file mode 100755 index 0000000..2b57881 Binary files /dev/null and b/src/assets/img/oauth/kakao/en/kakao_login_btn_small.png differ diff --git a/src/assets/img/oauth/kakao/en/kakao_login_btn_small.zip b/src/assets/img/oauth/kakao/en/kakao_login_btn_small.zip new file mode 100755 index 0000000..83cfef5 Binary files /dev/null and b/src/assets/img/oauth/kakao/en/kakao_login_btn_small.zip differ diff --git a/src/assets/img/oauth/kakao/en/kakao_login_btn_small_ov.png b/src/assets/img/oauth/kakao/en/kakao_login_btn_small_ov.png new file mode 100755 index 0000000..613942e Binary files /dev/null and b/src/assets/img/oauth/kakao/en/kakao_login_btn_small_ov.png differ diff --git a/src/assets/img/oauth/kakao/kr/kakao_account_login_btn_large_narrow.png b/src/assets/img/oauth/kakao/kr/kakao_account_login_btn_large_narrow.png new file mode 100755 index 0000000..b505467 Binary files /dev/null and b/src/assets/img/oauth/kakao/kr/kakao_account_login_btn_large_narrow.png differ diff --git a/src/assets/img/oauth/kakao/kr/kakao_account_login_btn_large_narrow.tar b/src/assets/img/oauth/kakao/kr/kakao_account_login_btn_large_narrow.tar new file mode 100755 index 0000000..c750274 Binary files /dev/null and b/src/assets/img/oauth/kakao/kr/kakao_account_login_btn_large_narrow.tar differ diff --git a/src/assets/img/oauth/kakao/kr/kakao_account_login_btn_large_narrow.zip b/src/assets/img/oauth/kakao/kr/kakao_account_login_btn_large_narrow.zip new file mode 100755 index 0000000..cb33e30 Binary files /dev/null and b/src/assets/img/oauth/kakao/kr/kakao_account_login_btn_large_narrow.zip differ diff --git a/src/assets/img/oauth/kakao/kr/kakao_account_login_btn_large_narrow_ov.png b/src/assets/img/oauth/kakao/kr/kakao_account_login_btn_large_narrow_ov.png new file mode 100755 index 0000000..ae5ad26 Binary files /dev/null and b/src/assets/img/oauth/kakao/kr/kakao_account_login_btn_large_narrow_ov.png differ diff --git a/src/assets/img/oauth/kakao/kr/kakao_account_login_btn_large_wide.png b/src/assets/img/oauth/kakao/kr/kakao_account_login_btn_large_wide.png new file mode 100755 index 0000000..1ed0a0e Binary files /dev/null and b/src/assets/img/oauth/kakao/kr/kakao_account_login_btn_large_wide.png differ diff --git a/src/assets/img/oauth/kakao/kr/kakao_account_login_btn_large_wide.tar b/src/assets/img/oauth/kakao/kr/kakao_account_login_btn_large_wide.tar new file mode 100755 index 0000000..f78f59c Binary files /dev/null and b/src/assets/img/oauth/kakao/kr/kakao_account_login_btn_large_wide.tar differ diff --git a/src/assets/img/oauth/kakao/kr/kakao_account_login_btn_large_wide.zip b/src/assets/img/oauth/kakao/kr/kakao_account_login_btn_large_wide.zip new file mode 100755 index 0000000..2a7d395 Binary files /dev/null and b/src/assets/img/oauth/kakao/kr/kakao_account_login_btn_large_wide.zip differ diff --git a/src/assets/img/oauth/kakao/kr/kakao_account_login_btn_large_wide_ov.png b/src/assets/img/oauth/kakao/kr/kakao_account_login_btn_large_wide_ov.png new file mode 100755 index 0000000..8ba9218 Binary files /dev/null and b/src/assets/img/oauth/kakao/kr/kakao_account_login_btn_large_wide_ov.png differ diff --git a/src/assets/img/oauth/kakao/kr/kakao_account_login_btn_medium_narrow.png b/src/assets/img/oauth/kakao/kr/kakao_account_login_btn_medium_narrow.png new file mode 100755 index 0000000..a29c7a7 Binary files /dev/null and b/src/assets/img/oauth/kakao/kr/kakao_account_login_btn_medium_narrow.png differ diff --git a/src/assets/img/oauth/kakao/kr/kakao_account_login_btn_medium_narrow.tar b/src/assets/img/oauth/kakao/kr/kakao_account_login_btn_medium_narrow.tar new file mode 100755 index 0000000..cf7efbc Binary files /dev/null and b/src/assets/img/oauth/kakao/kr/kakao_account_login_btn_medium_narrow.tar differ diff --git a/src/assets/img/oauth/kakao/kr/kakao_account_login_btn_medium_narrow.zip b/src/assets/img/oauth/kakao/kr/kakao_account_login_btn_medium_narrow.zip new file mode 100755 index 0000000..4ed8bc9 Binary files /dev/null and b/src/assets/img/oauth/kakao/kr/kakao_account_login_btn_medium_narrow.zip differ diff --git a/src/assets/img/oauth/kakao/kr/kakao_account_login_btn_medium_narrow_ov.png b/src/assets/img/oauth/kakao/kr/kakao_account_login_btn_medium_narrow_ov.png new file mode 100755 index 0000000..31d6daa Binary files /dev/null and b/src/assets/img/oauth/kakao/kr/kakao_account_login_btn_medium_narrow_ov.png differ diff --git a/src/assets/img/oauth/kakao/kr/kakao_account_login_btn_medium_wide.png b/src/assets/img/oauth/kakao/kr/kakao_account_login_btn_medium_wide.png new file mode 100755 index 0000000..4b3a6f0 Binary files /dev/null and b/src/assets/img/oauth/kakao/kr/kakao_account_login_btn_medium_wide.png differ diff --git a/src/assets/img/oauth/kakao/kr/kakao_account_login_btn_medium_wide.tar b/src/assets/img/oauth/kakao/kr/kakao_account_login_btn_medium_wide.tar new file mode 100755 index 0000000..7a56087 Binary files /dev/null and b/src/assets/img/oauth/kakao/kr/kakao_account_login_btn_medium_wide.tar differ diff --git a/src/assets/img/oauth/kakao/kr/kakao_account_login_btn_medium_wide.zip b/src/assets/img/oauth/kakao/kr/kakao_account_login_btn_medium_wide.zip new file mode 100755 index 0000000..efcd7ca Binary files /dev/null and b/src/assets/img/oauth/kakao/kr/kakao_account_login_btn_medium_wide.zip differ diff --git a/src/assets/img/oauth/kakao/kr/kakao_account_login_btn_medium_wide_ov.png b/src/assets/img/oauth/kakao/kr/kakao_account_login_btn_medium_wide_ov.png new file mode 100755 index 0000000..5e5c604 Binary files /dev/null and b/src/assets/img/oauth/kakao/kr/kakao_account_login_btn_medium_wide_ov.png differ diff --git a/src/assets/img/oauth/kakao/kr/kakao_login_btn_large.png b/src/assets/img/oauth/kakao/kr/kakao_login_btn_large.png new file mode 100755 index 0000000..bff360f Binary files /dev/null and b/src/assets/img/oauth/kakao/kr/kakao_login_btn_large.png differ diff --git a/src/assets/img/oauth/kakao/kr/kakao_login_btn_large.tar b/src/assets/img/oauth/kakao/kr/kakao_login_btn_large.tar new file mode 100755 index 0000000..42a26e4 Binary files /dev/null and b/src/assets/img/oauth/kakao/kr/kakao_login_btn_large.tar differ diff --git a/src/assets/img/oauth/kakao/kr/kakao_login_btn_large.zip b/src/assets/img/oauth/kakao/kr/kakao_login_btn_large.zip new file mode 100755 index 0000000..faba877 Binary files /dev/null and b/src/assets/img/oauth/kakao/kr/kakao_login_btn_large.zip differ diff --git a/src/assets/img/oauth/kakao/kr/kakao_login_btn_large_ov.png b/src/assets/img/oauth/kakao/kr/kakao_login_btn_large_ov.png new file mode 100755 index 0000000..89fa330 Binary files /dev/null and b/src/assets/img/oauth/kakao/kr/kakao_login_btn_large_ov.png differ diff --git a/src/assets/img/oauth/kakao/kr/kakao_login_btn_medium.png b/src/assets/img/oauth/kakao/kr/kakao_login_btn_medium.png new file mode 100755 index 0000000..d9a7cda Binary files /dev/null and b/src/assets/img/oauth/kakao/kr/kakao_login_btn_medium.png differ diff --git a/src/assets/img/oauth/kakao/kr/kakao_login_btn_medium.tar b/src/assets/img/oauth/kakao/kr/kakao_login_btn_medium.tar new file mode 100755 index 0000000..7c85467 Binary files /dev/null and b/src/assets/img/oauth/kakao/kr/kakao_login_btn_medium.tar differ diff --git a/src/assets/img/oauth/kakao/kr/kakao_login_btn_medium.zip b/src/assets/img/oauth/kakao/kr/kakao_login_btn_medium.zip new file mode 100755 index 0000000..ffc4364 Binary files /dev/null and b/src/assets/img/oauth/kakao/kr/kakao_login_btn_medium.zip differ diff --git a/src/assets/img/oauth/kakao/kr/kakao_login_btn_medium_ov.png b/src/assets/img/oauth/kakao/kr/kakao_login_btn_medium_ov.png new file mode 100755 index 0000000..ced859d Binary files /dev/null and b/src/assets/img/oauth/kakao/kr/kakao_login_btn_medium_ov.png differ diff --git a/src/assets/img/oauth/kakao/kr/kakao_login_btn_small.png b/src/assets/img/oauth/kakao/kr/kakao_login_btn_small.png new file mode 100755 index 0000000..56e3092 Binary files /dev/null and b/src/assets/img/oauth/kakao/kr/kakao_login_btn_small.png differ diff --git a/src/assets/img/oauth/kakao/kr/kakao_login_btn_small.tar b/src/assets/img/oauth/kakao/kr/kakao_login_btn_small.tar new file mode 100755 index 0000000..ff96541 Binary files /dev/null and b/src/assets/img/oauth/kakao/kr/kakao_login_btn_small.tar differ diff --git a/src/assets/img/oauth/kakao/kr/kakao_login_btn_small.zip b/src/assets/img/oauth/kakao/kr/kakao_login_btn_small.zip new file mode 100755 index 0000000..7b19727 Binary files /dev/null and b/src/assets/img/oauth/kakao/kr/kakao_login_btn_small.zip differ diff --git a/src/assets/img/oauth/kakao/kr/kakao_login_btn_small_ov.png b/src/assets/img/oauth/kakao/kr/kakao_login_btn_small_ov.png new file mode 100755 index 0000000..6d87560 Binary files /dev/null and b/src/assets/img/oauth/kakao/kr/kakao_login_btn_small_ov.png differ diff --git a/src/assets/img/oauth/naver/en/naver_account_login_btn_icon_Green.PNG b/src/assets/img/oauth/naver/en/naver_account_login_btn_icon_Green.PNG new file mode 100755 index 0000000..97e43f8 Binary files /dev/null and b/src/assets/img/oauth/naver/en/naver_account_login_btn_icon_Green.PNG differ diff --git a/src/assets/img/oauth/naver/en/naver_account_login_btn_icon_White.PNG b/src/assets/img/oauth/naver/en/naver_account_login_btn_icon_White.PNG new file mode 100755 index 0000000..ec09013 Binary files /dev/null and b/src/assets/img/oauth/naver/en/naver_account_login_btn_icon_White.PNG differ diff --git a/src/assets/img/oauth/naver/en/naver_account_login_btn_logout_Green.PNG b/src/assets/img/oauth/naver/en/naver_account_login_btn_logout_Green.PNG new file mode 100755 index 0000000..cb62910 Binary files /dev/null and b/src/assets/img/oauth/naver/en/naver_account_login_btn_logout_Green.PNG differ diff --git a/src/assets/img/oauth/naver/en/naver_account_login_btn_logout_White.PNG b/src/assets/img/oauth/naver/en/naver_account_login_btn_logout_White.PNG new file mode 100755 index 0000000..a9d9eb4 Binary files /dev/null and b/src/assets/img/oauth/naver/en/naver_account_login_btn_logout_White.PNG differ diff --git a/src/assets/img/oauth/naver/en/naver_account_login_btn_official_Green.PNG b/src/assets/img/oauth/naver/en/naver_account_login_btn_official_Green.PNG new file mode 100755 index 0000000..7290264 Binary files /dev/null and b/src/assets/img/oauth/naver/en/naver_account_login_btn_official_Green.PNG differ diff --git a/src/assets/img/oauth/naver/en/naver_account_login_btn_official_White.PNG b/src/assets/img/oauth/naver/en/naver_account_login_btn_official_White.PNG new file mode 100755 index 0000000..0adfb08 Binary files /dev/null and b/src/assets/img/oauth/naver/en/naver_account_login_btn_official_White.PNG differ diff --git a/src/assets/img/oauth/naver/en/naver_account_login_btn_short_Green.PNG b/src/assets/img/oauth/naver/en/naver_account_login_btn_short_Green.PNG new file mode 100755 index 0000000..107db15 Binary files /dev/null and b/src/assets/img/oauth/naver/en/naver_account_login_btn_short_Green.PNG differ diff --git a/src/assets/img/oauth/naver/en/naver_account_login_btn_short_White.PNG b/src/assets/img/oauth/naver/en/naver_account_login_btn_short_White.PNG new file mode 100755 index 0000000..3929fc4 Binary files /dev/null and b/src/assets/img/oauth/naver/en/naver_account_login_btn_short_White.PNG differ diff --git a/src/assets/img/oauth/naver/ko/naver_account_login_btn_icon_Green.PNG b/src/assets/img/oauth/naver/ko/naver_account_login_btn_icon_Green.PNG new file mode 100755 index 0000000..97e43f8 Binary files /dev/null and b/src/assets/img/oauth/naver/ko/naver_account_login_btn_icon_Green.PNG differ diff --git a/src/assets/img/oauth/naver/ko/naver_account_login_btn_icon_White.PNG b/src/assets/img/oauth/naver/ko/naver_account_login_btn_icon_White.PNG new file mode 100755 index 0000000..ec09013 Binary files /dev/null and b/src/assets/img/oauth/naver/ko/naver_account_login_btn_icon_White.PNG differ diff --git a/src/assets/img/oauth/naver/ko/naver_account_login_btn_logout_Green.PNG b/src/assets/img/oauth/naver/ko/naver_account_login_btn_logout_Green.PNG new file mode 100755 index 0000000..e3e0633 Binary files /dev/null and b/src/assets/img/oauth/naver/ko/naver_account_login_btn_logout_Green.PNG differ diff --git a/src/assets/img/oauth/naver/ko/naver_account_login_btn_logout_White.PNG b/src/assets/img/oauth/naver/ko/naver_account_login_btn_logout_White.PNG new file mode 100755 index 0000000..86152e1 Binary files /dev/null and b/src/assets/img/oauth/naver/ko/naver_account_login_btn_logout_White.PNG differ diff --git a/src/assets/img/oauth/naver/ko/naver_account_login_btn_official_Green.PNG b/src/assets/img/oauth/naver/ko/naver_account_login_btn_official_Green.PNG new file mode 100755 index 0000000..1e7bfe6 Binary files /dev/null and b/src/assets/img/oauth/naver/ko/naver_account_login_btn_official_Green.PNG differ diff --git a/src/assets/img/oauth/naver/ko/naver_account_login_btn_official_White.PNG b/src/assets/img/oauth/naver/ko/naver_account_login_btn_official_White.PNG new file mode 100755 index 0000000..f203d6d Binary files /dev/null and b/src/assets/img/oauth/naver/ko/naver_account_login_btn_official_White.PNG differ diff --git a/src/assets/img/oauth/naver/ko/naver_account_login_btn_short_Green.PNG b/src/assets/img/oauth/naver/ko/naver_account_login_btn_short_Green.PNG new file mode 100755 index 0000000..d282051 Binary files /dev/null and b/src/assets/img/oauth/naver/ko/naver_account_login_btn_short_Green.PNG differ diff --git a/src/assets/img/oauth/naver/ko/naver_account_login_btn_short_White.PNG b/src/assets/img/oauth/naver/ko/naver_account_login_btn_short_White.PNG new file mode 100755 index 0000000..2a61374 Binary files /dev/null and b/src/assets/img/oauth/naver/ko/naver_account_login_btn_short_White.PNG differ diff --git a/src/assets/img/profile/bg/1.jpg b/src/assets/img/profile/bg/1.jpg new file mode 100755 index 0000000..ace9d40 Binary files /dev/null and b/src/assets/img/profile/bg/1.jpg differ diff --git a/src/assets/img/profile/bg/2.jpg b/src/assets/img/profile/bg/2.jpg new file mode 100755 index 0000000..a943407 Binary files /dev/null and b/src/assets/img/profile/bg/2.jpg differ diff --git a/src/assets/img/profile/bg/3.jpg b/src/assets/img/profile/bg/3.jpg new file mode 100755 index 0000000..5106e9d Binary files /dev/null and b/src/assets/img/profile/bg/3.jpg differ diff --git a/src/assets/img/profile/bg/4.jpg b/src/assets/img/profile/bg/4.jpg new file mode 100755 index 0000000..bd31030 Binary files /dev/null and b/src/assets/img/profile/bg/4.jpg differ diff --git a/src/assets/img/profile/bg/5.jpg b/src/assets/img/profile/bg/5.jpg new file mode 100755 index 0000000..b8db284 Binary files /dev/null and b/src/assets/img/profile/bg/5.jpg differ diff --git a/src/assets/img/profile/bg/default-background-image.jpg b/src/assets/img/profile/bg/default-background-image.jpg new file mode 100755 index 0000000..bd31030 Binary files /dev/null and b/src/assets/img/profile/bg/default-background-image.jpg differ diff --git a/src/assets/img/profile/user/1.jpg b/src/assets/img/profile/user/1.jpg new file mode 100755 index 0000000..86c2fbe Binary files /dev/null and b/src/assets/img/profile/user/1.jpg differ diff --git a/src/assets/img/profile/user/2.jpg b/src/assets/img/profile/user/2.jpg new file mode 100755 index 0000000..fd3d641 Binary files /dev/null and b/src/assets/img/profile/user/2.jpg differ diff --git a/src/assets/img/profile/user/3.jpg b/src/assets/img/profile/user/3.jpg new file mode 100755 index 0000000..9d87262 Binary files /dev/null and b/src/assets/img/profile/user/3.jpg differ diff --git a/src/assets/img/profile/user/4.jpg b/src/assets/img/profile/user/4.jpg new file mode 100755 index 0000000..7658289 Binary files /dev/null and b/src/assets/img/profile/user/4.jpg differ diff --git a/src/assets/img/profile/user/5.jpg b/src/assets/img/profile/user/5.jpg new file mode 100755 index 0000000..d95a066 Binary files /dev/null and b/src/assets/img/profile/user/5.jpg differ diff --git a/src/assets/img/profile/user/defaultProfile.png b/src/assets/img/profile/user/defaultProfile.png new file mode 100755 index 0000000..5c3b9c4 Binary files /dev/null and b/src/assets/img/profile/user/defaultProfile.png differ diff --git a/src/assets/img/toolbar-icons/bot_bookmark.svg b/src/assets/img/toolbar-icons/bot_bookmark.svg new file mode 100755 index 0000000..db48da0 --- /dev/null +++ b/src/assets/img/toolbar-icons/bot_bookmark.svg @@ -0,0 +1,18 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + diff --git a/src/assets/img/toolbar-icons/bot_home.svg b/src/assets/img/toolbar-icons/bot_home.svg new file mode 100755 index 0000000..2b73e0e --- /dev/null +++ b/src/assets/img/toolbar-icons/bot_home.svg @@ -0,0 +1,21 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + diff --git a/src/assets/img/toolbar-icons/bot_my.svg b/src/assets/img/toolbar-icons/bot_my.svg new file mode 100755 index 0000000..42e2797 --- /dev/null +++ b/src/assets/img/toolbar-icons/bot_my.svg @@ -0,0 +1,22 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + + diff --git a/src/assets/img/toolbar-icons/bot_search.svg b/src/assets/img/toolbar-icons/bot_search.svg new file mode 100755 index 0000000..f9159fd --- /dev/null +++ b/src/assets/img/toolbar-icons/bot_search.svg @@ -0,0 +1,22 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + diff --git a/src/assets/img/toolbar-icons/bot_upload.svg b/src/assets/img/toolbar-icons/bot_upload.svg new file mode 100755 index 0000000..2dfdb59 --- /dev/null +++ b/src/assets/img/toolbar-icons/bot_upload.svg @@ -0,0 +1,24 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + + diff --git a/src/assets/img/user-support/favor-tag/BL.jpg b/src/assets/img/user-support/favor-tag/BL.jpg new file mode 100755 index 0000000..1da5ae5 Binary files /dev/null and b/src/assets/img/user-support/favor-tag/BL.jpg differ diff --git a/src/assets/img/user-support/favor-tag/GL.jpg b/src/assets/img/user-support/favor-tag/GL.jpg new file mode 100755 index 0000000..f2555c4 Binary files /dev/null and b/src/assets/img/user-support/favor-tag/GL.jpg differ diff --git a/src/assets/img/user-support/favor-tag/SF.jpg b/src/assets/img/user-support/favor-tag/SF.jpg new file mode 100755 index 0000000..91ecd1b Binary files /dev/null and b/src/assets/img/user-support/favor-tag/SF.jpg differ diff --git a/src/assets/img/user-support/favor-tag/개그.jpg b/src/assets/img/user-support/favor-tag/개그.jpg new file mode 100755 index 0000000..d0a79d7 Binary files /dev/null and b/src/assets/img/user-support/favor-tag/개그.jpg differ diff --git a/src/assets/img/user-support/favor-tag/도박.jpg b/src/assets/img/user-support/favor-tag/도박.jpg new file mode 100755 index 0000000..dec4d8b Binary files /dev/null and b/src/assets/img/user-support/favor-tag/도박.jpg differ diff --git a/src/assets/img/user-support/favor-tag/동물.png b/src/assets/img/user-support/favor-tag/동물.png new file mode 100755 index 0000000..472b88d Binary files /dev/null and b/src/assets/img/user-support/favor-tag/동물.png differ diff --git a/src/assets/img/user-support/favor-tag/드라마.png b/src/assets/img/user-support/favor-tag/드라마.png new file mode 100755 index 0000000..c241295 Binary files /dev/null and b/src/assets/img/user-support/favor-tag/드라마.png differ diff --git a/src/assets/img/user-support/favor-tag/모험_판타지.png b/src/assets/img/user-support/favor-tag/모험_판타지.png new file mode 100755 index 0000000..1f8bb96 Binary files /dev/null and b/src/assets/img/user-support/favor-tag/모험_판타지.png differ diff --git a/src/assets/img/user-support/favor-tag/미식.png b/src/assets/img/user-support/favor-tag/미식.png new file mode 100755 index 0000000..84a5968 Binary files /dev/null and b/src/assets/img/user-support/favor-tag/미식.png differ diff --git a/src/assets/img/user-support/favor-tag/베틀_액션.png b/src/assets/img/user-support/favor-tag/베틀_액션.png new file mode 100755 index 0000000..9674e09 Binary files /dev/null and b/src/assets/img/user-support/favor-tag/베틀_액션.png differ diff --git a/src/assets/img/user-support/favor-tag/비즈니스.png b/src/assets/img/user-support/favor-tag/비즈니스.png new file mode 100755 index 0000000..f896dba Binary files /dev/null and b/src/assets/img/user-support/favor-tag/비즈니스.png differ diff --git a/src/assets/img/user-support/favor-tag/세기말_종말.png b/src/assets/img/user-support/favor-tag/세기말_종말.png new file mode 100755 index 0000000..c9bce32 Binary files /dev/null and b/src/assets/img/user-support/favor-tag/세기말_종말.png differ diff --git a/src/assets/img/user-support/favor-tag/스포츠.jpg b/src/assets/img/user-support/favor-tag/스포츠.jpg new file mode 100755 index 0000000..fbbd2a0 Binary files /dev/null and b/src/assets/img/user-support/favor-tag/스포츠.jpg differ diff --git a/src/assets/img/user-support/favor-tag/심쿵.png b/src/assets/img/user-support/favor-tag/심쿵.png new file mode 100755 index 0000000..5277b56 Binary files /dev/null and b/src/assets/img/user-support/favor-tag/심쿵.png differ diff --git a/src/assets/img/user-support/favor-tag/아이돌.jpg b/src/assets/img/user-support/favor-tag/아이돌.jpg new file mode 100755 index 0000000..00c3bd2 Binary files /dev/null and b/src/assets/img/user-support/favor-tag/아이돌.jpg differ diff --git a/src/assets/img/user-support/favor-tag/역사_시대극.jpg b/src/assets/img/user-support/favor-tag/역사_시대극.jpg new file mode 100755 index 0000000..9840ef6 Binary files /dev/null and b/src/assets/img/user-support/favor-tag/역사_시대극.jpg differ diff --git a/src/assets/img/user-support/favor-tag/연애_사랑.jpg b/src/assets/img/user-support/favor-tag/연애_사랑.jpg new file mode 100755 index 0000000..44ddfd2 Binary files /dev/null and b/src/assets/img/user-support/favor-tag/연애_사랑.jpg differ diff --git a/src/assets/img/user-support/favor-tag/이세계.jpg b/src/assets/img/user-support/favor-tag/이세계.jpg new file mode 100755 index 0000000..149ea36 Binary files /dev/null and b/src/assets/img/user-support/favor-tag/이세계.jpg differ diff --git a/src/assets/img/user-support/favor-tag/일상.png b/src/assets/img/user-support/favor-tag/일상.png new file mode 100755 index 0000000..82554e1 Binary files /dev/null and b/src/assets/img/user-support/favor-tag/일상.png differ diff --git a/src/assets/img/user-support/favor-tag/호러.jpg b/src/assets/img/user-support/favor-tag/호러.jpg new file mode 100755 index 0000000..b688d41 Binary files /dev/null and b/src/assets/img/user-support/favor-tag/호러.jpg differ diff --git a/src/assets/json/article-tag.json b/src/assets/json/article-tag.json new file mode 100755 index 0000000..c32a1ae --- /dev/null +++ b/src/assets/json/article-tag.json @@ -0,0 +1,226 @@ +[{ + "id": "6LR3ibvURho9WJesizMdgW", + "createDate": "2019-01-19 10:22:42", + "updateDate": "2019-01-19 10:22:42", + "deleteDate": null, + "tagName": "GL", + "tagCount": 0, + "userId": null + }, + { + "id": "bjc7Esp7zAZdaGjLBkQUuF", + "createDate": "2019-01-19 10:22:42", + "updateDate": "2019-01-19 10:22:42", + "deleteDate": null, + "tagName": "도박", + "tagCount": 0, + "userId": null + }, + { + "id": "qr3QYDaiXZBEJLuR1KJwYw", + "createDate": "2019-01-19 10:22:42", + "updateDate": "2019-01-19 10:22:42", + "deleteDate": null, + "tagName": "동물", + "tagCount": 0, + "userId": null + }, + { + "id": "oVhoJA1AXsnRhcqvf2MdAe", + "createDate": "2019-01-19 10:22:42", + "updateDate": "2019-01-19 10:22:42", + "deleteDate": null, + "tagName": "SF", + "tagCount": 0, + "userId": null + }, + { + "id": "v7VrLnhyCHkx2DbyBpCeiG", + "createDate": "2019-01-19 10:22:42", + "updateDate": "2019-01-19 10:22:42", + "deleteDate": null, + "tagName": "BL", + "tagCount": 0, + "userId": null + }, + { + "id": "4YyEgYzFTqZKRwRBcpM5D7", + "createDate": "2019-01-19 10:22:42", + "updateDate": "2019-01-19 10:22:42", + "deleteDate": null, + "tagName": "개그", + "tagCount": 0, + "userId": null + }, + { + "id": "wr9mPE1i1RohAvZa7FYfzi", + "createDate": "2019-01-19 10:22:43", + "updateDate": "2019-01-19 10:22:43", + "deleteDate": null, + "tagName": "판타지", + "tagCount": 0, + "userId": null + }, + { + "id": "tZ5fZ7hqSLSx3c7gvai7Jc", + "createDate": "2019-01-19 10:22:43", + "updateDate": "2019-01-19 10:22:43", + "deleteDate": null, + "tagName": "미식", + "tagCount": 0, + "userId": null + }, + { + "id": "4ky43VAHr7znWQFqtDHMVK", + "createDate": "2019-01-19 10:22:43", + "updateDate": "2019-01-19 10:22:43", + "deleteDate": null, + "tagName": "모험", + "tagCount": 0, + "userId": null + }, + { + "id": "oczRnt8XnkzGYEimz68zVh", + "createDate": "2019-01-19 10:22:43", + "updateDate": "2019-01-19 10:22:43", + "deleteDate": null, + "tagName": "드라마", + "tagCount": 0, + "userId": null + }, + { + "id": "jg2q8NwSx5GvGZtMpSCuvo", + "createDate": "2019-01-19 10:22:43", + "updateDate": "2019-01-19 10:22:43", + "deleteDate": null, + "tagName": "베틀", + "tagCount": 0, + "userId": null + }, + { + "id": "55BYnmgNRiFsw5fKoEhEiG", + "createDate": "2019-01-19 10:22:43", + "updateDate": "2019-01-19 10:22:43", + "deleteDate": null, + "tagName": "액션", + "tagCount": 0, + "userId": null + }, + { + "id": "ub35RRSJiPEDdjRUs9jPSx", + "createDate": "2019-01-19 10:22:43", + "updateDate": "2019-01-19 10:22:43", + "deleteDate": null, + "tagName": "세기말", + "tagCount": 0, + "userId": null + }, + { + "id": "925GDDnF3dYfBUifkFGKog", + "createDate": "2019-01-19 10:22:43", + "updateDate": "2019-01-19 10:22:43", + "deleteDate": null, + "tagName": "종말", + "tagCount": 0, + "userId": null + }, + { + "id": "iBSWXWYGZPqhnpJVyNsymX", + "createDate": "2019-01-19 10:22:43", + "updateDate": "2019-01-19 10:22:43", + "deleteDate": null, + "tagName": "비즈니스", + "tagCount": 0, + "userId": null + }, + { + "id": "sRskhhDchmcEqZmDvxr5Gk", + "createDate": "2019-01-19 10:22:43", + "updateDate": "2019-01-19 10:22:43", + "deleteDate": null, + "tagName": "스포츠", + "tagCount": 0, + "userId": null + }, + { + "id": "b1ZSC3msK95KK2oqxb3Po6", + "createDate": "2019-01-19 10:22:43", + "updateDate": "2019-01-19 10:22:43", + "deleteDate": null, + "tagName": "심쿵", + "tagCount": 0, + "userId": null + }, + { + "id": "cAYKwQEZL98pm1Fr1JpkhY", + "createDate": "2019-01-19 10:22:43", + "updateDate": "2019-01-19 10:22:43", + "deleteDate": null, + "tagName": "아이돌", + "tagCount": 0, + "userId": null + }, + { + "id": "pjNG8MUo8XV5EKMqmCp87W", + "createDate": "2019-01-19 10:22:44", + "updateDate": "2019-01-19 10:22:44", + "deleteDate": null, + "tagName": "시대극", + "tagCount": 0, + "userId": null + }, + { + "id": "vQK1urofrXc9VTjLn5Hj78", + "createDate": "2019-01-19 10:22:44", + "updateDate": "2019-01-19 10:22:44", + "deleteDate": null, + "tagName": "역사", + "tagCount": 0, + "userId": null + }, + { + "id": "sw3kASgddqbPVc3EgEZ9fE", + "createDate": "2019-01-19 10:22:44", + "updateDate": "2019-01-19 10:22:44", + "deleteDate": null, + "tagName": "연애", + "tagCount": 0, + "userId": null + }, + { + "id": "gKKzq5p4DvSKSo1ZdRpKLQ", + "createDate": "2019-01-19 10:22:44", + "updateDate": "2019-01-19 10:22:44", + "deleteDate": null, + "tagName": "이세계", + "tagCount": 0, + "userId": null + }, + { + "id": "tmdwkv3EcvwzqEdy84FkG3", + "createDate": "2019-01-19 10:22:44", + "updateDate": "2019-01-19 10:22:44", + "deleteDate": null, + "tagName": "사랑", + "tagCount": 0, + "userId": null + }, + { + "id": "7tAubjirK9oQB1pG21uzgX", + "createDate": "2019-01-19 10:22:44", + "updateDate": "2019-01-19 10:22:44", + "deleteDate": null, + "tagName": "일상", + "tagCount": 0, + "userId": null + }, + { + "id": "pTARgrRWK3yHs17qf5sfmj", + "createDate": "2019-01-19 10:22:44", + "updateDate": "2019-01-19 10:22:44", + "deleteDate": null, + "tagName": "호러", + "tagCount": 0, + "userId": null + } +] diff --git a/src/assets/json/meta-comments-tag.json b/src/assets/json/meta-comments-tag.json new file mode 100755 index 0000000..a093821 --- /dev/null +++ b/src/assets/json/meta-comments-tag.json @@ -0,0 +1,203 @@ +[{ + "code": "TAG_1", + "type": "article", + "styleClass": "001" + }, { + "code": "TAG_2", + "type": "article", + "styleClass": "001" + }, { + "code": "TAG_3", + "type": "article", + "styleClass": "001" + }, { + "code": "TAG_4", + "type": "article", + "styleClass": "001" + }, { + "code": "TAG_5", + "type": "article", + "styleClass": "001" + }, { + "code": "TAG_6", + "type": "article", + "styleClass": "002" + }, { + "code": "TAG_7", + "type": "article", + "styleClass": "002" + }, { + "code": "TAG_8", + "type": "article", + "styleClass": "002" + }, { + "code": "TAG_9", + "type": "article", + "styleClass": "002" + }, { + "code": "TAG_10", + "type": "article", + "styleClass": "002" + }, { + "code": "TAG_11", + "type": "article", + "styleClass": "003" + }, { + "code": "TAG_12", + "type": "article", + "styleClass": "003" + }, { + "code": "TAG_13", + "type": "article", + "styleClass": "003" + }, { + "code": "TAG_14", + "type": "article", + "styleClass": "003" + }, { + "code": "TAG_15", + "type": "article", + "styleClass": "003" + }, { + "code": "TAG_16", + "type": "article", + "styleClass": "004" + }, { + "code": "TAG_17", + "type": "article", + "styleClass": "004" + }, { + "code": "TAG_18", + "type": "article", + "styleClass": "004" + }, { + "code": "TAG_19", + "type": "article", + "styleClass": "004" + }, { + "code": "TAG_20", + "type": "article", + "styleClass": "004" + }, { + "code": "TAG_21", + "type": "article", + "styleClass": "005" + }, { + "code": "TAG_22", + "type": "article", + "styleClass": "005" + }, { + "code": "TAG_23", + "type": "article", + "styleClass": "005" + }, { + "code": "TAG_24", + "type": "article", + "styleClass": "005" + }, { + "code": "TAG_25", + "type": "article", + "styleClass": "005" + }, { + "code": "TAG_26", + "type": "author", + "styleClass": "006" + }, { + "code": "TAG_27", + "type": "author", + "styleClass": "006" + }, { + "code": "TAG_28", + "type": "author", + "styleClass": "006" + }, { + "code": "TAG_29", + "type": "author", + "styleClass": "006" + }, { + "code": "TAG_30", + "type": "author", + "styleClass": "006" + }, { + "code": "TAG_31", + "type": "author", + "styleClass": "007" + }, { + "code": "TAG_32", + "type": "author", + "styleClass": "007" + }, { + "code": "TAG_33", + "type": "author", + "styleClass": "007" + }, { + "code": "TAG_34", + "type": "author", + "styleClass": "007" + }, { + "code": "TAG_35", + "type": "author", + "styleClass": "007" + }, { + "code": "TAG_36", + "type": "author", + "styleClass": "008" + }, { + "code": "TAG_37", + "type": "author", + "styleClass": "008" + }, { + "code": "TAG_38", + "type": "author", + "styleClass": "008" + }, { + "code": "TAG_39", + "type": "author", + "styleClass": "008" + }, { + "code": "TAG_40", + "type": "author", + "styleClass": "008" + }, { + "code": "TAG_41", + "type": "author", + "styleClass": "009" + }, { + "code": "TAG_42", + "type": "author", + "styleClass": "009" + }, { + "code": "TAG_43", + "type": "author", + "styleClass": "009" + }, { + "code": "TAG_44", + "type": "author", + "styleClass": "009" + }, { + "code": "TAG_45", + "type": "author", + "styleClass": "009" + }, { + "code": "TAG_46", + "type": "author", + "styleClass": "010" + }, { + "code": "TAG_47", + "type": "author", + "styleClass": "010" + }, { + "code": "TAG_48", + "type": "author", + "styleClass": "010" + }, { + "code": "TAG_49", + "type": "author", + "styleClass": "010" + }, { + "code": "TAG_50", + "type": "author", + "styleClass": "010" + } + +] diff --git a/src/assets/json/user-analysis-favor-tag.json b/src/assets/json/user-analysis-favor-tag.json new file mode 100755 index 0000000..564857f --- /dev/null +++ b/src/assets/json/user-analysis-favor-tag.json @@ -0,0 +1,146 @@ +[{ + + "articleTagList": [{ + "id": "v7VrLnhyCHkx2DbyBpCeiG" + }], + "imageName": "BL.jpg" + }, + { + + "articleTagList": [{ + "id": "6LR3ibvURho9WJesizMdgW" + }], + "imageName": "GL.jpg" + }, + { + + "articleTagList": [{ + "id": "oVhoJA1AXsnRhcqvf2MdAe" + }], + "imageName": "SF.jpg" + }, + { + + "articleTagList": [{ + "id": "4YyEgYzFTqZKRwRBcpM5D7" + }], + "imageName": "개그.jpg" + }, + { + + "articleTagList": [{ + "id": "bjc7Esp7zAZdaGjLBkQUuF" + }], + "imageName": "도박.jpg" + }, + { + "articleTagList": [{ + "id": "qr3QYDaiXZBEJLuR1KJwYw" + }], + "imageName": "동물.png" + }, + { + "articleTagList": [{ + "id": "oczRnt8XnkzGYEimz68zVh" + }], + "imageName": "드라마.png" + }, + { + "articleTagList": [{ + "id": "4ky43VAHr7znWQFqtDHMVK" + }, + { + "id": "wr9mPE1i1RohAvZa7FYfzi" + } + ], + "imageName": "모험_판타지.png" + }, + { + "articleTagList": [{ + "id": "tZ5fZ7hqSLSx3c7gvai7Jc" + }], + "imageName": "미식.png" + }, + { + "articleTagList": [{ + "id": "jg2q8NwSx5GvGZtMpSCuvo" + }, + { + "id": "55BYnmgNRiFsw5fKoEhEiG" + } + ], + "imageName": "베틀_액션.png" + }, + { + "articleTagList": [{ + "id": "iBSWXWYGZPqhnpJVyNsymX" + }], + "imageName": "비즈니스.png" + }, + { + "articleTagList": [{ + "id": "ub35RRSJiPEDdjRUs9jPSx" + }, + { + "id": "925GDDnF3dYfBUifkFGKog" + } + ], + "imageName": "세기말_종말.png" + }, + { + "articleTagList": [{ + "id": "sRskhhDchmcEqZmDvxr5Gk" + }], + "imageName": "스포츠.jpg" + }, + { + "articleTagList": [{ + "id": "b1ZSC3msK95KK2oqxb3Po6" + }], + "imageName": "심쿵.png" + }, + { + "articleTagList": [{ + "id": "cAYKwQEZL98pm1Fr1JpkhY" + }], + "imageName": "아이돌.jpg" + }, + { + "articleTagList": [{ + "id": "vQK1urofrXc9VTjLn5Hj78" + }, + { + "id": "pjNG8MUo8XV5EKMqmCp87W" + } + ], + "imageName": "역사_시대극.jpg" + }, + { + "articleTagList": [{ + "id": "sw3kASgddqbPVc3EgEZ9fE" + }, + { + "id": "tmdwkv3EcvwzqEdy84FkG3" + } + ], + "imageName": "연애_사랑.jpg" + }, + { + "articleTagList": [{ + "id": "gKKzq5p4DvSKSo1ZdRpKLQ" + }], + "imageName": "이세계.jpg" + }, + { + "articleTagList": [{ + "id": "7tAubjirK9oQB1pG21uzgX" + }], + "imageName": "일상.png" + }, + { + "articleTagList": [{ + "id": "pTARgrRWK3yHs17qf5sfmj" + }], + "imageName": "호러.jpg" + } +] diff --git a/src/assets/key/private.key b/src/assets/key/private.key new file mode 100755 index 0000000..57e9b71 --- /dev/null +++ b/src/assets/key/private.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAuW0ffv829BIL1s5dpSzg2gowxSB5OBdz/oGF6I/FHlpKd8cU +cKJsIiDutQYBRXg0nPHLWvpSknx6GWcEunbtbj8DghLNrzMt2aJC5SQHm6+n2viw +NuNX90u1Zn8enzwxFL2Xbla+ztHOBBICbXWV6vrEjfIIPmvu2cGt2ZuaYb1xPx/v +bmUL5EgVkDo3GFdfUzP99shzBOkdcd5JyQK7VvJTsspspt2yNHhYjHSUO72/W4Ty +9/a6nOqeI2I57R/RgAAPdmyqD000ZxocTg6US4soXor+vPwysJppbbp3DFXJHgPT +r34Pe2cgy0eLk5Dn2uFj/MvLFQah2H85cKa29QIDAQABAoIBAQCj+5NNFpscwhDi +SOKcPtIjQbvbuVo6dtNIEnfAMdUbgLI2t0KmUZ3bmNBCGytoqmOtIFC9Bn+buKWq +bWbLGH3lb6jEkaiA9nvn6g3K41AvKOsDRcdg4zFaLAQivuzv2aV2OC0BkYlxEi5l +fx/SeZi5lEfbWF5eBcnXnecHeQrQv90J7zJxfaYmizTKwAt3RvhH+vJsAXtnXgdy +6+kQK2YlR41P0ZSzc71MbRjRnkOtYcBavNihgBQspT/vDGgwIGqVM3L9LerQi3bX +r9iAYUVGfnUYA67/LCMDvlVUr0e3NnuUlNm6Bu4A09HQUPla5oTBB03Y3ZBNZvWp +5B7n1bDJAoGBAOK7Sgxw/8te1+mPuW4hSxqN3gymlRkMTPnB1eOS/HhNT8wwaJ98 +nUpZ2pFenQTUpfxFbTs9EtFm0VUOHufEXOZKg5PwYrkBUC0GGEbVPXCTtnAeehXd +lv9Z+DSfoHQA4fIo1Nbws5YPVxMHwdnNJKZC8a1Vu1F9e6VM/pKYjb0XAoGBANFc +1RlKDnErTI5i3+ZJu2nDbBHqUZV9onF5uWxrxLQXinjG6YG8ZiinqQAjLvd6OI4S +R6dXap8wLHhnx5q4HdpI4rMfNQ9xBmL/+wEeob8pcits9zvPpe0ioX6rnG0cuNjl +gpl92XKhPnt7MefSa4QEqi+o5UWJscobo4ZYFyvTAoGASiAVG3AuEJ0XVcKpSvoT +hcDv4Y7sm93LpLPca6R4ahRMjGMfMVUaT16E+JXIG/YxtgI0rfNPnapRsc8GB5vJ +C3k8/zbN79IgFjgx46Z/ibihIpK0M0XYwe72GK4/VO2c8QCsZQEYlWy6ePxGQiCx +ZLHqNVMcBI5TyD0d1WuOm00CgYBI8eFFiGnzwDRSP9zm7bWgqfgTXeLDBRz+EH0s +a3gDj1gtsJBXJX8qlw/o/lk03J6r0W6pvWlmwXAdG3uOSqwMC+0An9Tq5mRlRe78 +euG8KwKeYr7ZVgXn03MVIVHFKoa0+3I/bHvA1rFgcJH4xXW0b7OkG2KWkd0iH/Mf +sb2RBQKBgBfCE0n3jtPjpxUQqZ5+0iP+8TigKSeGCq1wq53N1sgcZXVepMUPHiXX +KVwdbzFROyQ5C1k21ABPyhOxhWABOJq42PuboolDWNAR/sCmUytSM0vWYkkqe1Yx +r+M1S6mgtQeRqkyQuQ33KPKXshc2YXlu784Z7lGhD1gEsR99dS74 +-----END RSA PRIVATE KEY----- \ No newline at end of file diff --git a/src/assets/key/public.pem b/src/assets/key/public.pem new file mode 100755 index 0000000..49dc9fd --- /dev/null +++ b/src/assets/key/public.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuW0ffv829BIL1s5dpSzg +2gowxSB5OBdz/oGF6I/FHlpKd8cUcKJsIiDutQYBRXg0nPHLWvpSknx6GWcEunbt +bj8DghLNrzMt2aJC5SQHm6+n2viwNuNX90u1Zn8enzwxFL2Xbla+ztHOBBICbXWV +6vrEjfIIPmvu2cGt2ZuaYb1xPx/vbmUL5EgVkDo3GFdfUzP99shzBOkdcd5JyQK7 +VvJTsspspt2yNHhYjHSUO72/W4Ty9/a6nOqeI2I57R/RgAAPdmyqD000ZxocTg6U +S4soXor+vPwysJppbbp3DFXJHgPTr34Pe2cgy0eLk5Dn2uFj/MvLFQah2H85cKa2 +9QIDAQAB +-----END PUBLIC KEY----- \ No newline at end of file diff --git a/src/assets/seed/article.json b/src/assets/seed/article.json new file mode 100755 index 0000000..04a40ea --- /dev/null +++ b/src/assets/seed/article.json @@ -0,0 +1,15 @@ +[{ + "id": "1", + "name": "Article 1", + "description": "Description 1", + "image": "../../../../assets/img/examples/portfolio1.jpg", + "owner": "member11" + }, + { + "id": "2", + "name": "Article 2", + "description": "Description 1", + "image": "../../../../assets/img/examples/portfolio2.png", + "owner": "member22" + } +] diff --git a/src/assets/seed/member.json b/src/assets/seed/member.json new file mode 100755 index 0000000..cc05b4e --- /dev/null +++ b/src/assets/seed/member.json @@ -0,0 +1,15 @@ +[{ + "id": "1", + "userId": "member11", + "userPw": "tksoqpscj3gh", + "nickName": "nickName1", + "image": "../../../../assets/img/examples/avata-01.jpg" + }, + { + "id": "2", + "userId": "member22", + "userPw": "tksoqpscj3gh", + "nickName": "nickName2", + "image": "../../../../assets/img/examples/avata-02.png" + } +] diff --git a/src/assets/seed/project.json b/src/assets/seed/project.json new file mode 100755 index 0000000..7357709 --- /dev/null +++ b/src/assets/seed/project.json @@ -0,0 +1,36 @@ +[{ + "id": "1", + "name": "Project 1", + "owner": "Owner 1" + }, + { + "id": "2", + "name": "Project 2", + "owner": "Owner 2" + }, + { + "id": "3", + "name": "Project 3", + "owner": "Owner 3" + }, + { + "id": "4", + "name": "Project 4", + "owner": "Owner 4" + }, + { + "id": "5", + "name": "Project 5", + "owner": "Owner 5" + }, + { + "id": "6", + "name": "Project 6", + "owner": "Owner 6" + }, + { + "id": "7", + "name": "Project 7", + "owner": "Owner 7" + } +] diff --git a/src/assets/styles/_api-theme.scss b/src/assets/styles/_api-theme.scss new file mode 100755 index 0000000..e864b38 --- /dev/null +++ b/src/assets/styles/_api-theme.scss @@ -0,0 +1,34 @@ +@import '../../../node_modules/@angular/material/theming'; + +// Mixin to apply theme colors for generated API docs. +@mixin docs-site-api-theme($theme) { + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + $is-dark-theme: map-get($theme, is-dark); + + .docs-api-method-returns-type, + .docs-api-method-parameter-type { + color: mat-color($primary, darker); + } + + // Force the top-level API docs header to be hidden, since this is already + // captured in the top nav-bar. + .docs-api-h1 { + display: none !important; + } + + // Prevent p tags from not breaking, causing x axis overflows. + .docs-api>p { + word-break: break-word; + } + + .docs-api-class-name, + .docs-api-module-import, + .docs-api-class-selector-name, + .docs-api-class-export-name { + background: rgba(mat-color($foreground, secondary-text), 0.06); + } +} diff --git a/src/assets/styles/_api.scss b/src/assets/styles/_api.scss new file mode 100755 index 0000000..2f90b2d --- /dev/null +++ b/src/assets/styles/_api.scss @@ -0,0 +1,86 @@ +// Styles for API docs generated via dgeni from material2 source JsDocs. + +// Top header, e.g., "API documentation for dialog". +.docs-api-h2 { + font-size: 30px; +} + +// Section header, e.g. "Services" or "Directives" +.docs-api-h3 { + font-size: 24px; + font-weight: 400; + margin-top: 45px; +} + +.docs-api-h4 { + font-size: 18px; + font-weight: 400; +} + +.docs-api-class-description { + font-size: 12px; +} +.docs-api-property-name { + margin: 0; +} + +.docs-api-method-name-row, +.docs-api-method-parameter-row, +.docs-api-properties-name-cell { + font-family: 'Roboto Mono', monospace; + font-weight: 600; +} + +.docs-api-properties-name-cell, +.docs-api-method-parameter-row { + font-size: 14px; +} + +.docs-api-method-parameter-type { + font-size: 12px; +} + +.docs-api-class-name, +.docs-api-module-import { + display: inline; +} + +.docs-api-method-name-cell { + font-weight: 700; + font-size: 18px; +} + +.docs-api-method-parameters-header-cell, +.docs-api-method-returns-header-cell { + font-size: 14px; +} + +.docs-api-input-marker, +.docs-api-output-marker, +.docs-api-deprecated-marker { + // Size corresponding to "caption"-style text in the spec. + font-size: 12px; +} + +.docs-api-module-import, +.docs-api-class-selector-name, +.docs-api-class-export-name { + font-family: 'Roboto Mono', monospace; + padding: 3px; +} + +.docs-api-deprecated-marker, +.docs-api-class-deprecated-marker, +.docs-api-interface-deprecated-marker { + display: inline-block; + font-weight: bold; + + &[title] { + border-bottom: 1px dotted grey; + cursor: help; + } +} + +.docs-api-deprecated-marker + .docs-api-property-name { + text-decoration: line-through; +} diff --git a/src/assets/styles/_constants.scss b/src/assets/styles/_constants.scss new file mode 100755 index 0000000..2e32891 --- /dev/null +++ b/src/assets/styles/_constants.scss @@ -0,0 +1,7 @@ +@import '../../../node_modules/@angular/material/theming'; + +$small-breakpoint-width: 720px; + +/* For desktop, the content should be aligned with the page title. */ +$content-padding-side: 70px; +$content-padding-side-xs: 15px; diff --git a/src/assets/styles/_general.scss b/src/assets/styles/_general.scss new file mode 100755 index 0000000..aff43bf --- /dev/null +++ b/src/assets/styles/_general.scss @@ -0,0 +1,55 @@ +body { + font-family: "Roboto","Helvetica Neue Light","Helvetica Neue",Helvetica,Arial,"Lucida Grande",sans-serif; + margin: 0; +} + +.docs-button[md-button], .docs-button[md-raised-button] { + text-transform: uppercase; +} + +h1, h2 { + font-weight: 400; +} + +.docs-primary-header { + padding-left: 20px; + + h1 { + font-size: 30px; + font-weight: 300; + margin: 0; + padding: 28px 8px; + font-size: 20px; + } +} + +code { + font-size: 90%; +} + +.docs-markdown-pre code { + font-size: 100%; +} + +// These styles are for controlling SVGs without using the /deep/ selector + +.docs-component-category-list-card-image svg { + width: 100%; +} + +.docs-footer-angular-logo svg { + height: 50px; +} + +.docs-angular-logo svg { + height: 26px; + margin: 0 4px 3px 0; + vertical-align: middle; +} + +.docs-github-logo svg { + height: 21px; + margin: 0 7px 2px 0; + vertical-align: middle; +} + diff --git a/src/assets/styles/_markdown-theme.scss b/src/assets/styles/_markdown-theme.scss new file mode 100755 index 0000000..8c33904 --- /dev/null +++ b/src/assets/styles/_markdown-theme.scss @@ -0,0 +1,34 @@ +@import '../../../node_modules/@angular/material/theming'; + +// Mixin to apply theme colors for docs generated from markdown files in the material2 repo. +@mixin docs-site-markdown-theme($theme) { + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + $is-dark-theme: map-get($theme, is-dark); + $exportBackgroundOpacity: if($is-dark-theme, 0.06, 0.03); + + .docs-markdown-a { + color: mat-color($primary); + } + + .docs-markdown-pre { + background: rgba(mat-color($foreground, secondary-text), .01); + border: .5px solid rgba(mat-color($foreground, secondary-text), .03); + + .docs-markdown-code { + background: transparent; + } + } + + .docs-markdown-h3 .material-icons, + .docs-markdown-h4 .material-icons { + color: mat-color($foreground, secondary-text); + } + + .docs-markdown-code { + background: rgba(mat-color($foreground, secondary-text), $exportBackgroundOpacity); + } +} diff --git a/src/assets/styles/_markdown.scss b/src/assets/styles/_markdown.scss new file mode 100755 index 0000000..f2a8825 --- /dev/null +++ b/src/assets/styles/_markdown.scss @@ -0,0 +1,87 @@ +// Styles for overview and guide docs generated via `marked` from the material2 repo. + +.docs-markdown { + max-width: 100%; +} + +.docs-markdown-h1 { + display: inline-block; + font-size: 34px; + font-weight: 400; + padding: 5px; +} + +.docs-markdown-h2 { + font-size: 24px; +} + +.docs-markdown-h3 { + font-size: 20px; +} + +.docs-markdown-h2, +.docs-markdown-h4 { + margin-top: 40px; +} + +.docs-markdown-h5 { + font-size: 18px; +} + +.docs-markdown-p, +.docs-markdown-ul, +.docs-markdown-ol { + font-size: 16px; + line-height: 28px; +} + +.docs-markdown-a { + text-decoration: none; +} + +.docs-markdown-td code { + font-size: 14px; +} + +.docs-markdown-pre { + border-radius: 5px; + display: block; + margin: 16px auto; + overflow-x: auto; + padding: 20px; + white-space: pre-wrap; + + .docs-markdown-code { + padding: 0; + } +} + +.docs-markdown-code { + padding: 3px; +} + +code, pre { + font-family: 'Roboto Mono', monospace; +} + +pre { + font-size: 14px; +} + +.docs-header-link { + a { + text-decoration: none; + // deduct -30px so the anchor icon will be positioned outside the content + margin-left: -30px; + display: inline-block; + vertical-align: middle; + } + + .material-icons { + visibility: hidden; + } + + &:hover .material-icons { + visibility: visible; + } +} diff --git a/src/assets/styles/_svg-theme.scss b/src/assets/styles/_svg-theme.scss new file mode 100755 index 0000000..d8b3111 --- /dev/null +++ b/src/assets/styles/_svg-theme.scss @@ -0,0 +1,42 @@ +// @import '~@angular/material/theming'; +@import '../../../node_modules/@angular/material/theming'; + +// Mixin to create a css class for each fill, stroke, and stop-color for a given color. +@mixin _svgColorProperties($className, $color) { + .docs-svg-#{$className}-fill { + fill: $color; + } + + .docs-svg-#{$className}-stroke { + stroke: $color; + } + + .docs-svg-#{$className}-stop-color { + stop-color: $color; + } +} + +// Mixin to apply theme colors for generated API docs. +@mixin docs-site-svg-theme($theme) { + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + $is-dark-theme: map-get($theme, is-dark); + + @include _svgColorProperties(primary, mat-color($primary)); + @include _svgColorProperties(accent, mat-color($accent)); + + // Use sass's `lighten` and `darken` to create gradient colors for the docs SVGs. + // This is specifically tailored to the SVGs in this app and is not a general approach. + $base: mat-color($primary); + @include _svgColorProperties(primary-dark-30, scale_color($base, $lightness: -30%)); + @include _svgColorProperties(primary-dark-40, scale_color($base, $lightness: -40%)); + @include _svgColorProperties(primary-light-20, scale_color($base, $lightness: +20%)); + @include _svgColorProperties(primary-light-30, scale_color($base, $lightness: +30%)); + @include _svgColorProperties(primary-light-40, scale_color($base, $lightness: +40%)); + @include _svgColorProperties(primary-light-60, scale_color($base, $lightness: +60%)); + @include _svgColorProperties(primary-light-80, scale_color($base, $lightness: +80%)); + @include _svgColorProperties(primary-light-85, scale_color($base, $lightness: +85%)); +} diff --git a/src/assets/styles/_tables-theme.scss b/src/assets/styles/_tables-theme.scss new file mode 100755 index 0000000..05c9277 --- /dev/null +++ b/src/assets/styles/_tables-theme.scss @@ -0,0 +1,27 @@ +@import '../../../node_modules/@angular/material/theming'; + +// Mixin to apply theme colors for both generated API docs and markdown docs (guides/overviews). +@mixin docs-site-tables-theme($theme) { + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + $is-dark-theme: map-get($theme, is-dark); + $tableBorderOpacity: if($is-dark-theme, 0.08, 0.03); + + .docs-api table, + .docs-markdown-table { + color: mat-color($foreground, text); + } + + .docs-api th, + .docs-markdown-th { + background: mat-color($background, app-bar); + } + + .docs-api td, + .docs-markdown-td { + border: 1px solid rgba(mat-color($foreground, secondary-text), $tableBorderOpacity); + } +} diff --git a/src/assets/styles/_tables.scss b/src/assets/styles/_tables.scss new file mode 100755 index 0000000..94f4729 --- /dev/null +++ b/src/assets/styles/_tables.scss @@ -0,0 +1,43 @@ +@import './constants'; + +.docs-api table, +.docs-markdown-table { + border-collapse: collapse; + border-radius: 2px; + border-spacing: 0; + margin: 0 0 32px 0; + width: 100%; + box-shadow: 0 2px 2px rgba(0, 0, 0, 0.24), 0 0 2px rgba(0, 0, 0, 0.12); +} + +.docs-api th, +.docs-markdown-th { + font-weight: 400; + max-width: 100px; + padding: 13px 32px; + text-align: left; +} + +.docs-api td, +.docs-markdown-td { + font-weight: 400; + padding: 8px 16px; +} + + +@media (max-width: $small-breakpoint-width) { + .docs-api table, + .docs-markdown-table { + margin: 0 0 32px 0; + } + + .docs-api th, + .docs-markdown-th { + padding: 6px 16px; + } + + .docs-api td, + .docs-markdown-td { + padding: 4px 8px; + } +} diff --git a/src/browserslist b/src/browserslist new file mode 100755 index 0000000..f2cecda --- /dev/null +++ b/src/browserslist @@ -0,0 +1,11 @@ +# This file is currently used by autoprefixer to adjust CSS to support the below specified browsers +# For additional information regarding the format and rule options, please see: +# https://github.com/browserslist/browserslist#queries +# +# For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed + +> 0.5% +last 2 versions +Firefox ESR +not dead +not IE 9-11 \ No newline at end of file diff --git a/src/config/contents/cartoons.config.ts b/src/config/contents/cartoons.config.ts new file mode 100755 index 0000000..ba3095f --- /dev/null +++ b/src/config/contents/cartoons.config.ts @@ -0,0 +1,98 @@ +import { ContentsSize } from '../../shared/contents/model/contents-size.model'; +import { ContentsType } from '../../shared/contents/type/contents-type.type'; +import { ContentsDisplay } from '../../shared/contents/type/contents-display.type'; +import { ContentsRequest } from '../../shared/contents/type/contents-request.type'; +import { ContentsRequestUser } from '../../shared/contents/type/contents-request-user.type'; + +export const CartoonsContentsSizeMap: Map> = new Map([ + [ + ContentsRequest.Recommendations, new Map([ + [ + ContentsRequestUser.Guest, [ + { + type: ContentsType.Article, + displayOption: { + type: ContentsDisplay.Card, + }, + size: 10, + }, + { type: ContentsType.FeaturedAuthor, size: 6 }, + { type: ContentsType.Advertisements, size: 1 }, + { type: ContentsType.Event, size: 3 }, + { + type: ContentsType.Article, + displayOption: { + type: ContentsDisplay.Grid, + option: { + colSize: 2, + }, + }, + size: 6, + }, + { + type: ContentsType.Article, + displayOption: { + type: ContentsDisplay.Grid, + option: { + colSize: 3, + }, + }, + size: 12, + }, + ] + ], + [ + ContentsRequestUser.User, [ + { + type: ContentsType.Article, + displayOption: { + type: ContentsDisplay.Card, + }, + size: 10, + }, + { type: ContentsType.FeaturedAuthor, size: 6 }, + { type: ContentsType.Advertisements, size: 1 }, + { type: ContentsType.Event, size: 3 }, + { + type: ContentsType.Article, + displayOption: { + type: ContentsDisplay.Grid, + option: { + colSize: 2, + }, + }, + size: 6, + }, + { + type: ContentsType.Article, + displayOption: { + type: ContentsDisplay.Grid, + option: { + colSize: 3, + }, + }, + size: 12, + }, + ] + ] + ]) + ], + [ + ContentsRequest.Following, new Map([ + [ + ContentsRequestUser.User, [ + { + type: ContentsType.Article, + displayOption: { + type: ContentsDisplay.Card, + }, + size: 10, + }, + { type: ContentsType.FeaturedAuthor, size: 6 }, + { type: ContentsType.Advertisements, size: 1 }, + { type: ContentsType.Event, size: 3 }, + ] + ] + ]) + ] +]); diff --git a/src/config/contents/index.ts b/src/config/contents/index.ts new file mode 100755 index 0000000..152c7c4 --- /dev/null +++ b/src/config/contents/index.ts @@ -0,0 +1,45 @@ +import { ContentsSize } from '../../shared/contents/model/contents-size.model'; +import { ContentsRequest } from '../../shared/contents/type/contents-request.type'; +import { ContentsRequestUser } from '../../shared/contents/type/contents-request-user.type'; +import { ContentsSearch } from '../../shared/contents/type/contents-search.type'; +import { ArticleType } from '../../shared/article/type/article-type.type'; + +import { CartoonsContentsSizeMap } from './cartoons.config'; +import { SearchContentsSizeMap } from './search.config'; + +const articleContentsSizeMap: Map>> = new Map([ + [ + ArticleType.Cartoons, CartoonsContentsSizeMap + ] +]); + + +const searchContentsSizeMap: Map = SearchContentsSizeMap; + + +export function getArticleContentsSize( + articleType: ArticleType, + contentsRequest: ContentsRequest, + contentsRequestUser: ContentsRequestUser +): ContentsSize[] { + if (!articleContentsSizeMap.has(articleType)) { + throw new Error(`config of contentsSize[article:${articleType}] is not exist`); + } + if (!articleContentsSizeMap.get(articleType).has(contentsRequest)) { + throw new Error(`config of contentsSize[article:${articleType}, contentsRequest:${contentsRequest}] is not exist`); + } + if (!articleContentsSizeMap.get(articleType).get(contentsRequest).has(contentsRequestUser)) { + // tslint:disable-next-line:max-line-length + throw new Error(`config of contentsSize[article:${articleType}, contentsRequest:${contentsRequest}, contentsRequestUser:${contentsRequestUser}] is not exist`); + } + return articleContentsSizeMap.get(articleType).get(contentsRequest).get(contentsRequestUser); +} + +export function getSearchContentsSize( + contentsSearch: ContentsSearch +): ContentsSize[] { + if (!searchContentsSizeMap.has(contentsSearch)) { + throw new Error(`config of contentsSize[search:${contentsSearch}] is not exist`); + } + return searchContentsSizeMap.get(contentsSearch); +} diff --git a/src/config/contents/search.config.ts b/src/config/contents/search.config.ts new file mode 100755 index 0000000..d5bed80 --- /dev/null +++ b/src/config/contents/search.config.ts @@ -0,0 +1,41 @@ +import { ContentsSize } from '../../shared/contents/model/contents-size.model'; +import { ContentsType } from '../../shared/contents/type/contents-type.type'; +import { ContentsDisplay } from '../../shared/contents/type/contents-display.type'; +import { ContentsSearch } from '../../shared/contents/type/contents-search.type'; + +export const SearchContentsSizeMap: Map = new Map([ + [ + ContentsSearch.Tag, [ + { + type: ContentsType.Article, + displayOption: { + type: ContentsDisplay.Card, + }, + size: 10, + }, + { type: ContentsType.FeaturedAuthor, size: 6 }, + { type: ContentsType.Advertisements, size: 1 }, + { type: ContentsType.Event, size: 3 }, + { + type: ContentsType.Article, + displayOption: { + type: ContentsDisplay.Grid, + option: { + colSize: 2, + }, + }, + size: 6, + }, + { + type: ContentsType.Article, + displayOption: { + type: ContentsDisplay.Grid, + option: { + colSize: 3, + }, + }, + size: 12, + }, + ] + ] +]); diff --git a/src/config/images/article.config.ts b/src/config/images/article.config.ts new file mode 100755 index 0000000..0c8385d --- /dev/null +++ b/src/config/images/article.config.ts @@ -0,0 +1,56 @@ +import { ImageConfig } from '../../shared/attachments/type/image-config.type'; +import { ArticleImage } from '../../shared/article/type/article-image.type'; +import { ArticleDisplay } from '../../shared/article/type/article-display.type'; + +export const articleImageConfig: Map = new Map([ + [ + ArticleImage.Cover, + { + validation: { + width: 690, + height: 400 + }, + thumbnails: new Map([ + [ + ArticleDisplay.Card, + { + width: 600, + height: 400 + } + ], + [ + ArticleDisplay.Tile3, + { + width: 200, + height: 200 + } + ], + [ + ArticleDisplay.Tile2, + { + width: 300, + height: 300 + } + ] + ]) + } + ], + [ + ArticleImage.Contents, + { + validation: { + width: 690, + height: 150 + }, + thumbnails: new Map([ + [ + ArticleDisplay.Viewer, + { + width: 600, + height: -1, + } + ] + ]), + } + ] +]); diff --git a/src/config/images/index.ts b/src/config/images/index.ts new file mode 100755 index 0000000..063070a --- /dev/null +++ b/src/config/images/index.ts @@ -0,0 +1,43 @@ +import { articleImageConfig } from './article.config'; +import { userImageConfig } from './user.config'; +import { ImageConfig } from '../../shared/attachments/type/image-config.type'; +import { ImageThumbnails, ImageValidation } from '../../shared/attachments/type/image.type'; + +const imageConfig: Map> = new Map([ + ['article', articleImageConfig], + ['user', userImageConfig], +]); + + +export const imageValidationConfig = (type: string, imageType: string): ImageValidation => { + if (!imageConfig.has(type)) { + throw new Error(`Image config of ${type} is not exist.`); + } + + const configMap = imageConfig.get(type); + + if (!configMap.has(imageType)) { + throw new Error(`ImageType[${imageType}] config of ${type} is not exist.`); + } + + return configMap.get(imageType).validation; +}; + +export const imageThumbnailsConfig = (type: string, imageType: string, displayType: string): ImageThumbnails => { + if (!imageConfig.has(type)) { + throw new Error(`Image config of ${type} is not exist.`); + } + + const configMap = imageConfig.get(type); + + if (!configMap.has(imageType)) { + throw new Error(`ImageType[${imageType}] config of ${type} is not exist.`); + } + + const thumbnailsConfigList = configMap.get(imageType).thumbnails; + + if (!thumbnailsConfigList.has(displayType)) { + throw new Error(`thumbnails[${displayType}] ImageType[${imageType}] config of ${type} is not exist.`); + } + return thumbnailsConfigList.get(displayType); +}; diff --git a/src/config/images/user.config.ts b/src/config/images/user.config.ts new file mode 100755 index 0000000..cde77f2 --- /dev/null +++ b/src/config/images/user.config.ts @@ -0,0 +1,56 @@ +import { ImageConfig } from '../../shared/attachments/type/image-config.type'; +import { UserImage } from '../../shared/user/type/user-image.type'; +import { UserDisplay } from '../../shared/user/type/user-display.type'; + +export const userImageConfig: Map = new Map([ + [ + UserImage.Profile, + { + validation: { + width: 150, + height: 150 + }, + thumbnails: new Map([ + [ + UserDisplay.Article, + { + width: 30, + height: 30 + } + ], + [ + UserDisplay.Profile, + { + width: 77, + height: 77 + } + ], + ]) + } + ], + [ + UserImage.Background, + { + validation: { + width: 690, + height: 120 + }, + thumbnails: new Map([ + [ + UserDisplay.Profile, + { + width: 600, + height: 120 + } + ], + [ + UserDisplay.Recommendations, + { + width: 320, + height: 214 + } + ], + ]) + } + ] +]); diff --git a/src/custom.scss b/src/custom.scss new file mode 100755 index 0000000..3d6522a --- /dev/null +++ b/src/custom.scss @@ -0,0 +1,149 @@ +// Custom color Define +$commonWhiteBG: #fff; +$commonBorder: #e7e7e7; +$commonBorder2: #d0d0d0; +$commonActiveColor:#000; +$commonPhaseColor:#767676; +$commoniconColor:#989898; +$commonTabBG : #f7f7f7; + +// temporary change theme Background Color. this will delete when fixed the Theme Color +$matPrimCustum2: #4494e5; +$matsecondCustum2: #673ab7; +$matAccentCustum2: #ffd740; +$matWarnCustum2: #f44336; +$matdisabledCustum2: #e7e7e7; + +// Custom Color palette +$mat-custom: (50: #e8eaf6, +100: #c5cae9, +200: #9fa8da, +300: #7986cb, +400: #5c6bc0, +500: #509ccd, +600: #3949ab, +700: #303f9f, +800: #283593, +900: #1a237e, +A100: #8c9eff, +A200: #536dfe, +A400: #3d5afe, +A700: #304ffe, +contrast: (50: $dark-primary-text, +100: $dark-primary-text, +200: $dark-primary-text, +300: $light-primary-text, +400: $light-primary-text, +500: $light-primary-text, +600: $light-primary-text, +700: $light-primary-text, +800: $light-primary-text, +900: $light-primary-text, +A100: $dark-primary-text, +A200: $light-primary-text, +A400: $light-primary-text, +A700: $light-primary-text, +)); + +// Alias for alternate spelling. +$mat-custom-gray: $mat-custom; +/*(50: #fafafa, +100: #f5f5f5, +200: #eeeeee, +300: #e0e0e0, +400: #bdbdbd, +500: #f9f9f9, +600: #757575, +700: #616161, +800: #424242, +900: #212121, +A100: #ffffff, +A200: #eeeeee, +A400: #bdbdbd, +A700: #616161, +contrast: (50: $dark-primary-text, +100: $dark-primary-text, +200: $dark-primary-text, +300: $dark-primary-text, +400: $dark-primary-text, +500: $dark-primary-text, +600: $light-primary-text, +700: $light-primary-text, +800: $light-primary-text, +900: $light-primary-text, +A100: $dark-primary-text, +A200: $dark-primary-text, +A400: $dark-primary-text, +A700: $light-primary-text, +) +*/ + +// Background palette for light themes. +$mat-light-theme-background: (status-bar: map_get($mat-custom, 300), +app-bar: map_get($mat-custom, 100), +background: map_get($mat-custom, 50), +hover: rgba(black, 0.04), // TODO(kara): check style with Material Design UX +card: white, +dialog: white, +disabled-button: rgba(black, 0.12), +raised-button: white, +focused-button: $dark-focused, +selected-button: map_get($mat-custom, 300), +selected-disabled-button: map_get($mat-custom, 400), +disabled-button-toggle: map_get($mat-custom, 200), +unselected-chip: map_get($mat-custom, 300), +disabled-list-option: map_get($mat-custom, 200), +); + +// Background palette for dark themes. +$mat-dark-theme-background: (status-bar: black, +app-bar: map_get($mat-custom, 900), +background: #303030, +hover: rgba(white, 0.04), // TODO(kara): check style with Material Design UX +card: map_get($mat-custom, 800), +dialog: map_get($mat-custom, 800), +disabled-button: rgba(white, 0.12), +raised-button: map-get($mat-custom, 800), +focused-button: $light-focused, +selected-button: map_get($mat-custom, 900), +selected-disabled-button: map_get($mat-custom, 800), +disabled-button-toggle: black, +unselected-chip: map_get($mat-custom, 700), +disabled-list-option: black, +); + +// Foreground palette for light themes. +$mat-light-theme-foreground: (base: black, +divider: $dark-dividers, +dividers: $dark-dividers, +disabled: $dark-disabled-text, +disabled-button: rgba(black, 0.26), +disabled-text: $dark-disabled-text, +elevation: black, +hint-text: $dark-disabled-text, +secondary-text: $dark-secondary-text, +icon: rgba(black, 0.54), +icons: rgba(black, 0.54), +text: rgba(black, 0.87), +slider-min: rgba(black, 0.87), +slider-off: rgba(black, 0.26), +slider-off-active: rgba(black, 0.38), +); + +// Foreground palette for dark themes. +$mat-dark-theme-foreground: (base: white, +divider: $light-dividers, +dividers: $light-dividers, +disabled: $light-disabled-text, +disabled-button: rgba(white, 0.3), +disabled-text: $light-disabled-text, +elevation: black, +hint-text: $light-disabled-text, +secondary-text: $light-secondary-text, +icon: white, +icons: white, +text: white, +slider-min: white, +slider-off: rgba(white, 0.3), +slider-off-active: rgba(white, 0.3), +); diff --git a/src/customDialog.scss b/src/customDialog.scss new file mode 100755 index 0000000..30cb7af --- /dev/null +++ b/src/customDialog.scss @@ -0,0 +1,375 @@ +// mat-dialog +@mixin calc($property, $expression) { + #{$property}: -moz-calc(#{$expression}); + #{$property}: -webkit-calc(#{$expression}); + #{$property}: calc(#{$expression}); +} + +// circle icon +@mixin icon-circle($scale, $iconScale, $bgColor, $fillColor) { + width: $scale+px !important; + height: $scale+px !important; + background-color: $bgColor; + fill: $fillColor !important; + border-radius: 50%; + display: flex !important; + align-items: center; + justify-content: center; + + svg { + width: $iconScale+0%; + height: $iconScale+0%; + } +} + +// snackbar +.mat-snack-bar-container { + .sb-wrap { + display: flex; + flex-direction: column; + } + + b { + display: flex; + margin: auto 0; + align-items: center; + + .mat-cion { + margin-right: 5px; + } + } + + .title-failure { + color: $matWarnCustum2; + } + + .title-success { + color: $matAccentCustum2; + } + + .notice { + color: $matPrimCustum2; + display: flex; + margin: auto 0; + padding-top: 10px; + } +} + +// modal pop +.mat-dialog-container { + border-radius: 10px !important; + + //for unfollow + .unfollow { + text-align: center; + + .size100 { + height: 100%; + width: 100%; + + } + + .avatarInit { + height: 75px; + width: 75px; + margin: 0 auto; + } + + .avatar { + border-radius: 50%; + border: solid 1px #fff; + object-fit: cover; + background: url('../src/assets/img/examples/avata.jpg') no-repeat center center; + background-size: contain; + overflow: hidden; + white-space: nowrap; + @extend .avatarInit; + margin-bottom: 20px; + } + + .user-info-header-image { + @extend .avatarInit; + @extend .size100; + + img { + @extend .size100; + } + } + + .notice { + font-size: 0.96em; + line-height: 1.5em; + + .nickname { + font-weight: bold; + } + } + + .mat-dialog-content { + padding: 0 + } + } + + // for reply + .select-of-Reply { + margin: 20px; + position: relative; + top: 50px; + + .mat-chip-list-wrapper { + text-align: center; + display: block; + flex-direction: unset; + } + } + + // viewer-article + .viewer-article { + width: 100%; + display: block; + + .banout { + position: fixed; + top: 40px; + right: 20px; + } + + img { + width: 100%; + } + + .card-data { + margin: 10px 20px; + } + + .viewer-tool { + position: fixed; + left: 0; + padding: 0 20px; + width: calc(100vw - 40px); + height: 50px; + + .card-button { + height: 50px; + } + } + + .viewer-header { + top: 0; + @extend .viewer-tool; + background: rgba(255, 255, 255, 0.9); + + .fl-left { + width: calc(100vw - 50px); + display: block; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + } + + .card-title { + display: block; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + } + + .viewer-reactarea { + bottom: 0; + @extend .viewer-tool; + background: rgba(255, 255, 255, 0.9); + } + } + + .button-farm { + margin: 0; + } + + .mat-stroked-button.mat-primary { + color: $matPrimCustum2; + } + + .mat-flat-button.mat-primary { + background-color: $matPrimCustum2; + color: #fff; + } + + .mat-flat-button.mat-disabled, + .mat-flat-button.mat-primary[disabled], + .mat-flat-button.mat-accent[disabled], + .mat-flat-button.mat-warn[disabled], + .mat-flat-button[disabled][disabled], + .mat-raised-button.mat-primary[disabled], + .mat-raised-button.mat-accent[disabled], + .mat-raised-button.mat-warn[disabled], + .mat-raised-button[disabled][disabled], + .mat-fab.mat-primary[disabled], + .mat-fab.mat-accent[disabled], + .mat-fab.mat-warn[disabled], + .mat-fab[disabled][disabled], + .mat-mini-fab.mat-primary[disabled], + .mat-mini-fab.mat-accent[disabled], + .mat-mini-fab.mat-warn[disabled], + .mat-mini-fab[disabled][disabled] { + background-color: $matdisabledCustum2; + color: #000; + } + + .mat-flat-button.mat-secondary { + background-color: $matsecondCustum2; + color: #fff; + } + + .mat-flat-button.mat-accent { + background-color: $matAccentCustum2; + color: #000; + } + + .mat-flat-button.mat-warn { + background-color: $matWarnCustum2; + color: #fff; + } + + .confirm-card, + .example-card { + box-shadow: none !important; + padding: 0; + border-radius: 0; + + .mat-card-header-text { + margin: 0; + + .mat-card-title { + color: $matWarnCustum2; + } + + .mat-card-subtitle { + display: none; + } + } + + .notice { + font-size: 0.95em + } + + .button-farm.flex-column { + button { + width: 100%; + } + } + + .flex-column { + display: flex; + flex-direction: column; + + button { + margin: 5px auto; + } + } + + .flex-row { + display: flex; + flex-direction: row; + + button { + margin: auto; + } + } + } + + // align + .txL { + text-align: left; + } + + .txC { + text-align: center; + } + + .txR { + text-align: right; + } + + // margin + .mt10 { + margin-top: 10px; + } + + .ml10 { + margin-left: 10px; + } + + .mr10 { + margin-right: 10px; + } + +} + +// media dialog +// full screen dialog +.app-full-screen-dialog .mat-dialog-container { + position: relative; + width: 100vw; + height: 100vh; + left: 50%; + right: 50%; + margin-left: -50vw; + margin-right: -50vw; + border-radius: 0px !important; + padding: 0px; +} + +// mat-bottom-sheet +.mat-bottom-sheet-container { + min-width: calc(100vw - 26px) !important; + border-radius: 10px 10px 0 0 !important; + padding: 16px !important; +} + +// share bottom sheet +.share-sheet { + a { + cursor: default; + } + + .icon-circle.share-facebook { + @include icon-circle(36, 95, #6986c0, $commonWhiteBG); + } + + .icon-circle.share-twitter { + @include icon-circle(36, 85, #38a1ce, $commonWhiteBG); + } + + .icon-circle.share-common { + @include icon-circle(36, 95, #eeeeee, #adadad); + } + + .icon-circle.share-naver { + @include icon-circle(36, 75, #01c73c, $commonWhiteBG); + } + + .icon-circle.share-kakao { + @include icon-circle(36, 75, #e6d000, $commonWhiteBG); + } + + .icon-circle.share-google { + @include icon-circle(36, 75, #679bf1, $commonWhiteBG); + } + + .icon-circle.share-line { + @include icon-circle(36, 75, #01c73c, $commonWhiteBG); + } + + .icon-circle.share-weibo { + @include icon-circle(36, 75, #ef5f5f, $commonWhiteBG); + } + + .icon-circle.share-instagram { + @include icon-circle(36, 75, #c389de, $commonWhiteBG); + } + + .icon-circle.share-googlePlus { + @include icon-circle(36, 95, #db4437, $commonWhiteBG); + } +} diff --git a/src/customUpload.scss b/src/customUpload.scss new file mode 100755 index 0000000..b915748 --- /dev/null +++ b/src/customUpload.scss @@ -0,0 +1,195 @@ +//common mat-grid-tile +.upFile-header { + margin: auto; + padding: 0; + display: flex; + flex-direction: row; + flex-basis: auto; + flex-wrap: wrap; + height: 48px; +} + +.upFile-list { + z-index: 0; + + .mat-grid-tile { + white-space: nowrap; + + .mat-figure { + background-color: #fefefe; + flex-direction: column; + } + + .prevDelete { + position: absolute; + top: 0; + right: 0; + z-index: 2; + + button { + min-width: 26px; + line-height: 22px; + padding: 0; + border-radius: 50%; + border: 1px solid #fff; + background-color: #fff; + } + } + + .previewImg { + position: relative; + + .preview-image, + .preview-img { + max-width: 100%; + max-height: 100%; + width: auto; + height: auto; + } + } + + .fileName { + position: absolute; + @extend .text-overflow; + margin-top: auto; + bottom: 0; + text-shadow: 1px 1px 1px white; + margin-bottom: 3px; + font-size: 0.75em; + text-align: center; + width: 98%; + } + } +} + +// upload +.upFile-wrap { + .mat-grid-tile { + .mat-figure { + margin: 2px; + border: 1px solid $commonBorder2; + height: calc(((100% - 0.75px) * 1) - 3px); + overflow: hidden; + + a { + display: block; + width: 100%; + height: 100%; + color: inherit; + } + } + } +} + +.app-article-media-dialog { + $thisPaddingX: 8px; + $thisPaddingY: 16px; + padding: $thisPaddingY $thisPaddingX !important; + height: calc(100vh - 32px); + + .mat-card-header-text { + width: 100%; + + .right { + float: right; + + button { + margin-left: 10px; + } + } + + .mat-card-title { + overflow: hidden; + } + + .mat-card-subtitle { + text-align: right; + } + } + + .mat-card-content { + @extend .upFile-list; + } + + .option-gif { + @include calc(width, '100% - 2*'$thisPaddingX); + position: fixed; + bottom: 5px; + } + + .preview-gif { + position: relative; + height: 200px; + + .mat-expansion-panel-header-title { + font-size: 1.1em; + font-weight: 500; + } + + .preview-gif-data { + height: 120px; + + .app-article-media-dialog-gif { + width: 100px; + height: 100px; + } + + .mat-spinner { + position: absolute; + height: 100px; + width: 100%; + top: 0; + left: 0; + } + } + + .button-farm-bottom { + position: fixed; + width: auto; + bottom: 15px; + left: 5px; + + button { + margin: 2px; + min-width: 32px !important; + padding: 0 8px !important; + } + } + } + + .adjust-gif { + + .mat-list-base .mat-list-item, + .mat-list-base .mat-list-option { + height: auto; + } + + .mat-list-item-content { + font-size: 12px; + color: #666; + + .mat-slider-horizontal { + margin-right: 10px; + } + } + } +} + +.GIF-creator { + height: auto !important; + box-shadow: none !important; + margin-bottom: 270px; +} + +.crop-wrap { + .crop-image { + max-width: 100%; + max-height: calc(100vh - 72px); + } + + .mat-card-content { + @extend .crop-image; + box-shadow: none !important; + } + +} diff --git a/src/customcolor2.scss b/src/customcolor2.scss new file mode 100755 index 0000000..3b43117 --- /dev/null +++ b/src/customcolor2.scss @@ -0,0 +1,51 @@ +.mat-chip.mat-standard-chip { + &.reply_001 { + background-color: #f5dcda; + color: #ce4444; + } + + &.reply_002 { + background-color: #f4d7e0; + color: #b22851; + } + + &.reply_003 { + background-color: #edddef; + color: #a13fb0; + } + + &.reply_004 { + background-color: #e3daf4; + color: #643aba; + } + + &.reply_005 { + background-color: #dbe0f7; + color: #643aba; + } + + &.reply_006 { + background-color: #d8e6f5; + color: #125298; + } + + &.reply_007 { + background-color: #d6eaee; + color: #1f6676; + } + + &.reply_008 { + background-color: #d8ecd8; + color: #1d6820; + } + + &.reply_009 { + background-color: #efecd0; + color: #a77705; + } + + &.reply_010 { + background-color: #f5e5d0; + color: #cc700e; + } +} diff --git a/src/customcomponent.scss b/src/customcomponent.scss new file mode 100755 index 0000000..9e962b8 --- /dev/null +++ b/src/customcomponent.scss @@ -0,0 +1,1069 @@ +@mixin calc($property, $expression) { + #{$property}: -moz-calc(#{$expression}); + #{$property}: -webkit-calc(#{$expression}); + #{$property}: calc(#{$expression}); +} + +// circle icon +@mixin icon-circle($scale, $iconScale, $bgColor, $fillColor) { + width: $scale + px !important; + height: $scale + px !important; + background-color: $bgColor; + fill: $fillColor !important; + border-radius: 50%; + display: flex !important; + align-items: center; + justify-content: center; + + svg { + width: $iconScale + 0%; + height: $iconScale + 0%; + } +} + +// title of thumb +@mixin title-thumb ($outterMargin, $paddingInner, $TBposition) { + position: absolute; + #{$TBposition}: $outterMargin; + left: $outterMargin; + $pad-lead: $paddingInner; + padding: 0 $pad-lead; + $rst2: $pad-lead*2 + $outterMargin*2; + @include calc(width, '100% -'$rst2); + display: block; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + color: #fff; + background: rgba(0, 0, 0, .38); + line-height: 24px !important; + font-size: 14px; +} + +// tab init +@mixin tab-init($commonTabBG, +$commonBorder, +$commoniconColor, +$matPrimCustum2, +$fontSize, +$tabHeightPx) { + .mat-tab-labels { + background-color: $commonTabBG; + border-bottom: 1px solid $commonBorder2; + + .mat-tab-label { + height: $tabHeightPx + px; + color: $commoniconColor; + font-size: $fontSize; + font-weight: 600; + + .mat-tab-label-content { + .mat-icon { + margin-top: 0; + } + + span { + position: relative; + width: auto; + display: inherit; + bottom: auto; + } + } + } + + .mat-tab-label-active { + color: $matPrimCustum2; + } + } +} + +// margin init +$marginWrite: 0px; +$marginDetail: 40px; +$marginWriteSection: 20px; + +// width +.example-full-width { + @include calc(width, '100% - '$marginWrite); +} + +.just-full-width { + width: 100%; +} + +// height +.example-viewport { + width: 100%; + + @media (max-width: 800px) { + height: calc(100vh - 156px); + } + + @media (min-width: 801px) { + height: calc(100vh - 55px); + } +} + +.example-viewport2 { + width: 100%; + + @media (max-width: 800px) { + height: calc(100vh - 106px); + } + + @media (min-width: 801px) { + height: calc(100vh - 55px); + } +} + +// mat ripple:hover +.mat-button:hover { + background: rgba(255, 255, 255, 0.25) radial-gradient(circle, transparent 1%, rgba(255, 255, 255, 0.25) 1%) center/15000%; +} + +// mat ripple:active +.mat-button:active { + background-color: rgba(255, 255, 255, 0.25); + background-size: 100%; + transition: background 0s; +} + +// like button progress mat-spinner position set. +.mat-inner-spinner { + position: inherit; + + .mat-spinner { + position: absolute; + top: calc(50% - 8px); + left: calc(50% - 12px); + } +} + +// out Layout CSS +.card-button { + .mp-init { + margin: 0; + padding: 0; + } + + @extend .mp-init; + display: flex; + overflow: hidden; + align-items: center; + + ul { + margin: auto; + } + + li { + list-style: none; + margin-left: 20px; + + &:nth-child(1) { + margin-left: 0; + } + } + + .fl-left { + @extend .mp-init; + display: flex; + margin-right: auto; + + li { + @extend .mp-init; + margin-right: 10px; + } + } + + .fl-right { + @extend .mp-init; + display: flex; + float: right; + + li { + @extend .mp-init; + margin-left: 10px; + } + } + + .mat-button, + .mat-button * { + min-width: 24px !important; + } + + .mat-button { + padding: 0 3px !important; + } +} + +// thumb selected +.selected { + position: relative; + + &:after { + content: ''; + position: absolute; + overflow: hidden; + top: 0; + left: 0; + display: block; + width: 100%; + height: 100%; + background: url('../src/assets/img/login/ico_check2.svg') no-repeat center center rgba(0, 0, 0, 0.5); + background-size: 24px 24px; + } +} + +.selected-cover { + position: relative; + + &:after { + content: 'Cover...'; + color: #fff; + text-align: center; + font-weight: 600; + position: absolute; + overflow: hidden; + top: 0; + left: 0; + display: block; + width: 100%; + height: 100%; + background: url('../src/assets/img/icons/account-box-multiple.svg') no-repeat center center rgba(0, 0, 0, 0.5); + background-size: 24px 24px; + } +} + +// in Layout CSS +app-root { + + // width + .example-full-width { + @extend .example-full-width; + } + + // bg + .whiteBG { + background: { + color: $commonWhiteBG; + } + } + + // align + .txL { + text-align: left; + } + + .txC { + text-align: center; + } + + .txR { + text-align: right; + } + + // margin + .mt10 { + margin-top: 10px; + } + + .ml10 { + margin-left: 10px; + } + + .mr10 { + margin-right: 10px; + } + + .banout { + float: right; + line-height: 24px; + position: absolute; + top: 20px; + right: 20px; + + &.mat-button { + padding: 0; + min-width: 32px; + } + } + + .common-headline-title { + color: $commonActiveColor; + margin: 0 0 5px 0; + word-break: break-all; + } + + .button-farm { + margin: $marginWriteSection 0; + overflow: hidden; + + button { + @extend .button-style; + } + + .left { + text-align: left; + float: left; + } + + .right { + text-align: right; + float: right; + } + } + + .flex-column { + display: flex; + flex-direction: column; + + button { + margin: 5px auto; + width: 100%; + @extend .button-style; + } + } + + .flex-row { + display: flex; + flex-direction: row; + list-style: none; + padding: 0; + + li { + flex: 0 0 auto; + + &:nth-child(1) { + margin-right: auto; + } + } + } + + .button-style { + .mat-flat-button.mat-primary { + background-color: $matPrimCustum2; + color: #fff; + } + + .mat-flat-button.mat-disabled, + .mat-flat-button.mat-primary[disabled], + .mat-flat-button.mat-accent[disabled], + .mat-flat-button.mat-warn[disabled], + .mat-flat-button[disabled][disabled], + .mat-raised-button.mat-primary[disabled], + .mat-raised-button.mat-accent[disabled], + .mat-raised-button.mat-warn[disabled], + .mat-raised-button[disabled][disabled], + .mat-fab.mat-primary[disabled], + .mat-fab.mat-accent[disabled], + .mat-fab.mat-warn[disabled], + .mat-fab[disabled][disabled], + .mat-mini-fab.mat-primary[disabled], + .mat-mini-fab.mat-accent[disabled], + .mat-mini-fab.mat-warn[disabled], + .mat-mini-fab[disabled][disabled] { + background-color: $matdisabledCustum2; + color: #000; + } + + .mat-flat-button.mat-secondary { + background-color: $matsecondCustum2; + color: #fff; + } + + .mat-flat-button.mat-accent { + background-color: $matAccentCustum2; + color: #000; + } + + .mat-flat-button.mat-warn { + background-color: $matWarnCustum2; + color: #fff; + } + + .mat-stroked-button.mat-mat-primary, + .mat-stroked-button.mat-primary { + color: $matPrimCustum2; + } + } + + .example-right-align { + text-align: right; + } + + mat-hint { + @extend .example-right-align; + width: 100%; + } + + .text-overflow, + { + display: block; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + } + + // progress bar + .progress-bar { + display: block; + width: 100%; + position: fixed; + top: 55px; + left: 0; + z-index: 999; + } + + // for common + .mat-form-field { + line-height: 1.255; + } + + // for virtual Scroll Height + .virtualScroll-height { + $restHeight: 105px; + @include calc(height, '100vh -'$restHeight); + width: 100%; + } + + // for tag list + .tag-list-count { + @extend .example-right-align; + } + + .tag-list { + @extend .example-full-width; + } + + // notice top + .notice-top { + display: flex; + flex-direction: column; + margin: auto; + + h2.mat-primary { + color: $matPrimCustum2; + margin: 0 auto; + margin-top: 20px; + margin-bottom: 10px; + } + + .description { + display: flex; + margin: auto; + font-weight: 600; + margin-bottom: 20px; + padding-right: 50px; + + mat-icon { + fill: $matPrimCustum2; + } + } + } + + // mat-slide + .mat-slide-toggle { + .mat-slide-toggle-thumb { + height: 20px; + width: 20px; + } + + .mat-slide-toggle-bar { + height: 22px; + width: 38px; + border-radius: 11px; + } + + .mat-slide-toggle-thumb-container { + top: 1px; + left: 1px; + } + } + + // for defaultLayout + .defaultLayout { + @extend .button-style; + + // mat Init + .mat-card { + border-radius: 2px; + } + } + + @extend .button-style; + + .common-section { + position: relative; + display: flex; + flex-direction: column; + min-height: 40px; + @extend .whiteBG; + + &:after { + content: ''; + display: block; + overflow: hidden; + background-color: $commonBorder; + height: 1px; + } + } + + .last-section { + @extend .whiteBG; + + &:after { + content: ''; + height: 0 !important; + } + } + + .section-wrap { + margin: $marginWriteSection 10px; + } + + // for user edit + .profile-edit { + .icon-circle { + @include icon-circle(36, 70, $matPrimCustum2, $commonWhiteBG); + } + } + + // for favor + .tutorial-wrap { + width: 100%; + } + + .favor-wrap { + position: relative; + } + + .addFavorButtonWrap { + margin: 10px; + text-align: right; + position: absolute; + top: -60px; + right: 0; + } + + // for setting + .settings { + @extend .common-section; + + .mat-list { + .mat-list-item-content { + padding: 0; + } + + .right { + button { + border: none; + background: none; + min-width: 60px; + width: auto; + } + } + + .mat-list-icon { + color: $commoniconColor; + } + } + } + + // for content-card-section + .select-of-Reply { + margin: 10px 0; + text-align: center; + display: block; + overflow: hidden; + white-space: nowrap; + width: 100%; + max-height: 54px; + + .mat-chip-list-wrapper { + margin: 0 !important; + } + } + + .user-info-header-image { + border-radius: 50%; + border: solid 1px #fff; + // background-color: #eee; + object-fit: cover; + background: url('../src/assets/img/examples/avata.jpg') no-repeat center center; + background-size: contain; + + img { + width: 100%; + height: 100%; + } + } + + .content-card-section { + margin: $marginWriteSection auto; + @include calc(max-width, '100% - '$marginDetail); + + .card-data { + .card-title { + font-weight: 600; + margin: 10px 0 5px 0; + word-break: break-all; + } + + .card-description { + font-size: 0.9em; + color: $commonPhaseColor; + margin: 10px 0; + display: block; + word-break: break-all; + } + } + + &.mat-card-header { + margin: 0 0 20px 0 !important; + + .avatar-area { + width: 40px; + height: 40px; + overflow: hidden; + white-space: nowrap; + @extend .user-info-header-image; + + .user-info-header-image { + width: 100%; + height: 100%; + + img { + width: 100%; + height: 100%; + } + } + } + + .profillTitle { + @extend .text-overflow; + line-height: 40px; + margin-bottom: 0 !important; + } + + @media (max-width: 420px) { + .profillTitle { + max-width: 170px; + } + } + + @media (min-width: 421px) { + .profillTitle { + max-width: 250px; + } + } + + .chip_follow_list { + line-height: 40px; + display: block; + margin: auto 0; + flex-direction: inherit; + flex-wrap: unset; + + .chip_follow { + background-color: $matPrimCustum2 !important; + color: #fff !important; + min-height: 24px !important; + line-height: 24px !important; + font-size: 12px !important; + } + } + } + + .tag-cloud { + margin: 10px 0 0 0; + width: 100% !important; + } + } + + // for recommend-card + .recommend-hidden { + width: 100%; + overflow: hidden; + white-space: nowrap; + + .recommend-section { + width: 65%; + + .carousel { + overflow: visible !important; + margin: 10px auto; + } + } + } + + .content-display { + width: 100%; + max-width: 100%; + overflow: hidden; + + .mat-spinner { + position: absolute; + top: calc((50% - 0.75px) - 50px); + left: calc((50% - 0.75px) - 50px); + } + + img { + @extend .content-display; + display: none; + + &:nth-child(1) { + display: block; + } + } + } + + + // main + .main-wrap { + @extend .example-viewport2; + } + + .main-grid-item { + .mat-grid-list-text { + @include title-thumb(0, 16px, bottom); + } + } + + //for main thumb + .main-thumb-wrap { + position: relative; + vertical-align: top; + margin-bottom: 10px; + + .pickMeup { + position: absolute; + top: 0; + + .thumb-item { + position: relative; + display: block; + overflow: hidden; + width: 100%; + height: 100%; + + .mat-grid-tile-header { + @include title-thumb(0, 16px, bottom); + top: auto; + } + + .grid-item { + position: absolute; + width: calc(100% - 2px); + height: calc(100% - 2px); + display: flex; + border: 1px solid $commonBorder2; + + app-cartoons-display-cover { + margin: auto; + } + } + } + } + } + + // for article/write style + // for my profile + .my-profile { + @extend .example-viewport2; + + .mat-tab-label { + min-width: 80px; + } + } + + // thumbnail list + .thumb-wrap { + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: flex-start; + + .article-display-tile { + position: relative; + flex: 0 1 calc(33.3% - 2px); + padding-top: calc(33.3% - 2px); + vertical-align: top; + + .pickMeup { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + + .thumb-item { + position: relative; + display: block; + overflow: hidden; + width: calc(100% - 2px); + height: calc(100% - 2px); + + .grid-item { + position: absolute; + width: calc(100% - 2px); + height: calc(100% - 2px); + display: flex; + border: 1px solid $commonBorder2; + + app-article-display-tile-content { + margin: auto; + } + + .article-title-thumb { + @include title-thumb(0, 16px, bottom); + top: auto; + } + } + } + } + } + } + + // for search + .seatch-tab { + @include tab-init($commonTabBG, + $commonBorder2, + $commoniconColor, + $matPrimCustum2, + 14px, + 48); + + .mat-ink-bar { + display: block !important; + } + } + + + // for search thumb + .search-viewport { + margin-top: 1px; + } + + // for favor, search + .listImage { + $rst: 2px; + @include calc(height, '100% -'$rst); + @include calc(width, '100% -'$rst); + + .grid-tile-header-03 { + @include title-thumb(1px, 16px, top); + } + + .gridImgSize { + width: 100%; + height: 100%; + } + } + + // for write detail + .writeDetail { + .write-tab { + .tab-in-tab { + @include tab-init($commonTabBG, + $commonBorder2, + $commoniconColor, + $matPrimCustum2, + 14px, + 48); + + .mat-ink-bar { + display: block !important; + } + } + + .mat-tab-label { + @include calc(width, '100%/3'); + height: 80px; + + .mat-tab-label-content { + .mat-icon { + margin-top: -10px; + @include icon-circle(50, 70, $commonBorder2, $commonWhiteBG); + } + + span { + position: absolute; + display: block; + width: 100%; + bottom: 0; + } + } + } + + .mat-tab-label-active { + .mat-icon { + background-color: $matPrimCustum2 !important; + } + + span { + color: $matPrimCustum2; + } + } + } + + section { + @extend .common-section; + + .mat-chip { + color: $commonWhiteBG; + } + + .description { + position: relative; + + p { + font-size: 90%; + color: $commonPhaseColor; + } + } + + .button-float-right { + position: absolute; + top: $marginWriteSection; + right: 10px; + } + } + } + + // for event, recommend + .dataDL-list { + padding: 0; + + li { + list-style: none; + margin: 0; + padding: 0; + overflow: hidden; + + .dataDL-dl { + margin: 0; + padding: 0; + display: flex; + + .dataDL-banner { + width: 60px; + height: 100%; + display: block; + padding-right: 10px; + + .dataDL-image, + img { + max-width: 60px; + max-height: 60px; + border-radius: 50%; + border: 3px solid #fff; + margin: auto; + justify-content: center; + align-items: center; + object-fit: cover; + background: url('../src/assets/img/examples/avata.jpg') no-repeat center center; + background-size: contain; + } + } + + .dataDL-data { + // 아래의 2줄은 사용환경에 따라 $rest 값을 재 정의해서 해당 컴퍼넌트 레벨의 scss에서 재 정의하여 재사용. + $rest: 150px; + @include calc(width, '100% -'$rest); + margin: auto; + + .dataDL-inner { + @extend .text-overflow; + width: 100%; + margin: 0; + } + + .dataDL-data-title { + @extend .dataDL-inner; + font-size: 1.05em; + font-weight: 600; + word-break: break-all; + } + + .dataDL-data-description { + @extend .dataDL-inner; + font-size: 0.95em; + color: $commonPhaseColor; + } + } + + .dataDL-button { + min-width: 88px; + margin: auto; + justify-content: center; + align-items: center; + + button { + @extend .dataDL-button; + } + } + } + } + } + + .event { + .dataDL-image { + background: none !important; + border: none !important; + border-radius: 0 !important; + } + } + + // for tag cloud + .tag-cloud { + $buttonMargin: 30px; + @include calc(width, '100% - '$buttonMargin); + + .hash { + background: url('../src/assets/img/background/pound.svg') no-repeat 0px 55%; + background-size: contain; + -webkit-filter: grayscale(100%) brightness(5) brightness(0.9); + filter: grayscale(100%) brightness(5) brightness(0.9); + box-shadow: none !important; + border-radius: 0; + margin: 0 5px 0 0; + padding: 0 0 0 14px; + min-width: auto !important; + line-height: 14px; + + span { + color: #222 !important; + text-align: left; + } + } + } + + // for mat tab + .mat-tab-group { + $matPrimCustum: #ffffff; + // $matActiveCustum: #ccc; + // $matForGCustum: #fff; + + &.mat-primary { + background-color: $matPrimCustum !important; + } + + .mat-tab-nav-bar, + .mat-tab-header { + border-bottom: none; + } + + .mat-tab-label { + color: #d0d0d0; + } + + .mat-tab-label-active { + color: $commonActiveColor; + } + + .mat-ink-bar { + // this element for Active Tab Indicator; + display: none; + } + } +} diff --git a/src/customlayout.scss b/src/customlayout.scss new file mode 100755 index 0000000..6fa4839 --- /dev/null +++ b/src/customlayout.scss @@ -0,0 +1,61 @@ +app-root { + $topHeight: 55px; + top: $topHeight; + display: flex; + flex-direction: column; + position: absolute; + bottom: 0; + left: 0; + right: 0; + + app-root>app-homepage, + app-root>app-guides, + app-root>guide-viewer { + overflow-y: visible; + } + + app-user-support-tutorial { + top: 0; + left: 0; + position: fixed; + z-index: 999; + } +} + +@media (max-width: 800px) { + app-root { + + &:after { + content: ""; + margin-bottom: 50px; + } + } +} + +@media (min-width: 801px) { + app-root { + .pcLayout { + max-width: 800px !important; + margin: 0 auto; + + .defaultLayout { + max-width: 800px; + + } + + .mainLayout { + max-width: 800px; + + .mat-tab-body-wrapper { + max-width: 500px; + } + } + + .main-rcommend { + position: absolute; + top: 70px; + left: calc(100vw/2 + 120px); + } + } + } +} diff --git a/src/customlogin.scss b/src/customlogin.scss new file mode 100755 index 0000000..84c0edd --- /dev/null +++ b/src/customlogin.scss @@ -0,0 +1,103 @@ +// circle icon +@mixin icon-circle($scale, $iconScale, $bgColor, $fillColor) { + width: $scale+px !important; + height: $scale+px !important; + background-color: $bgColor; + fill: $fillColor !important; + border-radius: 50%; + display: flex !important; + align-items: center; + justify-content: center; + + svg { + width: $iconScale+0%; + height: $iconScale+0%; + } +} + +// for accounts +.login-card .mat-list-item-content { + padding: 0 !important; +} + +// login-card +.login-card { + .mat-card-footer { + text-align: center; + margin-top: 20px; + background: none; + + .login-trouble { + color: $commonPhaseColor; + font-size: 1.06em; + + .mat-icon { + margin-right: 5px; + vertical-align: text-bottom; + } + } + } + + button.mat-button { + text-align: center; + padding: 0; + + .icon-circle { + margin: 0 auto; + margin-bottom: -5px; + } + + span { + color: $commonPhaseColor; + font-weight: 400; + } + } + + .login-logo-naver { + .icon-circle { + @include icon-circle(60, 50, #01c73c, $commonWhiteBG); + } + } + + .login-logo-kakao { + .icon-circle { + @include icon-circle(60, 50, #e6d000, $commonWhiteBG); + } + } + + .login-logo-facebook { + .icon-circle { + @include icon-circle(60, 50, #6986c0, $commonWhiteBG); + } + } + + .login-logo-google { + .icon-circle { + @include icon-circle(60, 50, #679bf1, $commonWhiteBG); + } + } + + .login-logo-line { + .icon-circle { + @include icon-circle(60, 50, #01c73c, $commonWhiteBG); + } + } + + .login-logo-twitter { + .icon-circle { + @include icon-circle(60, 50, #38a1ce, $commonWhiteBG); + } + } + + .login-logo-weibo { + .icon-circle { + @include icon-circle(60, 50, #ef5f5f, $commonWhiteBG); + } + } + + .login-logo-instagram { + .icon-circle { + @include icon-circle(60, 50, #c389de, $commonWhiteBG); + } + } +} diff --git a/src/environments/environment.hmr.ts b/src/environments/environment.hmr.ts new file mode 100755 index 0000000..486b18a --- /dev/null +++ b/src/environments/environment.hmr.ts @@ -0,0 +1,50 @@ +export const environment = { + production: false, + hmr: true, + apiEntryPoint: 'http://localhost:8080/api', + attachmentsEntryPoint: 'http://localhost:8080/api/attachments', + oauth: { + kakao: { + authenticateURL: 'http://localhost:8080/oauth/authentication/kakao', + }, + naver: { + authenticateURL: 'http://localhost:8080/oauth/authentication/naver', + }, + oneall: { + providers: [ + 'facebook', 'google', 'instagram', 'twitter', + ], + authenticateURL: 'https://chailocalhost.api.oneall.com/socialize/connect/direct', + callbackURL: 'http://localhost:8080/oauth/oneall/authentication', + }, + }, + article: { + default: 'Cartoons', + favor: { + commitInterval: 2000, + }, + cartoons: { + series: { + write: { + titleMinLength: 2, + titleMaxLength: 32, + descriptionMinLength: 2, + descriptionMaxLength: 100, + } + } + } + }, + events: { + matSnackBar: { + duration: 2000, + } + }, + userAnalysis: { + favorTag: { + selectMinCount: 3, + } + }, + supportLanguages: [ + 'ko', 'ja', 'en' + ] +}; diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts new file mode 100755 index 0000000..a1a722a --- /dev/null +++ b/src/environments/environment.prod.ts @@ -0,0 +1,50 @@ +export const environment = { + production: true, + hmr: false, + apiEntryPoint: 'http://localhost:8080/api', + attachmentsEntryPoint: 'http://localhost:8080/api/attachments', + oauth: { + kakao: { + authenticateURL: 'http://localhost:8080/oauth/authentication/kakao', + }, + naver: { + authenticateURL: 'http://localhost:8080/oauth/authentication/naver', + }, + oneall: { + providers: [ + 'facebook', 'google', 'instagram', 'twitter', + ], + authenticateURL: 'https://chailocalhost.api.oneall.com/socialize/connect/direct', + callbackURL: 'http://localhost:8080/oauth/oneall/authentication', + }, + }, + article: { + default: 'Cartoons', + favor: { + commitInterval: 10000, + }, + cartoons: { + series: { + write: { + titleMinLength: 2, + titleMaxLength: 32, + descriptionMinLength: 2, + descriptionMaxLength: 100, + } + } + } + }, + events: { + matSnackBar: { + duration: 2000, + } + }, + userAnalysis: { + favorTag: { + selectMinCount: 3, + } + }, + supportLanguages: [ + 'ko', 'ja', 'en' + ] +}; diff --git a/src/environments/environment.test.ts b/src/environments/environment.test.ts new file mode 100755 index 0000000..303797f --- /dev/null +++ b/src/environments/environment.test.ts @@ -0,0 +1,50 @@ +export const environment = { + production: false, + hmr: true, + apiEntryPoint: 'http://54.180.12.41:8080/api', + attachmentsEntryPoint: 'http://54.180.12.41:8080/api/attachments', + oauth: { + kakao: { + authenticateURL: 'http://54.180.12.41:8080/oauth/authentication/kakao', + }, + naver: { + authenticateURL: 'http://54.180.12.41:8080/oauth/authentication/naver', + }, + oneall: { + providers: [ + 'facebook', 'google', 'instagram', 'twitter', + ], + authenticateURL: 'https://chailocalhost.api.oneall.com/socialize/connect/direct', + callbackURL: 'http://54.180.12.41:8080/oauth/oneall/authentication', + }, + }, + article: { + default: 'Cartoons', + favor: { + commitInterval: 2000, + }, + cartoons: { + series: { + write: { + titleMinLength: 2, + titleMaxLength: 32, + descriptionMinLength: 2, + descriptionMaxLength: 100, + } + } + } + }, + events: { + matSnackBar: { + duration: 2000, + } + }, + userAnalysis: { + favorTag: { + selectMinCount: 3, + } + }, + supportLanguages: [ + 'ko', 'ja', 'en' + ] +}; diff --git a/src/environments/environment.ts b/src/environments/environment.ts new file mode 100755 index 0000000..b4787ce --- /dev/null +++ b/src/environments/environment.ts @@ -0,0 +1,63 @@ +// This file can be replaced during build by using the `fileReplacements` array. +// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. +// The list of file replacements can be found in `angular.json`. + +export const environment = { + production: false, + hmr: false, + apiEntryPoint: 'http://localhost:8080/api', + attachmentsEntryPoint: 'http://localhost:8080/api/attachments', + oauth: { + kakao: { + authenticateURL: 'http://localhost:8080/oauth/authentication/kakao', + }, + naver: { + authenticateURL: 'http://localhost:8080/oauth/authentication/naver', + }, + oneall: { + providers: [ + 'facebook', 'google', 'instagram', 'twitter', + ], + authenticateURL: 'https://chailocalhost.api.oneall.com/socialize/connect/direct', + callbackURL: 'http://localhost:8080/oauth/oneall/authentication', + }, + }, + article: { + default: 'Cartoons', + favor: { + commitInterval: 2000, + }, + cartoons: { + series: { + write: { + titleMinLength: 2, + titleMaxLength: 32, + descriptionMinLength: 2, + descriptionMaxLength: 100, + } + } + } + }, + events: { + matSnackBar: { + duration: 2000, + } + }, + userAnalysis: { + favorTag: { + selectMinCount: 3, + } + }, + supportLanguages: [ + 'ko', 'jp', 'en' + ] +}; + +/* + * For easier debugging in development mode, you can import the following file + * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. + * + * This import should be commented out in production mode because it will have a negative impact + * on performance if an error is thrown. + */ +// import 'zone.js/dist/zone-error'; // Included with Angular CLI. diff --git a/src/favicon.ico b/src/favicon.ico new file mode 100755 index 0000000..8081c7c Binary files /dev/null and b/src/favicon.ico differ diff --git a/src/hmr.ts b/src/hmr.ts new file mode 100755 index 0000000..789b245 --- /dev/null +++ b/src/hmr.ts @@ -0,0 +1,15 @@ +import { NgModuleRef, ApplicationRef } from '@angular/core'; +import { createNewHosts } from '@angularclass/hmr'; + +export const hmrBootstrap = (module: any, bootstrap: () => Promise>) => { + let ngModule: NgModuleRef; + module.hot.accept(); + bootstrap().then(mod => ngModule = mod); + module.hot.dispose(() => { + const appRef: ApplicationRef = ngModule.injector.get(ApplicationRef); + const elements = appRef.components.map(c => c.location.nativeElement); + const makeVisible = createNewHosts(elements); + ngModule.destroy(); + makeVisible(); + }); +}; diff --git a/src/index.html b/src/index.html new file mode 100755 index 0000000..a70e9a8 --- /dev/null +++ b/src/index.html @@ -0,0 +1,18 @@ + + + + + + + AngularUniversal + + + + + + + + + + + diff --git a/src/karma.conf.js b/src/karma.conf.js new file mode 100755 index 0000000..fee4a56 --- /dev/null +++ b/src/karma.conf.js @@ -0,0 +1,31 @@ +// Karma configuration file, see link for more information +// https://karma-runner.github.io/1.0/config/configuration-file.html + +module.exports = function (config) { + config.set({ + basePath: '', + frameworks: ['jasmine', '@angular-devkit/build-angular'], + plugins: [ + require('karma-jasmine'), + require('karma-chrome-launcher'), + require('karma-jasmine-html-reporter'), + require('karma-coverage-istanbul-reporter'), + require('@angular-devkit/build-angular/plugins/karma') + ], + client: { + clearContext: false // leave Jasmine Spec Runner output visible in browser + }, + coverageIstanbulReporter: { + dir: require('path').join(__dirname, '../coverage'), + reports: ['html', 'lcovonly'], + fixWebpackSourcePaths: true + }, + reporters: ['progress', 'kjhtml'], + port: 9876, + colors: true, + logLevel: config.LOG_INFO, + autoWatch: true, + browsers: ['Chrome'], + singleRun: false + }); +}; \ No newline at end of file diff --git a/src/main.ts b/src/main.ts new file mode 100755 index 0000000..6df51fe --- /dev/null +++ b/src/main.ts @@ -0,0 +1,27 @@ +import { enableProdMode } from '@angular/core'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app/app.module'; +import { environment } from './environments/environment'; +import { hmrBootstrap } from './hmr'; +import 'hammerjs'; + +if (environment.production) { + enableProdMode(); +} + +// platformBrowserDynamic().bootstrapModule(AppModule) +// .catch(err => console.error(err)); + +const bootstrap = () => platformBrowserDynamic().bootstrapModule(AppModule); + +if (environment.hmr) { + if (module['hot']) { + hmrBootstrap(module, bootstrap); + } else { + console.error('HMR is not enabled for webpack-dev-server!'); + console.log('Are you using the --hmr flag for ng serve?'); + } +} else { + bootstrap().catch(err => console.log(err)); +} diff --git a/src/modules/accounts/accounts-store.module.ts b/src/modules/accounts/accounts-store.module.ts new file mode 100755 index 0000000..5849953 --- /dev/null +++ b/src/modules/accounts/accounts-store.module.ts @@ -0,0 +1,17 @@ +import { NgModule, ModuleWithProviders } from '@angular/core'; +import { StoreModule } from '@ngrx/store'; +import { EffectsModule } from '@ngrx/effects'; + +import { + REDUCERS, + EFFECTS, +} from './store'; + +@NgModule({ + imports: [ + StoreModule.forFeature('accounts', REDUCERS), + EffectsModule.forFeature(EFFECTS), + ], +}) +export class AccountsStoreModule { +} diff --git a/src/modules/accounts/accounts.module.ts b/src/modules/accounts/accounts.module.ts new file mode 100755 index 0000000..e25b4e5 --- /dev/null +++ b/src/modules/accounts/accounts.module.ts @@ -0,0 +1,52 @@ +/** + * 파 일 명: accounts.module.ts + * 작성일자: 2018-12-20 + * 작 성 자: 박병준 + * 설 명: accounts module을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { NgModule, ModuleWithProviders } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { TranslateModule } from '@ngx-translate/core'; + +import { COMPONENTS } from './component'; +import { AccountsService } from './service/accounts.service'; +import { AccountsStoreModule } from './accounts-store.module'; +import { SharedModule } from '../common/shared/shared.module'; + +@NgModule({ + imports: [ + CommonModule, + TranslateModule, + SharedModule, + ], + declarations: [ + ...COMPONENTS, + ], + exports: [ + ...COMPONENTS, + ], +}) +export class AccountsModule { + public static forRoot(): ModuleWithProviders { + return { + ngModule: AccountsRootModule, + providers: [ + AccountsService, + ], + }; + } +} + +@NgModule({ + imports: [ + AccountsStoreModule, + ], + exports: [ + ] +}) +export class AccountsRootModule { +} diff --git a/src/modules/accounts/component/authentication/authentication.component.html b/src/modules/accounts/component/authentication/authentication.component.html new file mode 100755 index 0000000..4097aa4 --- /dev/null +++ b/src/modules/accounts/component/authentication/authentication.component.html @@ -0,0 +1,62 @@ +

+ +

+ diff --git a/src/modules/accounts/component/authentication/authentication.component.scss b/src/modules/accounts/component/authentication/authentication.component.scss new file mode 100755 index 0000000..d746956 --- /dev/null +++ b/src/modules/accounts/component/authentication/authentication.component.scss @@ -0,0 +1,20 @@ +h1.coco { + text-align: center; + + .logo-login { + margin: 0 auto; + width: 50%; + height: auto; + max-width: 200px; + } +} + +.login-card { + box-shadow: none; + background: none; + + mat-card-actions { + max-width: 320px; + margin: 0 auto; + } +} diff --git a/src/modules/accounts/component/authentication/authentication.component.ts b/src/modules/accounts/component/authentication/authentication.component.ts new file mode 100755 index 0000000..18861f9 --- /dev/null +++ b/src/modules/accounts/component/authentication/authentication.component.ts @@ -0,0 +1,39 @@ +/** + * 파 일 명: authentication.component.ts + * 작성일자: 2018-12-20 + * 작 성 자: 박병준 + * 설 명: authentication component를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Component, Input } from '@angular/core'; +import { AccountsService } from '../../service/accounts.service'; + +@Component({ + selector: 'app-authentication', + templateUrl: './authentication.component.html', + styleUrls: ['./authentication.component.scss'], +}) +export class AuthenticationComponent { + @Input() + params: Map; + + constructor( + private accountService: AccountsService, + ) { + } + + login(oauthType: string) { + this.accountService.authenticate(oauthType, this.params); + } + + loginOneall(oauthType: string) { + this.accountService.authenticateOneall(oauthType, this.params); + } + + loginTrouble() { + + } +} diff --git a/src/modules/accounts/component/index.ts b/src/modules/accounts/component/index.ts new file mode 100755 index 0000000..6c3b9e0 --- /dev/null +++ b/src/modules/accounts/component/index.ts @@ -0,0 +1,5 @@ +import { AuthenticationComponent } from './authentication/authentication.component'; + +export const COMPONENTS = [ + AuthenticationComponent, +]; diff --git a/src/modules/accounts/service/accounts.service.ts b/src/modules/accounts/service/accounts.service.ts new file mode 100755 index 0000000..6fc0c12 --- /dev/null +++ b/src/modules/accounts/service/accounts.service.ts @@ -0,0 +1,71 @@ +/** + * 파 일 명: accounts.service.ts + * 작성일자: 2018-12-20 + * 작 성 자: 박병준 + * 설 명: accounts Service를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; + +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; + +import { CookieService } from 'ngx-cookie-service'; +import { environment } from '../../../environments/environment'; +import { User } from '../../../shared/user/model/user.model'; + + +@Injectable() +export class AccountsService { + + public constructor( + private httpClient: HttpClient, + private cookieService: CookieService, + ) { + } + + public authenticate(oauthType: string, params?: Map) { + const query = this.getQueryParams(params); + + const externalUrl = new URL(`${environment.oauth[oauthType].authenticateURL}?${query}`); + + window.open(externalUrl.href, '_self'); + } + + public authenticateOneall(oauthType: string, params?: Map) { + const callbackUri = `${environment.oauth.oneall.callbackURL}?${this.getQueryParams(params)}`; + + let query = ''; + query += `service=social_login`; + query += `&callback_uri=${callbackUri}`; + + const externalUrl = new URL(`${environment.oauth.oneall.authenticateURL}/${oauthType}/?${query}`); + + window.open(externalUrl.href, '_self'); + } + + protected getQueryParams(params?: Map): string { + let query = ''; + + if (!params || 0 === params.size) { + return query; + } + + params.forEach((value, key) => { + if ('' !== query) { + query += '&'; + } + query += `${key}=${value}`; + }); + + return query; + } + + public login(): Observable { + return this.httpClient.get(`${environment.apiEntryPoint}/accounts/login`); + } +} diff --git a/src/modules/accounts/store/auth/auth.action.ts b/src/modules/accounts/store/auth/auth.action.ts new file mode 100755 index 0000000..56e1795 --- /dev/null +++ b/src/modules/accounts/store/auth/auth.action.ts @@ -0,0 +1,48 @@ +import { Action } from '@ngrx/store'; +import { User } from '../../../../shared/user/model/user.model'; + +export enum ActionType { + LoginProcessing = '[account.auth] LoginProcessing', + + LoginSuccess = '[account.auth] LoginSuccess', + LogoutSuccess = '[account.auth] LogoutSuccess', + ChangeUser = '[account.auth] ChangeUser', + LoginRequired = '[account.auth] LoginRequired', +} + +export class LoginProcessing implements Action { + readonly type = ActionType.LoginProcessing; + + constructor(public payload: { processing: boolean }) { } +} + +export class LoginSuccess implements Action { + readonly type = ActionType.LoginSuccess; + + constructor(public payload: { user: User, returnURL: string }) { } +} + +export class ChangeUser implements Action { + readonly type = ActionType.ChangeUser; + + constructor(public payload: { user: User }) { } +} + +export class LogoutSuccess implements Action { + readonly type = ActionType.LogoutSuccess; +} + +export class LoginRequired implements Action { + readonly type = ActionType.LoginRequired; + + constructor() { } +} + + +export type Actions = + | LoginProcessing + | LoginSuccess + | ChangeUser + | LogoutSuccess + | LoginRequired + ; diff --git a/src/modules/accounts/store/auth/auth.reducer.ts b/src/modules/accounts/store/auth/auth.reducer.ts new file mode 100755 index 0000000..32fdd98 --- /dev/null +++ b/src/modules/accounts/store/auth/auth.reducer.ts @@ -0,0 +1,43 @@ +import { Actions, ActionType } from './auth.action'; + +import { State, initialState } from './auth.state'; + +export function reducer(state: State = initialState, action: Actions): State { + switch (action.type) { + case ActionType.LoginProcessing: { + return { + ...state, + processing: action.payload.processing + }; + } + + case ActionType.LoginSuccess: { + return { + ...state, + processing: false, + logined: true, + user: action.payload.user + }; + } + + case ActionType.ChangeUser: { + return { + ...state, + user: action.payload.user + }; + } + + case ActionType.LogoutSuccess: { + return { + ...state, + processing: false, + logined: false, + user: null + }; + } + + default: { + return state; + } + } +} diff --git a/src/modules/accounts/store/auth/auth.state.ts b/src/modules/accounts/store/auth/auth.state.ts new file mode 100755 index 0000000..bab9946 --- /dev/null +++ b/src/modules/accounts/store/auth/auth.state.ts @@ -0,0 +1,22 @@ +import { Selector, createSelector } from '@ngrx/store'; +import { User } from '../../../../shared/user/model/user.model'; + +export interface State { + processing: boolean; + logined: boolean; + user: User | null; +} + +export const initialState: State = { + processing: false, + logined: false, + user: null, +}; + +export function getSelectors(selector: Selector) { + return { + selectProcessing: createSelector(selector, (state: State) => state.processing), + selectLogined: createSelector(selector, (state: State) => state.logined), + selectUser: createSelector(selector, (state: State) => state.user), + }; +} diff --git a/src/modules/accounts/store/auth/index.ts b/src/modules/accounts/store/auth/index.ts new file mode 100755 index 0000000..3fa2c8d --- /dev/null +++ b/src/modules/accounts/store/auth/index.ts @@ -0,0 +1,3 @@ +export * from './auth.action'; +export * from './auth.reducer'; +export * from './auth.state'; diff --git a/src/modules/accounts/store/index.ts b/src/modules/accounts/store/index.ts new file mode 100755 index 0000000..e048023 --- /dev/null +++ b/src/modules/accounts/store/index.ts @@ -0,0 +1,24 @@ +import { + createSelector, + createFeatureSelector, +} from '@ngrx/store'; + +import * as AuthStore from './auth'; + +export interface State { + auth: AuthStore.State; +} + +export const REDUCERS = { + auth: AuthStore.reducer, +}; + +export const EFFECTS = [ +]; + +export const selectState = createFeatureSelector('accounts'); + +export const AuthSelector = AuthStore.getSelectors(createSelector( + selectState, + (state: State) => state.auth, +)); diff --git a/src/modules/accounts/util/accounts.util.ts b/src/modules/accounts/util/accounts.util.ts new file mode 100755 index 0000000..5926565 --- /dev/null +++ b/src/modules/accounts/util/accounts.util.ts @@ -0,0 +1,36 @@ +import { Store, select } from '@ngrx/store'; + +import { of } from 'rxjs'; +import { take, map, catchError } from 'rxjs/operators'; + +import { User } from '../../../shared/user/model/user.model'; +import * as AccountsStore from '../store'; + + +export class AccountsUtil { + public static getUser(store: Store): Promise { + return new Promise((resolve, reject) => { + store.pipe( + take(1), + select(AccountsStore.AuthSelector.selectUser), + map((user: User) => { + return resolve(user); + }), + catchError((err) => { + reject(err); + return of(err); + }), + ).subscribe(); + }); + } + + public static async checkOwner(store: Store, uid: string): Promise { + return new Promise(async (resolve, reject) => { + const user = await AccountsUtil.getUser(store); + if (!user) { + return resolve(false); + } + return resolve(user.id === uid); + }); + } +} diff --git a/src/modules/article/article-store.module.ts b/src/modules/article/article-store.module.ts new file mode 100755 index 0000000..7469666 --- /dev/null +++ b/src/modules/article/article-store.module.ts @@ -0,0 +1,17 @@ +import { NgModule, ModuleWithProviders } from '@angular/core'; +import { StoreModule } from '@ngrx/store'; +import { EffectsModule } from '@ngrx/effects'; + +import { + REDUCERS, + EFFECTS, +} from './store'; + +@NgModule({ + imports: [ + StoreModule.forFeature('article', REDUCERS), + EffectsModule.forFeature(EFFECTS), + ], +}) +export class ArticleStoreModule { +} diff --git a/src/modules/article/article.module.ts b/src/modules/article/article.module.ts new file mode 100755 index 0000000..394f2d5 --- /dev/null +++ b/src/modules/article/article.module.ts @@ -0,0 +1,58 @@ +/** + * 파 일 명: article.module.ts + * 작성일자: 2018-12-20 + * 작 성 자: 박병준 + * 설 명: article module을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { NgModule, ModuleWithProviders } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { TranslateModule } from '@ngx-translate/core'; + +import { SharedModule } from '../common/shared/shared.module'; + +import { COMPONENTS } from './component'; +import { DIALOGS } from './dialog'; +import { SERVICES } from './service'; +import { TagModule } from '../tag/tag.module'; +import { AttachmentsModule } from '../attachments/attachments.module'; +import { UserSupportModule } from '../user-support/user-support.module'; +import { ArticleStoreModule } from './article-store.module'; + + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + ReactiveFormsModule, + TranslateModule, + SharedModule, + TagModule, + AttachmentsModule, + UserSupportModule, + // CartoonsModule, + ], + exports: [...COMPONENTS], + declarations: [...COMPONENTS, ...DIALOGS], + entryComponents: [...DIALOGS] +}) +export class ArticleModule { + public static forRoot(): ModuleWithProviders { + return { + ngModule: ArticleRootModule, + providers: [...SERVICES] + }; + } +} + +@NgModule({ + imports: [ + ArticleStoreModule, + ], + exports: [] +}) +export class ArticleRootModule { } diff --git a/src/modules/article/component/bookmarks/button/button.component.html b/src/modules/article/component/bookmarks/button/button.component.html new file mode 100755 index 0000000..54c2f90 --- /dev/null +++ b/src/modules/article/component/bookmarks/button/button.component.html @@ -0,0 +1,5 @@ + + + diff --git a/src/modules/article/component/bookmarks/button/button.component.scss b/src/modules/article/component/bookmarks/button/button.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/modules/article/component/bookmarks/button/button.component.spec.ts b/src/modules/article/component/bookmarks/button/button.component.spec.ts new file mode 100755 index 0000000..590018e --- /dev/null +++ b/src/modules/article/component/bookmarks/button/button.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ButtonComponent } from './button.component'; + +describe('ButtonComponent', () => { + let component: ButtonComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ButtonComponent] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ButtonComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/modules/article/component/bookmarks/button/button.component.ts b/src/modules/article/component/bookmarks/button/button.component.ts new file mode 100755 index 0000000..4f00597 --- /dev/null +++ b/src/modules/article/component/bookmarks/button/button.component.ts @@ -0,0 +1,146 @@ +/** + * 파 일 명: button.component.ts + * 작성일자: 2018-12-17 + * 작 성 자: 조현정 + * 설 명: 북마크버튼 + * 수정일시: 2018-12-20 + * 수 정 자: 조현정 + * 수정내용: Added API descr. + * 참고사항: GET: AID, UID, loginedYN, bookmarkAID + * PUT: AID, bookmarkAID + * loginedYN 체크- 로그아웃상태면 로그인으로 + * 로그인 상태면 사용자의 bookmarkAID를 가져와서 북마크 여부 체크 + * 북마크 안되어 있으면 북마크 버튼 활성화 + * 북마크 Click 이벤트 발생 + * bookmarkAID에 추가하고 북마크 아이콘 상태값Y + */ +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; +import { User } from '../../../../../shared/user/model/user.model'; +import { Store } from '@ngrx/store'; +import { AccountsUtil } from '../../../../accounts/util/accounts.util'; +import { ArticleBookmarksService } from '../../../service/article-bookmarks.service'; +import { take, catchError, tap, map, debounceTime } from 'rxjs/operators'; +import { ArticleBookmarks } from 'src/shared/article/model/article-bookmarks.model'; +import { of } from 'rxjs'; +import { Article } from 'src/shared/article/model/article.model'; +import * as AccountsAuthStore from '../../../../accounts/store/auth'; +import * as AuthStore from '../../../../accounts/store/auth'; +import { ArticleUtil } from 'src/modules/article/util/article.util'; + + + +@Component({ + selector: 'app-article-bookmarks-button', + templateUrl: './button.component.html', + styleUrls: ['./button.component.scss'] +}) +export class ButtonComponent implements OnInit { + @Input() + aid: string; + + @Output() + changedBookmarks = new EventEmitter(); + + user: User; + bookmarked: boolean; + bid: string; // articleBookmarks Id - for delete + + constructor( + private store: Store, + private articleBookmarksService: ArticleBookmarksService, + ) { + } + + ngOnInit(): void { + + this.bookmarked = false; + + AccountsUtil.getUser(this.store) + .then((user) => { + if (user) { + this.user = user; + this.checkBookmarks(); + } + }) + .catch((reason) => { + console.log(reason); + }); + + } + + onClickBookmarks() { + if (this.user) { + + if (!this.bookmarked) { + this.bookmarks(); + } else { + this.unBookmarks(); + } + + } else { + this.store.dispatch(new AccountsAuthStore.LoginRequired()); + } + } + + + checkBookmarks() { + this.bid = ArticleUtil.checkBookmarks(this.store, this.user, this.aid); + if (this.bid) { + this.bookmarked = true; + } + } + + bookmarks() { + + const bookmarks: ArticleBookmarks = { + user: this.user, + articleId: this.aid + }; + + this.articleBookmarksService.add(bookmarks).pipe( + take(1), + tap(() => { + }), + map((_bookmarks) => { + if (_bookmarks) { + this.bookmarked = true; + this.bid = _bookmarks.id; + this.changedBookmarks.emit(this.bookmarked); + + this.user.articleBookmarksList.push(_bookmarks); + + this.store.dispatch(new AuthStore.ChangeUser({ user: this.user })); + } + }), + catchError((error) => { + return of(error); + }) + ).subscribe(); + + } + + unBookmarks() { + + if (this.bookmarked && this.bid === null) { + return; + } + + this.articleBookmarksService.delete(this.bid).pipe( + take(1), + map((_bookmarks) => { + ArticleUtil.removeBookmarks(this.user, this.bid); + this.bookmarked = false; + this.bid = null; + this.changedBookmarks.emit(this.bookmarked); + + this.store.dispatch(new AuthStore.ChangeUser({ user: this.user })); + + }), + catchError((error) => { + return of(error); + }) + ).subscribe(); + + } + +} diff --git a/src/modules/article/component/bookmarks/index.ts b/src/modules/article/component/bookmarks/index.ts new file mode 100755 index 0000000..657ba3e --- /dev/null +++ b/src/modules/article/component/bookmarks/index.ts @@ -0,0 +1,5 @@ +import { ButtonComponent } from './button/button.component'; + +export const BOOKMARKS_COMPONENTS = [ + ButtonComponent, +]; diff --git a/src/modules/article/component/button/comment/comment-button.component.html b/src/modules/article/component/button/comment/comment-button.component.html new file mode 100755 index 0000000..20aa92a --- /dev/null +++ b/src/modules/article/component/button/comment/comment-button.component.html @@ -0,0 +1,6 @@ + + + diff --git a/src/modules/article/component/button/comment/comment-button.component.scss b/src/modules/article/component/button/comment/comment-button.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/modules/article/component/button/comment/comment-button.component.spec.ts b/src/modules/article/component/button/comment/comment-button.component.spec.ts new file mode 100755 index 0000000..0035f96 --- /dev/null +++ b/src/modules/article/component/button/comment/comment-button.component.spec.ts @@ -0,0 +1,24 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CommentButtonComponent } from './comment-button.component'; + +describe('ViewsButtonComponent', () => { + let component: CommentButtonComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [CommentButtonComponent] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CommentButtonComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/modules/article/component/button/comment/comment-button.component.ts b/src/modules/article/component/button/comment/comment-button.component.ts new file mode 100755 index 0000000..a02d0a3 --- /dev/null +++ b/src/modules/article/component/button/comment/comment-button.component.ts @@ -0,0 +1,56 @@ +/** + * 파 일 명: views-button.component.ts + * 작성일자: 2018-12-17 + * 작 성 자: 조현정 + * 설 명: 뷰카운터 + * 수정일시: 2018-12-20 + * 수 정 자: 조현정 + * 수정내용: Added API descr. + * 참고사항: GET: AID, counterOfViews + * PUT: AID, counterOfViews + */ +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; + +import { Store, } from '@ngrx/store'; + +import { Article } from '../../../../../shared/article/model/article.model'; +import { AccountsUtil } from '../../../../accounts/util/accounts.util'; +import * as AccountsAuthStore from '../../../../accounts/store/auth'; +import { User } from '../../../../../shared/user/model/user.model'; + +@Component({ + selector: 'app-article-comment-button', + templateUrl: './comment-button.component.html', + styleUrls: ['./comment-button.component.scss'] +}) +export class CommentButtonComponent implements OnInit { + @Input() + article: Article; + + @Output() + toggleReplyBox = new EventEmitter(); + + user: User; + + constructor( + private store: Store, + ) { } + + ngOnInit(): void { + AccountsUtil.getUser(this.store) + .then((user) => { + this.user = user; + }) + .catch((reason) => { + console.log(reason); + }); + } + + onClickToggleReplyBox() { + if (this.user) { + this.toggleReplyBox.emit(); + } else { + this.store.dispatch(new AccountsAuthStore.LoginRequired()); + } + } +} diff --git a/src/modules/article/component/button/comments-tag/comments-tag.button.component.html b/src/modules/article/component/button/comments-tag/comments-tag.button.component.html new file mode 100755 index 0000000..3f1bbe0 --- /dev/null +++ b/src/modules/article/component/button/comments-tag/comments-tag.button.component.html @@ -0,0 +1,6 @@ + + + diff --git a/src/modules/article/component/button/comments-tag/comments-tag.button.component.scss b/src/modules/article/component/button/comments-tag/comments-tag.button.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/modules/article/component/button/comments-tag/comments-tag.button.component.spec.ts b/src/modules/article/component/button/comments-tag/comments-tag.button.component.spec.ts new file mode 100755 index 0000000..09509bc --- /dev/null +++ b/src/modules/article/component/button/comments-tag/comments-tag.button.component.spec.ts @@ -0,0 +1,24 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CommentsTagButtonComponent } from './comments-tag.button.component'; + +describe('CommentsTagButtonComponent', () => { + let component: CommentsTagButtonComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [CommentsTagButtonComponent] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CommentsTagButtonComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/modules/article/component/button/comments-tag/comments-tag.button.component.ts b/src/modules/article/component/button/comments-tag/comments-tag.button.component.ts new file mode 100755 index 0000000..bc70ffa --- /dev/null +++ b/src/modules/article/component/button/comments-tag/comments-tag.button.component.ts @@ -0,0 +1,155 @@ +/** + * 파 일 명: views-button.component.ts + * 작성일자: 2018-12-17 + * 작 성 자: 조현정 + * 설 명: 뷰카운터 + * 수정일시: 2018-12-20 + * 수 정 자: 조현정 + * 수정내용: Added API descr. + * 참고사항: GET: AID, counterOfViews + * PUT: AID, counterOfViews + */ +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; +import { MatDialog } from '@angular/material'; + +import { Store, } from '@ngrx/store'; + +import { of } from 'rxjs'; +import { + take, + map, + catchError, + finalize, +} from 'rxjs/operators'; + +import { Article } from '../../../../../shared/article/model/article.model'; +import { AccountsUtil } from '../../../../accounts/util/accounts.util'; +import * as AccountsAuthStore from '../../../../accounts/store/auth'; +import { User } from '../../../../../shared/user/model/user.model'; +import { UIUtil } from '../../../../../modules/common/util/ui/dialog.util'; +import { + CommentsTagDialogComponent, + CommentsTagDialogData, + CommentsTagDialogResult, +} from '../../../../../modules/article/dialog/comments-tag/comments-tag.dialog.component'; + +import { ArticleCommentsTagService } from '../../../service/article-comments-tag.service'; +import { ArticleBestCommentsTagService } from 'src/modules/article/service/article-best-comments-tag.service'; +import { ArticleCommentsTag } from 'src/shared/article/model/article-comments-tag.model'; + +@Component({ + selector: 'app-article-comments-tag-button', + templateUrl: './comments-tag.button.component.html', + styleUrls: ['./comments-tag.button.component.scss'] +}) +export class CommentsTagButtonComponent implements OnInit { + @Input() + article: Article; + + @Output() + added = new EventEmitter(); + + user: User; + processing = false; + + constructor( + private store: Store, + private matDialog: MatDialog, + private articleCommentsTagService: ArticleCommentsTagService, + private articleBestCommentsTagService: ArticleBestCommentsTagService + ) { } + + ngOnInit(): void { + AccountsUtil.getUser(this.store) + .then((user) => { + this.user = user; + }) + .catch((reason) => { + console.log(reason); + }); + } + + async onClickReply() { + if (!this.user) { + this.store.dispatch(new AccountsAuthStore.LoginRequired()); + return; + } + + const result = await UIUtil.dialogOpen( + this.matDialog, CommentsTagDialogComponent, + { + panelClass: 'app-full-screen-dialog', + data: { + article: this.article, + user: this.user, + } + }); + + if (!result.metaCommentsTag) { + return; + } + + const articleCommentsTag: ArticleCommentsTag = { + article: { + id: this.article.id, + }, + metaCommentsTag: result.metaCommentsTag, + user: { + id: this.user.id, + } + }; + + this.processing = true; + this.articleCommentsTagService.add(articleCommentsTag).pipe( + take(1), + map((_articleCommentsTag) => { + this.article.commentsCount++; + this.added.emit(_articleCommentsTag); + + this.processBestCommentsTag(result); + }), + catchError(err => { + console.log(err); + return of(err); + }), + finalize(() => { + this.processing = false; + }) + ).subscribe(); + } + + processBestCommentsTag(commentsTag) { + this.articleCommentsTagService.getAllByArticleId(this.article.id).pipe( + map((list) => { + console.log(list); + }), + catchError(err => { + console.log(err); + return of(err); + }), + ).subscribe(); + // const articleCommentsTag: ArticleBestCommentsTag = { + // article: { + // id: this.article.id, + // }, + // metaCommentsTag: commentsTag.metaCommentsTag, + // count: 1, + // }; + + // this.articleBestCommentsTagService.add(articleCommentsTag).pipe( + // take(1), + // map((res) => { + // console.log(res); + // }), + // catchError(err => { + // console.log(err); + // return of(err); + // }), + // ).subscribe(); + + // const commentsTagList = this.article.commentsTagList; + // commentsTagList.forEach(c => { + // }); + } + +} diff --git a/src/modules/article/component/button/index.ts b/src/modules/article/component/button/index.ts new file mode 100755 index 0000000..1278e1f --- /dev/null +++ b/src/modules/article/component/button/index.ts @@ -0,0 +1,11 @@ +import { LikeButtonComponent } from './like/like-button.component'; +import { ShareButtonComponent } from './share/share-button.component'; +import { ViewsButtonComponent } from './views/views-button.component'; +import { CommentsTagButtonComponent } from './comments-tag/comments-tag.button.component'; + +export const BUTTON_COMPONENTS = [ + LikeButtonComponent, + ShareButtonComponent, + ViewsButtonComponent, + CommentsTagButtonComponent, +]; diff --git a/src/modules/article/component/button/like/like-button.component.html b/src/modules/article/component/button/like/like-button.component.html new file mode 100755 index 0000000..c85500f --- /dev/null +++ b/src/modules/article/component/button/like/like-button.component.html @@ -0,0 +1,21 @@ + + + + {{ likeCount }} + diff --git a/src/modules/article/component/button/like/like-button.component.scss b/src/modules/article/component/button/like/like-button.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/modules/article/component/button/like/like-button.component.spec.ts b/src/modules/article/component/button/like/like-button.component.spec.ts new file mode 100755 index 0000000..02b34f0 --- /dev/null +++ b/src/modules/article/component/button/like/like-button.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { LikeButtonComponent } from './like-button.component'; + +describe('LikeButtonComponent', () => { + let component: LikeButtonComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [LikeButtonComponent] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(LikeButtonComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/modules/article/component/button/like/like-button.component.ts b/src/modules/article/component/button/like/like-button.component.ts new file mode 100755 index 0000000..3cf3e51 --- /dev/null +++ b/src/modules/article/component/button/like/like-button.component.ts @@ -0,0 +1,160 @@ +/** + * 파 일 명: like-button.component.ts + * 작성일자: 2018-12-17 + * 작 성 자: 조현정 + * 설 명: 좋아요버튼 + * 수정일시: 2018-12-20 + * 수 정 자: 조현정 + * 수정내용: Added API descr. + * 참고사항: GET: AID, countOfLike + * PUT: AID, countOfLike + * countOfLike를 가져오고 이벤트 처리 결과의 countOfLike 값을 반환 + */ +import { Component, OnInit, Input, Output, EventEmitter, OnDestroy } from '@angular/core'; +import { Store, } from '@ngrx/store'; + +import { Subject, of } from 'rxjs'; +import { + take, + tap, + map, + catchError, + finalize, + debounceTime +} from 'rxjs/operators'; + +import { User } from '../../../../../shared/user/model/user.model'; + +import { Article } from '../../../../../shared/article/model/article.model'; +import { AccountsUtil } from '../../../../accounts/util/accounts.util'; +import * as AccountsAuthStore from '../../../../accounts/store/auth'; +import { ArticleLike } from '../../../../../shared/article/model/article-like.model'; +import { ArticleLikeService } from '../../../service/article-like.service'; + +@Component({ + selector: 'app-article-like-button', + templateUrl: './like-button.component.html', + styleUrls: ['./like-button.component.scss'] +}) +export class LikeButtonComponent implements OnInit, OnDestroy { + @Input() + article: Article; + + @Output() + changed = new EventEmitter(); + + user: User; + modifySubject: Subject; + processing = false; + + likeCount = 0; + userLike: ArticleLike; + + constructor( + private store: Store, + private articleLikeService: ArticleLikeService, + ) { } + + ngOnInit(): void { + this.setLikeCount(); + AccountsUtil.getUser(this.store) + .then((user) => { + if (user) { + this.user = user; + this.setUserLike(); + } + }) + .catch((reason) => { + console.log(reason); + }); + + this.modifySubject = new Subject(); + this.modifySubject.pipe( + debounceTime(1000), + map(async () => { + this.processing = true; + this.articleLikeService.save(this.userLike).pipe( + take(1), + map((articleLike) => { + this.updateUserLike(articleLike); + }), + catchError(err => { + console.log(err); + return of(err); + }), + finalize(() => { + this.processing = false; + }) + ).subscribe(); + }) + ).subscribe(); + } + + ngOnDestroy(): void { + this.modifySubject.complete(); + } + + onClickIncreaseLike(): void { + if (this.user) { + this.userLike.count++; + this.setLikeCount(); + this.modifySubject.next(); + this.changed.emit(this.likeCount); + } else { + this.store.dispatch(new AccountsAuthStore.LoginRequired()); + } + } + + getIndexOfUserLike(): number { + if (!this.article.likeList) { + this.article.likeList = []; + return -1; + } + + for (let index = 0; index < this.article.likeList.length; index++) { + const _articleLike = this.article.likeList[index]; + if (this.user.id === _articleLike.user.id) { + return index; + } + } + return -1; + } + + updateUserLike(articleLike: ArticleLike) { + this.userLike = articleLike; + const likeIndex = this.getIndexOfUserLike(); + this.article.likeList[likeIndex] = articleLike; + } + + setUserLike() { + if (!this.user) { + return; + } + + const likeIndex = this.getIndexOfUserLike(); + if (-1 === likeIndex) { + this.userLike = { + article: { + id: this.article.id, + }, + user: { + id: this.user.id, + }, + count: 0, + }; + this.article.likeList.push(this.userLike); + } else { + this.userLike = this.article.likeList[likeIndex]; + } + } + + setLikeCount() { + this.likeCount = 0; + if (!this.article.likeList || 0 === this.article.likeList.length) { + return; + } + for (const like of this.article.likeList) { + this.likeCount += like.count; + } + } +} diff --git a/src/modules/article/component/button/share/share-button.component.html b/src/modules/article/component/button/share/share-button.component.html new file mode 100755 index 0000000..ef2cc52 --- /dev/null +++ b/src/modules/article/component/button/share/share-button.component.html @@ -0,0 +1,5 @@ + + + diff --git a/src/modules/article/component/button/share/share-button.component.scss b/src/modules/article/component/button/share/share-button.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/modules/article/component/button/share/share-button.component.spec.ts b/src/modules/article/component/button/share/share-button.component.spec.ts new file mode 100755 index 0000000..c7a556a --- /dev/null +++ b/src/modules/article/component/button/share/share-button.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ShareButtonComponent } from './share-button.component'; + +describe('ShareButtonComponent', () => { + let component: ShareButtonComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ShareButtonComponent] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ShareButtonComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/modules/article/component/button/share/share-button.component.ts b/src/modules/article/component/button/share/share-button.component.ts new file mode 100755 index 0000000..93c6101 --- /dev/null +++ b/src/modules/article/component/button/share/share-button.component.ts @@ -0,0 +1,65 @@ +/** + * 파 일 명: share-button.component.ts + * 작성일자: 2018-12-17 + * 작 성 자: 조현정 + * 설 명: 공유버튼 + * 수정일시: 2018-12-20 + * 수 정 자: 조현정 + * 수정내용: Added API descr. + * 참고사항: GET: AID + * PUT: urlAID + */ +import { + Component, + Inject, + OnInit, + Output, + EventEmitter, + Input +} from '@angular/core'; +import { MatDialog, MatSnackBar, MatBottomSheet } from '@angular/material'; + +import { Article } from '../../../../../shared/article/model/article.model'; + +import { + ShareBottomSheetComponent, + ShareBottomSheetData, + ShareBottomSheetResult +} from '../../../dialog/share/share.dialog.component'; +import { UIUtil } from 'src/modules/common/util/ui/dialog.util'; + +@Component({ + selector: 'app-article-share-button', + templateUrl: './share-button.component.html', + styleUrls: ['./share-button.component.scss'] +}) +export class ShareButtonComponent implements OnInit { + @Input() + aid: string; + @Input() + link: string; + @Input() + uid: string; + + constructor( + public dialog: MatDialog, + public snackBar: MatSnackBar, + public matBottomSheet: MatBottomSheet + ) { } + + ngOnInit() { } + + async onClickShare() { + const result = await UIUtil.bottomSheetOpen( + this.matBottomSheet, ShareBottomSheetComponent, + { + data: { + aid: this.aid, + link: this.link + } + } + ); + if (result.type) { + } + } +} diff --git a/src/modules/article/component/button/views/views-button.component.html b/src/modules/article/component/button/views/views-button.component.html new file mode 100755 index 0000000..95b5dcc --- /dev/null +++ b/src/modules/article/component/button/views/views-button.component.html @@ -0,0 +1,6 @@ + + + {{ article.viewCount }} + diff --git a/src/modules/article/component/button/views/views-button.component.scss b/src/modules/article/component/button/views/views-button.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/modules/article/component/button/views/views-button.component.spec.ts b/src/modules/article/component/button/views/views-button.component.spec.ts new file mode 100755 index 0000000..f85a42e --- /dev/null +++ b/src/modules/article/component/button/views/views-button.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ViewsButtonComponent } from './views-button.component'; + +describe('ViewsButtonComponent', () => { + let component: ViewsButtonComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ViewsButtonComponent] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ViewsButtonComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/modules/article/component/button/views/views-button.component.ts b/src/modules/article/component/button/views/views-button.component.ts new file mode 100755 index 0000000..3fcd24e --- /dev/null +++ b/src/modules/article/component/button/views/views-button.component.ts @@ -0,0 +1,29 @@ +/** + * 파 일 명: views-button.component.ts + * 작성일자: 2018-12-17 + * 작 성 자: 조현정 + * 설 명: 뷰카운터 + * 수정일시: 2018-12-20 + * 수 정 자: 조현정 + * 수정내용: Added API descr. + * 참고사항: GET: AID, counterOfViews + * PUT: AID, counterOfViews + */ +import { Component, OnInit, Input } from '@angular/core'; +import { Article } from '../../../../../shared/article/model/article.model'; +@Component({ + selector: 'app-article-views-button', + templateUrl: './views-button.component.html', + styleUrls: ['./views-button.component.scss'] +}) +export class ViewsButtonComponent implements OnInit { + @Input() + article: Article; + + constructor() { } + + ngOnInit(): void { + // 나중에 수정 해야함. + // this.changed.emit(++this.article.viewCount); + } +} diff --git a/src/modules/article/component/comments-tag/best/best.component.html b/src/modules/article/component/comments-tag/best/best.component.html new file mode 100755 index 0000000..8cbc3bb --- /dev/null +++ b/src/modules/article/component/comments-tag/best/best.component.html @@ -0,0 +1,8 @@ +
+ + + {{('comments.tag.' + + commentsTag.metaCommentsTag.code) | translate}} + + +
diff --git a/src/modules/article/component/comments-tag/best/best.component.scss b/src/modules/article/component/comments-tag/best/best.component.scss new file mode 100755 index 0000000..b3015ab --- /dev/null +++ b/src/modules/article/component/comments-tag/best/best.component.scss @@ -0,0 +1,10 @@ +.mat-chip { + margin: 0 5px 5px 0 !important; + padding: 0 12px; + border-radius: 24px; + min-height: 24px; + line-height: 24px; + font-size: 12px; + white-space: nowrap; + box-sizing: border-box; +} diff --git a/src/modules/article/component/comments-tag/best/best.component.spec.ts b/src/modules/article/component/comments-tag/best/best.component.spec.ts new file mode 100755 index 0000000..cf9d57c --- /dev/null +++ b/src/modules/article/component/comments-tag/best/best.component.spec.ts @@ -0,0 +1,24 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { BestComponent } from './best.component'; + +describe('BestComponent', () => { + let component: BestComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [BestComponent] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(BestComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/modules/article/component/comments-tag/best/best.component.ts b/src/modules/article/component/comments-tag/best/best.component.ts new file mode 100755 index 0000000..e18aa2d --- /dev/null +++ b/src/modules/article/component/comments-tag/best/best.component.ts @@ -0,0 +1,30 @@ +/** + * 파 일 명: best.component.ts + * 작성일자: 2018-12-22 + * 작 성 자: 박병준 + * 설 명: best Comments component를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Component, OnInit, Input } from '@angular/core'; +import { ArticleCommentsTag } from '../../../../../shared/article/model/article-comments-tag.model'; + +@Component({ + selector: 'app-article-comments-tag-best', + templateUrl: './best.component.html', + styleUrls: ['./best.component.scss'] +}) +export class BestComponent implements OnInit { + + @Input() + commentsTagList: ArticleCommentsTag[]; + + constructor() { + } + + ngOnInit() { + + } +} diff --git a/src/modules/article/component/comments-tag/index.ts b/src/modules/article/component/comments-tag/index.ts new file mode 100755 index 0000000..c7dceda --- /dev/null +++ b/src/modules/article/component/comments-tag/index.ts @@ -0,0 +1,5 @@ +import { BestComponent } from './best/best.component'; + +export const COMMENTS_TAG_COMPONENTS = [ + BestComponent, +]; diff --git a/src/modules/article/component/display/card/component/card.component.html b/src/modules/article/component/display/card/component/card.component.html new file mode 100755 index 0000000..8772e0a --- /dev/null +++ b/src/modules/article/component/display/card/component/card.component.html @@ -0,0 +1,71 @@ + + + + + + +
+ +
+ + + +
+ +

+ {{article ? article.title : 'title'}} +

+ +

+ {{article ? article.description : 'description'}} +

+
+ + + + + +
+
    +
  • + + + + +
  • +
  • + + +
  • +
  • + + +
  • +
+
    +
  • + + +
  • +
  • + + +
  • +
+
+
+
diff --git a/src/modules/article/component/display/card/component/card.component.scss b/src/modules/article/component/display/card/component/card.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/modules/article/component/display/card/component/card.component.ts b/src/modules/article/component/display/card/component/card.component.ts new file mode 100755 index 0000000..1db778b --- /dev/null +++ b/src/modules/article/component/display/card/component/card.component.ts @@ -0,0 +1,96 @@ +import { + Component, + OnInit, + Input, + ViewChild, + ViewChildren, + QueryList, + ElementRef +} from '@angular/core'; + +import { Store, select } from '@ngrx/store'; + +import { Observable, of, Subject } from 'rxjs'; +import { + tap, + exhaustMap, + take, + map, + catchError, + finalize +} from 'rxjs/operators'; + +import { Article } from '../../../../../../shared/article/model/article.model'; +import { ArticleService } from '../../../../service/article.service'; + +import { MetaCommentsTag } from '../../../../../../shared/meta/model/meta-comments-tag.model'; +import { User } from '../../../../../../shared/user/model/user.model'; +import { AccountsUtil } from '../../../../../accounts/util/accounts.util'; +import { ArticleCommentsTag } from 'src/shared/article/model/article-comments-tag.model'; + +@Component({ + selector: 'app-article-display-card', + templateUrl: './card.component.html', + styleUrls: ['./card.component.scss'] +}) +export class CardComponent implements OnInit { + constructor( + private store: Store, + public articleService: ArticleService, + ) { + + } + @Input() + article: Article; + + @Input() + modifySubject: Subject; + + @Input() + authorFollowYn: boolean; + + user: User; + textOverflow = true; + + ngOnInit() { + this.textOverflow = true; + + AccountsUtil.getUser(this.store) + .then((user) => { + this.user = user; + }) + .catch((reason) => { + console.log(reason); + }); + + } + + onArticleChanged() { + this.modifySubject.next(); + } + updateFollow() { + console.log('updateFollow'); + } + updateReport() { + console.log('updateReport'); + } + updateBookmarks() { + console.log('updateBookmarks'); + } + + onChangedLike(countOfLike: number) { + console.log('countOfLike', countOfLike); + } + + onAddedCommentsTag(articleCommentsTag: ArticleCommentsTag) { + if (!this.article.commentsTagList) { + this.article.commentsTagList = []; + } + this.article.commentsTagList.push(articleCommentsTag); + this.modifySubject.next(); + } + + onClickTitleDescription() { + this.textOverflow = !this.textOverflow; + } +} diff --git a/src/modules/article/component/display/card/component/header/header.component.html b/src/modules/article/component/display/card/component/header/header.component.html new file mode 100755 index 0000000..95e7b52 --- /dev/null +++ b/src/modules/article/component/display/card/component/header/header.component.html @@ -0,0 +1,35 @@ + +
+ + + + + + +
+ + {{ author.nickname }} + + + + + {{ 'article.follow' | translate }} + + + + + + + + + +
diff --git a/src/modules/article/component/display/card/component/header/header.component.scss b/src/modules/article/component/display/card/component/header/header.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/modules/article/component/display/card/component/header/header.component.ts b/src/modules/article/component/display/card/component/header/header.component.ts new file mode 100755 index 0000000..8bd95be --- /dev/null +++ b/src/modules/article/component/display/card/component/header/header.component.ts @@ -0,0 +1,152 @@ +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; +import { Router } from '@angular/router'; +import { Store } from '@ngrx/store'; + +import { of } from 'rxjs'; +import { take, tap, map, catchError, finalize } from 'rxjs/operators'; + +import { User } from '../../../../../../../shared/user/model/user.model'; +import { AccountsUtil } from '../../../../../../../modules/accounts/util/accounts.util'; + +import { MatDialog } from '@angular/material'; + +import { + UnfollowDialogComponent, + UnfollowDialogData, + UnfollowDialogResult +} from '../../../../../../user/dialog/unfollow/unfollow.dialog.component'; + +import { UserFollowers } from '../../../../../../../shared/user/model/user-followers.model'; +import { UserFollowersService } from '../../../../../../user/service/user-followers.service'; +import * as AccountsAuthStore from '../../../../../../accounts/store/auth'; +import * as UserProfileStore from '../../../../../../user/store/profile'; +import * as ArticleRouterStore from '../../../../../../article/store/router'; + +@Component({ + selector: 'app-article-card-header', + templateUrl: './header.component.html', + styleUrls: ['./header.component.scss'] +}) +export class HeaderComponent implements OnInit { + @Input() + author: User; + + @Input() + aid: string; + + @Input() + authorFollowYn: boolean; + + user: User; + + // followingID: string; + + constructor( + private router: Router, + private store: Store, + private userFollowersService: UserFollowersService, + public matDialog: MatDialog, + ) { + } + + @Output() + updateFollow = new EventEmitter(); + + @Output() + updateReport = new EventEmitter(); + + ngOnInit() { + AccountsUtil.getUser(this.store) + .then((user) => { + this.user = user; + + this.authorFollowYn = this.userFollowersService.checkLocalFollowersId(this.user, this.author.id); + }) + .catch((reason) => { + console.log(reason); + }); + } + + addReport(aid: string) { + if (this.user) { + if (this.user.id === this.author.id) { + console.log('본인계정'); + } else { + // backe End here + if (this.updateReport) { + this.updateReport.emit(); + } + } + } else { + console.log('로그인 필요'); + } + } + + + onClickFollow(uid: string) { + + if (this.user) { + + const userFollow = { user: this.user, target: { id: uid } }; + this.userFollowersService.add(userFollow).pipe( + take(1), + tap(() => { + }), + map((res: any) => { + this.userFollowersService.addLocalFollowers(this.user, res, this.store); + this.updateFollow.emit(); + this.authorFollowYn = true; + }), + catchError((error) => { + return of(error); + }), + finalize(() => { + }) + ).subscribe(); + + } else { + this.store.dispatch(new AccountsAuthStore.LoginRequired()); + } + + } + + onClickUnfollow(uid: string) { + if (this.user) { + + + const userFollowersId: string = this.userFollowersService.getLocalFollowersId(this.user, uid); + + if (!userFollowersId) { + return; + } + + this.userFollowersService.delete(userFollowersId).pipe( + take(1), + map((res) => { + if (res) { + this.userFollowersService.removeLocalFollowers(this.user, userFollowersId, this.store); + this.updateFollow.emit(); + this.authorFollowYn = false; + } + }), + catchError(error => { + return of(error); + }) + ).subscribe(); + + + } + } + + onClickGotoProfile(uid: string) { + this.store.dispatch(new UserProfileStore.GotoProfile({ uid: uid })); + } + + modifyArticle(aid: string) { + this.store.dispatch(new ArticleRouterStore.GotoSave({ aid: aid })); + } + + deleteArticle(aid: string) { + console.log(`deleteArticle :: ${aid}`); + } +} diff --git a/src/modules/article/component/display/card/directive/content.directive.ts b/src/modules/article/component/display/card/directive/content.directive.ts new file mode 100755 index 0000000..4940935 --- /dev/null +++ b/src/modules/article/component/display/card/directive/content.directive.ts @@ -0,0 +1,6 @@ +import { Directive } from '@angular/core'; + +@Directive({ + selector: 'app-article-display-card-content, [app-article-display-card-content], [appArticleDisplayCardContent]', +}) +export class ContentDirective { } diff --git a/src/modules/article/component/display/card/index.ts b/src/modules/article/component/display/card/index.ts new file mode 100755 index 0000000..4d051ee --- /dev/null +++ b/src/modules/article/component/display/card/index.ts @@ -0,0 +1,10 @@ +import { CardComponent } from './component/card.component'; +import { HeaderComponent } from './component/header/header.component'; + +import { ContentDirective } from './directive/content.directive'; + +export const CARD_COMPONENTS = [ + CardComponent, + HeaderComponent, + ContentDirective, +]; diff --git a/src/modules/article/component/display/grid/component/grid.component.html b/src/modules/article/component/display/grid/component/grid.component.html new file mode 100755 index 0000000..bbc3792 --- /dev/null +++ b/src/modules/article/component/display/grid/component/grid.component.html @@ -0,0 +1,44 @@ + + {{ article.title }} + + + + + + + + + + diff --git a/src/modules/article/component/display/grid/component/grid.component.scss b/src/modules/article/component/display/grid/component/grid.component.scss new file mode 100755 index 0000000..4176a2f --- /dev/null +++ b/src/modules/article/component/display/grid/component/grid.component.scss @@ -0,0 +1,143 @@ +@mixin calc($property, $expression) { + #{$property}: -moz-calc(#{$expression}); + #{$property}: -webkit-calc(#{$expression}); + #{$property}: calc(#{$expression}); +} + +.gridImgSize { + width: 100%; + @include calc(height, '100vw/2 -30px') +} + +.mat-grid-tile-header { + height: 24px !important; + z-index: 2; +} + +.mat-grid-tile-bottom { + display: flex; + bottom: 0; + align-items: center; + height: 48px; + color: #fff; + background: rgba(0, 0, 0, .38); + overflow: hidden; + padding: 0 16px; + position: absolute; + left: 0; + right: 0; + height: 24px !important; +} + +.container { + width: 96%; + margin: 10px auto; + + .example-viewport { + @include calc(height, '100vh - 355px'); + // height: 220px; + width: 100%; + border: 1px solid black; + background: white; + border: solid 1px #999; + } + + .bookmark-viewport { + @include calc(height, '100vh - 155px'); + // height: 220px; + width: 100%; + border: 1px solid black; + background: white; + border: solid 1px #999; + } + + .gridColumn_02 { + width: 49%; + height: 49%; + float: left; + padding: 1.5px; + } + + .grid-bookmark-btn-02 { + margin: 0 auto; + width: 39%; + display: flex; + align-items: center; + float: right; + height: 48px; + color: red; + overflow: hidden; + padding: 0 16px; + position: absolute; + height: 24px !important; + font-size: 14px; + } + + .grid-bookmark-btn-03 { + margin: 0 auto; + width: 22.1%; + display: flex; + align-items: center; + float: right; + height: 48px; + color: red; + overflow: hidden; + padding: 0 16px; + position: absolute; + height: 24px !important; + font-size: 14px; + } + + .grid-tile-header-02 { + margin: 0 auto; + width: 39%; + display: flex; + align-items: center; + height: 48px; + color: #fff; + background: rgba(0, 0, 0, .38); + overflow: hidden; + padding: 0 16px; + position: absolute; + height: 24px !important; + font-size: 14px; + } + + .grid-tile-header-03 { + margin: 0 auto; + width: 22.1%; + display: flex; + align-items: center; + height: 48px; + color: #fff; + background: rgba(0, 0, 0, .38); + overflow: hidden; + padding: 0 16px; + position: absolute; + height: 24px !important; + font-size: 14px; + } + + .gridColumn_03 { + width: 32.1%; + height: 32.1%; + float: left; + padding: 1.5px; + } + + .example-item { + height: 160px; + padding: 5px 10px; + color: #999; + + .img-item { + float: left; + width: 25%; + } + + .p-item { + float: left; + width: 75%; + } + } +} diff --git a/src/modules/article/component/display/grid/component/grid.component.ts b/src/modules/article/component/display/grid/component/grid.component.ts new file mode 100755 index 0000000..0aa9a86 --- /dev/null +++ b/src/modules/article/component/display/grid/component/grid.component.ts @@ -0,0 +1,38 @@ +import { + Component, + Input, + OnInit, + ChangeDetectionStrategy +} from '@angular/core'; +import { Article } from '../../../../../../shared/article/model/article.model'; + +@Component({ + selector: 'app-article-display-grid', + templateUrl: './grid.component.html', + styleUrls: ['./grid.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class GridComponent implements OnInit { + @Input() + article: Article; + + @Input() + showTitle = true; + + @Input() + bookmark = false; + + constructor() { } + + ngOnInit(): void { } + + goDetailPage(aid: string): void { + // 상세페이지 이동 + alert('aid: ' + aid); + } + + bookmarkCancel(aid: string) { + // 북마크 취소 기능 + alert('bookmark 취소: ' + aid); + } +} diff --git a/src/modules/article/component/display/grid/directive/content.directive.ts b/src/modules/article/component/display/grid/directive/content.directive.ts new file mode 100755 index 0000000..24e0623 --- /dev/null +++ b/src/modules/article/component/display/grid/directive/content.directive.ts @@ -0,0 +1,6 @@ +import { Directive } from '@angular/core'; + +@Directive({ + selector: 'app-article-display-grid-content, [app-article-display-grid-content], [appArticleDisplayGridContent]', +}) +export class ContentDirective { } diff --git a/src/modules/article/component/display/grid/index.ts b/src/modules/article/component/display/grid/index.ts new file mode 100755 index 0000000..c7744c2 --- /dev/null +++ b/src/modules/article/component/display/grid/index.ts @@ -0,0 +1,8 @@ +import { GridComponent } from './component/grid.component'; + +import { ContentDirective } from './directive/content.directive'; + +export const GRID_COMPONENTS = [ + GridComponent, + ContentDirective, +]; diff --git a/src/modules/article/component/display/index.ts b/src/modules/article/component/display/index.ts new file mode 100755 index 0000000..4bacac3 --- /dev/null +++ b/src/modules/article/component/display/index.ts @@ -0,0 +1,9 @@ +import { CARD_COMPONENTS } from './card'; +import { GRID_COMPONENTS } from './grid'; +import { TILE_COMPONENTS } from './tile'; + +export const DISPLAY_COMPONENTS = [ + ...CARD_COMPONENTS, + ...GRID_COMPONENTS, + ...TILE_COMPONENTS, +]; diff --git a/src/modules/article/component/display/tile/component/tile.component.html b/src/modules/article/component/display/tile/component/tile.component.html new file mode 100755 index 0000000..3568f72 --- /dev/null +++ b/src/modules/article/component/display/tile/component/tile.component.html @@ -0,0 +1,8 @@ + +
+
+ {{ article.title }} +
+ +
+
diff --git a/src/modules/article/component/display/tile/component/tile.component.scss b/src/modules/article/component/display/tile/component/tile.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/modules/article/component/display/tile/component/tile.component.ts b/src/modules/article/component/display/tile/component/tile.component.ts new file mode 100755 index 0000000..94bbc73 --- /dev/null +++ b/src/modules/article/component/display/tile/component/tile.component.ts @@ -0,0 +1,44 @@ +import { + Component, + Input, + OnInit, + EventEmitter, +} from '@angular/core'; +import { Article } from '../../../../../../shared/article/model/article.model'; + +@Component({ + selector: 'app-article-display-tile', + templateUrl: './tile.component.html', + styleUrls: ['./tile.component.scss'], +}) +export class TileComponent implements OnInit { + @Input() + article: Article; + + @Input() + viewTitleYn = true; + + @Input() + selected = new EventEmitter
(); + + @Input() + clicked = new EventEmitter
(); + + constructor() { } + + ngOnInit(): void { } + + onClick() { + this.onSelected(); + if (this.selected) { + this.selected.emit(this.article); + } + if (this.clicked) { + this.clicked.emit(this.article); + } + } + + onSelected() { + + } +} diff --git a/src/modules/article/component/display/tile/directive/content.directive.ts b/src/modules/article/component/display/tile/directive/content.directive.ts new file mode 100755 index 0000000..7adf43a --- /dev/null +++ b/src/modules/article/component/display/tile/directive/content.directive.ts @@ -0,0 +1,6 @@ +import { Directive } from '@angular/core'; + +@Directive({ + selector: 'app-article-display-tile-content, [app-article-display-tile-content], [appArticleDisplayTileContent]', +}) +export class ContentDirective { } diff --git a/src/modules/article/component/display/tile/index.ts b/src/modules/article/component/display/tile/index.ts new file mode 100755 index 0000000..311c706 --- /dev/null +++ b/src/modules/article/component/display/tile/index.ts @@ -0,0 +1,7 @@ +import { TileComponent } from './component/tile.component'; +import { ContentDirective } from './directive/content.directive'; + +export const TILE_COMPONENTS = [ + TileComponent, + ContentDirective, +]; diff --git a/src/modules/article/component/index.ts b/src/modules/article/component/index.ts new file mode 100755 index 0000000..4ba14f0 --- /dev/null +++ b/src/modules/article/component/index.ts @@ -0,0 +1,17 @@ +import { BUTTON_COMPONENTS } from './button'; +import { DISPLAY_COMPONENTS } from './display'; +import { VIEWER_COMPONENTS } from './viewer'; +import { TAG_COMPONENTS } from './tag'; +import { WRITE_COMPONENTS } from './write'; +import { BOOKMARKS_COMPONENTS } from './bookmarks'; +import { COMMENTS_TAG_COMPONENTS } from './comments-tag'; + +export const COMPONENTS = [ + ...BUTTON_COMPONENTS, + ...DISPLAY_COMPONENTS, + ...VIEWER_COMPONENTS, + ...TAG_COMPONENTS, + ...WRITE_COMPONENTS, + ...BOOKMARKS_COMPONENTS, + ...COMMENTS_TAG_COMPONENTS, +]; diff --git a/src/modules/article/component/tag/index.ts b/src/modules/article/component/tag/index.ts new file mode 100755 index 0000000..885c5f5 --- /dev/null +++ b/src/modules/article/component/tag/index.ts @@ -0,0 +1,8 @@ +import { WriteComponent } from './write/write.component'; +import { SearchComponent } from './search/search.component'; + + +export const TAG_COMPONENTS = [ + WriteComponent, + SearchComponent +]; diff --git a/src/modules/article/component/tag/search/search.component.html b/src/modules/article/component/tag/search/search.component.html new file mode 100755 index 0000000..bca018a --- /dev/null +++ b/src/modules/article/component/tag/search/search.component.html @@ -0,0 +1,10 @@ + diff --git a/src/modules/article/component/tag/search/search.component.scss b/src/modules/article/component/tag/search/search.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/modules/article/component/tag/search/search.component.ts b/src/modules/article/component/tag/search/search.component.ts new file mode 100755 index 0000000..fd692b5 --- /dev/null +++ b/src/modules/article/component/tag/search/search.component.ts @@ -0,0 +1,132 @@ +import { Component, OnInit, Output, EventEmitter } from '@angular/core'; +import { Observable, of } from 'rxjs'; +import { tap, map, catchError, finalize, take } from 'rxjs/operators'; +import { ArticleTagService } from 'src/modules/article/service/article-tag.service'; +import { ArticleTag } from 'src/shared/article/model/article-tag.model'; + + +@Component({ + selector: 'app-article-tag-search', + templateUrl: './search.component.html', + styleUrls: ['./search.component.scss'] +}) +export class SearchComponent implements OnInit { + + @Output() + selected = new EventEmitter(); + + tags: ArticleTag[] = [ + { + tagName: 'BL', + tagCount: 1 + }, + { + tagName: 'GL', + tagCount: 2 + }, + { + tagName: '배틀', + tagCount: 3 + }, + { + tagName: '개그', + tagCount: 4 + }, + { + tagName: '도박', + tagCount: 5 + }, + { + tagName: '돼지', + tagCount: 6 + }, + { + tagName: '김민주', + tagCount: 7 + }, + { + tagName: '음악', + tagCount: 8 + }, + { + tagName: '쯔위', + tagCount: 9 + }, + { + tagName: '결투', + tagCount: 10 + }, + { + tagName: '드라마', + tagCount: 11 + }, + { + tagName: '이세계', + tagCount: 12 + }, + { + tagName: '모험', + tagCount: 13 + }, + { + tagName: '심쿵', + tagCount: 14 + }, + { + tagName: '프듀48', + tagCount: 15 + }, + { + tagName: '이쁘다', + tagCount: 16 + }, + { + tagName: '지효 ', + tagCount: 17 + }, + { + tagName: '판타지', + tagCount: 18 + }, + { + tagName: '일상', + tagCount: 19 + }, + { + tagName: '공포', + tagCount: 20 + }, + + ]; + + + constructor( + private tagService: ArticleTagService + ) { + } + + ngOnInit(): void { + // this.tagService.getAllPopular().pipe( + // take(1), + // tap(), + // map((res) => { + // console.log(res); + // this.tags = res; + // }), + // catchError((error) => { + // return of(error); + // }), + // finalize(() => { }) + // ).subscribe(); + } + + // 임시 함수 + // todo: 태그가 사용된 작품 중에 가장 인기있는 작품의 이미지 조회 + getRepresentImage(tag: ArticleTag) { + return 'assets/img/article/S/' + tag.tagCount + '.jpg'; + } + + onClickSelect(tag: ArticleTag) { + this.selected.emit(tag); + } +} diff --git a/src/modules/article/component/tag/write/write.component.html b/src/modules/article/component/tag/write/write.component.html new file mode 100755 index 0000000..4480dff --- /dev/null +++ b/src/modules/article/component/tag/write/write.component.html @@ -0,0 +1,5 @@ +
+
+ +
+
diff --git a/src/modules/article/component/tag/write/write.component.scss b/src/modules/article/component/tag/write/write.component.scss new file mode 100755 index 0000000..05600da --- /dev/null +++ b/src/modules/article/component/tag/write/write.component.scss @@ -0,0 +1,3 @@ +.mat-hint { + font-size: 75% +} diff --git a/src/modules/article/component/tag/write/write.component.spec.ts b/src/modules/article/component/tag/write/write.component.spec.ts new file mode 100755 index 0000000..a9636f8 --- /dev/null +++ b/src/modules/article/component/tag/write/write.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { WriteComponent } from './write.component'; + +describe('WriteComponent', () => { + let component: WriteComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [WriteComponent] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(WriteComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/modules/article/component/tag/write/write.component.ts b/src/modules/article/component/tag/write/write.component.ts new file mode 100755 index 0000000..6b14d2b --- /dev/null +++ b/src/modules/article/component/tag/write/write.component.ts @@ -0,0 +1,35 @@ +/** + * 파 일 명: write.component.ts + * 작성일자: 2018-12-20 + * 작 성 자: 최지련 + * 설 명: Tag write component를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Component, OnInit, Input, } from '@angular/core'; + +import { ArticleTagService } from '../../../service/article-tag.service'; +import { ArticleTag } from '../../../../../shared/article/model/article-tag.model'; + +@Component({ + selector: 'app-article-tag-write', + templateUrl: './write.component.html', + styleUrls: ['./write.component.scss'] +}) +export class WriteComponent implements OnInit { + @Input() + tagList: ArticleTag[]; + + constructor( + private articleTagService: ArticleTagService, + ) { + } + + ngOnInit(): void { + if (!this.tagList) { + this.tagList = []; + } + } +} diff --git a/src/modules/article/component/viewer/component/viewer.component.html b/src/modules/article/component/viewer/component/viewer.component.html new file mode 100755 index 0000000..a543dec --- /dev/null +++ b/src/modules/article/component/viewer/component/viewer.component.html @@ -0,0 +1,47 @@ +
+ +
+
+
    +
  • +

    + {{ article.title }} +

    +
  • +
+
    +
  • + +
  • +
+
+
+
+ + + + +
+
+
+
    + +
+
    +
  • + + +
  • +
  • + + +
  • +
+
+
+
+
diff --git a/src/modules/article/component/viewer/component/viewer.component.scss b/src/modules/article/component/viewer/component/viewer.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/modules/article/component/viewer/component/viewer.component.spec.ts b/src/modules/article/component/viewer/component/viewer.component.spec.ts new file mode 100755 index 0000000..521d9c9 --- /dev/null +++ b/src/modules/article/component/viewer/component/viewer.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ViewerComponent } from './viewer.component'; + +describe('ViewerComponent', () => { + let component: ViewerComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ViewerComponent] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ViewerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/modules/article/component/viewer/component/viewer.component.ts b/src/modules/article/component/viewer/component/viewer.component.ts new file mode 100755 index 0000000..1eb06a9 --- /dev/null +++ b/src/modules/article/component/viewer/component/viewer.component.ts @@ -0,0 +1,68 @@ +import { Component, OnInit, Input, OnDestroy, EventEmitter, Output } from '@angular/core'; +import { trigger, transition, style, animate, state } from '@angular/animations'; + +import { ArticleService } from '../../../service/article.service'; +import { Article } from '../../../../../shared/article/model/article.model'; + +@Component({ + selector: 'app-article-viewer', + templateUrl: './viewer.component.html', + styleUrls: ['./viewer.component.scss'], + animations: [ + trigger('barShowHideAnimation', [ + // ... + state('show', style({ + opacity: 1, + display: 'block', + })), + state('hide', style({ + opacity: 0, + display: 'none', + })), + transition('show => hide', [ + animate('1s') + ]), + transition('hide => show', [ + animate('0.5s') + ]), + ]), + ], +}) +export class ViewerComponent implements OnInit, OnDestroy { + @Input() + article: Article; + + @Output() + close = new EventEmitter(); + + showBar = true; + + constructor( + public articleService: ArticleService, + ) { + } + + ngOnInit() { + setTimeout(() => { + this.showBar = false; + }, 1000); + } + + ngOnDestroy(): void { + } + + onClickViewer(event: Event) { + this.showBar = !this.showBar; + } + + onChangedLike(countOfLike: number) { + console.log('countOfLike', countOfLike); + } + + onClickClose(event: Event) { + // if (!this.showBar) { + // return; + // } + this.close.emit(); + } +} diff --git a/src/modules/article/component/viewer/directive/content.directive.ts b/src/modules/article/component/viewer/directive/content.directive.ts new file mode 100755 index 0000000..af0d64c --- /dev/null +++ b/src/modules/article/component/viewer/directive/content.directive.ts @@ -0,0 +1,6 @@ +import { Directive } from '@angular/core'; + +@Directive({ + selector: 'app-article-viewer-content, [app-article-viewer-content], [appArticleViewerContent]', +}) +export class ContentDirective { } diff --git a/src/modules/article/component/viewer/directive/footer.directive.ts b/src/modules/article/component/viewer/directive/footer.directive.ts new file mode 100755 index 0000000..c5e2fdb --- /dev/null +++ b/src/modules/article/component/viewer/directive/footer.directive.ts @@ -0,0 +1,6 @@ +import { Directive } from '@angular/core'; + +@Directive({ + selector: `app-article-viewer-footer, [app-article-viewer-footer], [appArticleViewerFooter]`, +}) +export class FooterDirective { } diff --git a/src/modules/article/component/viewer/directive/header.directive.ts b/src/modules/article/component/viewer/directive/header.directive.ts new file mode 100755 index 0000000..2c41eea --- /dev/null +++ b/src/modules/article/component/viewer/directive/header.directive.ts @@ -0,0 +1,6 @@ +import { Directive } from '@angular/core'; + +@Directive({ + selector: `app-article-viewer-header, [app-article-viewer-header], [appArticleViewerHeader]`, +}) +export class HeaderDirective { } diff --git a/src/modules/article/component/viewer/directive/title.directive.ts b/src/modules/article/component/viewer/directive/title.directive.ts new file mode 100755 index 0000000..392e180 --- /dev/null +++ b/src/modules/article/component/viewer/directive/title.directive.ts @@ -0,0 +1,6 @@ +import { Directive } from '@angular/core'; + +@Directive({ + selector: `app-article-viewer-title, [app-article-viewer-title], [appArticleViewerTitle]`, +}) +export class TitleDirective { } diff --git a/src/modules/article/component/viewer/index.ts b/src/modules/article/component/viewer/index.ts new file mode 100755 index 0000000..a88be4e --- /dev/null +++ b/src/modules/article/component/viewer/index.ts @@ -0,0 +1,14 @@ +import { ViewerComponent } from './component/viewer.component'; + +import { ContentDirective } from './directive/content.directive'; +import { FooterDirective } from './directive/footer.directive'; +import { HeaderDirective } from './directive/header.directive'; +import { TitleDirective } from './directive/title.directive'; + +export const VIEWER_COMPONENTS = [ + ViewerComponent, + ContentDirective, + FooterDirective, + HeaderDirective, + TitleDirective, +]; diff --git a/src/modules/article/component/write/component/write.component.html b/src/modules/article/component/write/component/write.component.html new file mode 100755 index 0000000..2668c9d --- /dev/null +++ b/src/modules/article/component/write/component/write.component.html @@ -0,0 +1,59 @@ + + + + + + +
+
+

+ {{ 'myPage.isOriginalArticle' | translate }} +

+
+

{{ 'myPage.msgOriginalArticleInfo' | translate }}

+
+
+ +
+
+
+
+
+

{{ 'config.ageLimit' | translate }}

+
+

{{ 'article.msgDetailAgeLimit' | translate }}

+
+
+ +
+
+
+
+
+

{{ 'myPage.basicInfo' | translate }}

+
+ + + {{title.value?.length || 0}}/{{max_article_title_leng}} + Title is required + You must enter a minimum of {{min_article_title_leng}} + characters for the title. + You must enter a maximum of {{max_article_title_leng}} + characters for the title. + + + + + {{description.value?.length || 0}}/{{max_article_desc_leng}} + You must enter a minimum of {{min_article_desc_leng}} + characters for the description. + You must enter a maximum of {{max_article_desc_leng}} + characters for the description. + +
+
+
+ +
diff --git a/src/modules/article/component/write/component/write.component.scss b/src/modules/article/component/write/component/write.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/modules/article/component/write/component/write.component.spec.ts b/src/modules/article/component/write/component/write.component.spec.ts new file mode 100755 index 0000000..a9636f8 --- /dev/null +++ b/src/modules/article/component/write/component/write.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { WriteComponent } from './write.component'; + +describe('WriteComponent', () => { + let component: WriteComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [WriteComponent] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(WriteComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/modules/article/component/write/component/write.component.ts b/src/modules/article/component/write/component/write.component.ts new file mode 100755 index 0000000..dea96be --- /dev/null +++ b/src/modules/article/component/write/component/write.component.ts @@ -0,0 +1,150 @@ +import { Component, OnInit, Input, Output, EventEmitter, OnDestroy } from '@angular/core'; +import { MatDialog } from '@angular/material'; +import { FormGroup, FormBuilder, FormControl, Validators, FormGroupDirective, NgForm } from '@angular/forms'; +import { ErrorStateMatcher } from '@angular/material'; +import { map } from 'rxjs/operators'; +import { Subscription } from 'rxjs'; + +import { AgeLimitType } from '../../../../../shared/common/type/ageLimit.type'; +import { AgeLimitDialogComponent } from '../../../../common/shared/dialog/age-limit/age-limit.dialog.component'; + +import { ArticleService } from '../../../service/article.service'; +import { Article } from '../../../../../shared/article/model/article.model'; + +export class ArticleErrorStateMatcher implements ErrorStateMatcher { + isErrorState(formControl: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { + const isSubmitted = form && form.submitted; + return !!(formControl && formControl.invalid && (formControl.dirty || formControl.touched || isSubmitted)); + } +} + +@Component({ + selector: 'app-article-write', + templateUrl: './write.component.html', + styleUrls: ['./write.component.scss'] +}) +export class WriteComponent implements OnInit, OnDestroy { + @Input() + article: Article; + + @Output() + changedCanSave = new EventEmitter(); + + @Output() + changedSaveDirty = new EventEmitter(); + + articleForm: FormGroup; + errorStateMatcher = new ArticleErrorStateMatcher(); + articleFormValidSubscription: Subscription; + /** + * 연령제한 + * + * ageLimitTitle : 팝업 타이틀 + * ageLimits : 선택가능 연령제한 옵션들 및 선택여부 + */ + ageLimitTitle = 'myPage.ageRestriction'; + ageLimits = [ + { type: AgeLimitType.ALL, label: 'myPage.allAges', checked: false }, + { type: AgeLimitType.SOFT, label: 'myPage.expressMildSexual', checked: false }, + { type: AgeLimitType.HARD, label: 'myPage.expressViolenceAndCrotes', checked: false }, + ]; + + min_article_title_leng = 2; + max_article_title_leng = 32; + min_article_desc_leng = 2; + max_article_desc_leng = 3000; + + get titleFormControl() { return this.articleForm.get('title'); } + get descriptionFormControl() { return this.articleForm.get('description'); } + + constructor( + private dialog: MatDialog, + private formBuilder: FormBuilder, + public articleService: ArticleService, + ) { + } + + ngOnInit() { + this.initArticleForm(); + } + + ngOnDestroy(): void { + if (this.articleFormValidSubscription) { + this.articleFormValidSubscription.unsubscribe(); + } + } + + initArticleForm() { + // 값 초기화 수행 + if (!this.article.tagList) { + this.article.tagList = []; + } + if (!this.article.ageLimit) { + this.article.ageLimit = AgeLimitType.ALL; + } + if (!this.article.original) { + this.article.original = false; + } + + this.articleForm = this.formBuilder.group({ + title: new FormControl( + this.article.title, + [ + Validators.required, + Validators.minLength(this.min_article_title_leng), + Validators.maxLength(this.max_article_title_leng), + ]), + description: new FormControl( + this.article.description, + [ + Validators.minLength(this.min_article_desc_leng), + Validators.maxLength(this.max_article_desc_leng), + ]), + }); + + + // 값 초기화 수행 (ageLimit) + (!this.article.ageLimit) ? this.ageLimits[0].checked = true : this.ageLimits[0].checked = false; + (this.article.ageLimit % 2) ? this.ageLimits[1].checked = true : this.ageLimits[1].checked = false; + Math.floor(this.article.ageLimit / 2) ? this.ageLimits[2].checked = true : this.ageLimits[2].checked = false; + + this.articleFormValidSubscription = this.articleForm.statusChanges.pipe( + map(() => { + this.changedCanSave.emit(this.articleForm.valid); + this.changedSaveDirty.emit(this.articleForm.dirty); + }) + ).subscribe(); + this.articleForm.updateValueAndValidity(); + } + + onClickOriginal() { + this.article.original = !this.article.original; + } + + onClickAgeLimit(event) { + event.preventDefault(); + + const dialogAgeLimitRef = this.dialog.open(AgeLimitDialogComponent, { + data: { + title: this.ageLimitTitle, + ageLimits: this.ageLimits, + } + }); + + dialogAgeLimitRef.afterClosed().subscribe(result => { + let tempAgeLimits = 0; + + if (result) { + for (const key in result) { + if (result[key].checked) { + tempAgeLimits += result[key].type; + } + } + // console.log(`tempAgeLimits :: ${tempAgeLimits}`); + + this.article.ageLimit = tempAgeLimits; + this.ageLimits = result; + } + }); + } +} diff --git a/src/modules/article/component/write/directive/content.directive.ts b/src/modules/article/component/write/directive/content.directive.ts new file mode 100755 index 0000000..8b121cf --- /dev/null +++ b/src/modules/article/component/write/directive/content.directive.ts @@ -0,0 +1,6 @@ +import { Directive } from '@angular/core'; + +@Directive({ + selector: 'app-article-write-content, [app-article-write-content], [appArticleWriteContent]', +}) +export class ContentDirective { } diff --git a/src/modules/article/component/write/directive/footer.directive.ts b/src/modules/article/component/write/directive/footer.directive.ts new file mode 100755 index 0000000..bd11bb5 --- /dev/null +++ b/src/modules/article/component/write/directive/footer.directive.ts @@ -0,0 +1,6 @@ +import { Directive } from '@angular/core'; + +@Directive({ + selector: `app-article-write-footer, [app-article-write-footer], [appArticleWriteFooter]`, +}) +export class FooterDirective { } diff --git a/src/modules/article/component/write/directive/header.directive.ts b/src/modules/article/component/write/directive/header.directive.ts new file mode 100755 index 0000000..2c553eb --- /dev/null +++ b/src/modules/article/component/write/directive/header.directive.ts @@ -0,0 +1,6 @@ +import { Directive } from '@angular/core'; + +@Directive({ + selector: `app-article-write-header, [app-article-write-header], [appArticleWriteHeader]`, +}) +export class HeaderDirective { } diff --git a/src/modules/article/component/write/directive/title.directive.ts b/src/modules/article/component/write/directive/title.directive.ts new file mode 100755 index 0000000..daae010 --- /dev/null +++ b/src/modules/article/component/write/directive/title.directive.ts @@ -0,0 +1,6 @@ +import { Directive } from '@angular/core'; + +@Directive({ + selector: `app-article-write-title, [app-article-write-title], [appArticleWriteTitle]`, +}) +export class TitleDirective { } diff --git a/src/modules/article/component/write/index.ts b/src/modules/article/component/write/index.ts new file mode 100755 index 0000000..f49da9e --- /dev/null +++ b/src/modules/article/component/write/index.ts @@ -0,0 +1,14 @@ +import { WriteComponent } from './component/write.component'; + +import { ContentDirective } from './directive/content.directive'; +import { FooterDirective } from './directive/footer.directive'; +import { HeaderDirective } from './directive/header.directive'; +import { TitleDirective } from './directive/title.directive'; + +export const WRITE_COMPONENTS = [ + WriteComponent, + ContentDirective, + FooterDirective, + HeaderDirective, + TitleDirective, +]; diff --git a/src/modules/article/dialog/comments-tag/comments-tag.dialog.component.html b/src/modules/article/dialog/comments-tag/comments-tag.dialog.component.html new file mode 100755 index 0000000..158f30d --- /dev/null +++ b/src/modules/article/dialog/comments-tag/comments-tag.dialog.component.html @@ -0,0 +1,49 @@ +
+ +
+
+
    +
  • +

    + {{ article.title }} +

    +
  • +
+
    +
  • + +
  • +
+
+
+
+ +
+
+ + + + {{('comments.tag.' + commentsTag.code) | translate}} + + + +
+
+
+ +
+
diff --git a/src/modules/article/dialog/comments-tag/comments-tag.dialog.component.scss b/src/modules/article/dialog/comments-tag/comments-tag.dialog.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/modules/article/dialog/comments-tag/comments-tag.dialog.component.ts b/src/modules/article/dialog/comments-tag/comments-tag.dialog.component.ts new file mode 100755 index 0000000..0e444fa --- /dev/null +++ b/src/modules/article/dialog/comments-tag/comments-tag.dialog.component.ts @@ -0,0 +1,90 @@ +import { Component, Inject, OnInit, Output, EventEmitter } from '@angular/core'; +import { MatDialogRef, MAT_DIALOG_DATA, MatChip } from '@angular/material'; + +import { Store, select } from '@ngrx/store'; + +import { Observable, of, Subject } from 'rxjs'; +import { + tap, + exhaustMap, + take, + map, + catchError, + finalize +} from 'rxjs/operators'; + +import { User } from '../../../../shared/user/model/user.model'; +import * as MetaStore from '../../../meta/store'; +import { MetaCommentsTag } from '../../../../shared/meta/model/meta-comments-tag.model'; +import { MetaCommentsTagTarget } from '../../../../shared/meta/type/meta-comments-tag-target.type'; +import { Article } from '../../../../shared/article/model/article.model'; + +export interface CommentsTagDialogData { + article: Article; + user: User; +} + +export interface CommentsTagDialogResult { + metaCommentsTag: MetaCommentsTag | undefined; +} + +@Component({ + selector: 'app-article-comments-tag-dialog', + templateUrl: 'comments-tag.dialog.component.html', + styleUrls: ['./comments-tag.dialog.component.scss'] +}) +export class CommentsTagDialogComponent implements OnInit { + metaCommentsTagList: MetaCommentsTag[][]; + + article: Article; + user: User; + existCodes: string[]; + + constructor( + private store: Store, + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: CommentsTagDialogData, + ) { + this.user = data.user; + this.article = data.article; + } + + ngOnInit() { + this.store.pipe( + take(1), + select(MetaStore.CommentsTagSelector.selectCommentsTagListMap), + map((commentsTagListMap) => { + // this.checkDisabled(); + this.metaCommentsTagList = [ + commentsTagListMap.get(MetaCommentsTagTarget.Article), + commentsTagListMap.get(MetaCommentsTagTarget.Author), + ].sort(() => Math.random() - 0.5); + }), + ).subscribe(); + + } + + checkDisabled() { + this.existCodes = []; + this.article.commentsTagList.forEach(commentsTag => { + if (commentsTag.user.id === this.user.id) { + this.existCodes.push(commentsTag.metaCommentsTag.code); + } + }); + } + + onClickClose() { + this.dialogRef.close({ + metaCommentsTag: undefined, + }); + } + + onClickSelect(metaCommentsTag: MetaCommentsTag) { + // if (this.existCodes.indexOf(metaCommentsTag.code) >= 0) { + // return; + // } + this.dialogRef.close({ + metaCommentsTag: metaCommentsTag, + }); + } +} diff --git a/src/modules/article/dialog/index.ts b/src/modules/article/dialog/index.ts new file mode 100755 index 0000000..5fb8f2b --- /dev/null +++ b/src/modules/article/dialog/index.ts @@ -0,0 +1,9 @@ +import { CommentsTagDialogComponent } from './comments-tag/comments-tag.dialog.component'; +import { ReportDialogComponent } from './report/report.dialog.component'; +import { ShareBottomSheetComponent } from './share/share.dialog.component'; + +export const DIALOGS = [ + CommentsTagDialogComponent, + ReportDialogComponent, + ShareBottomSheetComponent, +]; diff --git a/src/modules/article/dialog/report/report.dialog.component.html b/src/modules/article/dialog/report/report.dialog.component.html new file mode 100755 index 0000000..b04d1b7 --- /dev/null +++ b/src/modules/article/dialog/report/report.dialog.component.html @@ -0,0 +1,8 @@ +

+ {{ 'common.msgReportingArticle' | translate }} +

+

+
+

+

+
diff --git a/src/modules/article/dialog/report/report.dialog.component.ts b/src/modules/article/dialog/report/report.dialog.component.ts new file mode 100755 index 0000000..e8ce62c --- /dev/null +++ b/src/modules/article/dialog/report/report.dialog.component.ts @@ -0,0 +1,18 @@ +import { Component, Inject, OnInit, Output, EventEmitter } from '@angular/core'; +import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material'; + +export interface DialogData { + spam: string; + inap: string; +} + +@Component({ + selector: 'app-article-report-dialog', + templateUrl: 'report.dialog.component.html' +}) +export class ReportDialogComponent { + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: DialogData + ) { } +} diff --git a/src/modules/article/dialog/share/share.dialog.component.html b/src/modules/article/dialog/share/share.dialog.component.html new file mode 100755 index 0000000..c94ca43 --- /dev/null +++ b/src/modules/article/dialog/share/share.dialog.component.html @@ -0,0 +1,33 @@ + diff --git a/src/modules/article/dialog/share/share.dialog.component.scss b/src/modules/article/dialog/share/share.dialog.component.scss new file mode 100755 index 0000000..6321ea5 --- /dev/null +++ b/src/modules/article/dialog/share/share.dialog.component.scss @@ -0,0 +1,7 @@ +.mat-card-avatar { + height: 25px; + width: 25px; + border-radius: 50%; + flex-shrink: 0; + object-fit: cover; +} diff --git a/src/modules/article/dialog/share/share.dialog.component.ts b/src/modules/article/dialog/share/share.dialog.component.ts new file mode 100755 index 0000000..d1e0327 --- /dev/null +++ b/src/modules/article/dialog/share/share.dialog.component.ts @@ -0,0 +1,76 @@ +import { Component, OnInit, Inject, Input } from '@angular/core'; +import { + MatBottomSheetRef, + MAT_BOTTOM_SHEET_DATA +} from '@angular/material'; +import { TranslateService } from '@ngx-translate/core'; + +import { ArticleShare } from '../../../../shared/article/type/article-share.type'; + +export interface ShareBottomSheetData { + aid: string; + link: string; +} + +export interface ShareBottomSheetResult { + type: ArticleShare; +} + +@Component({ + selector: 'app-article-share-dialog', + templateUrl: './share.dialog.component.html', + styleUrls: ['./share.dialog.component.scss'] +}) +export class ShareBottomSheetComponent implements OnInit { + articleShare = ArticleShare; + + facebook: string; + twitter: string; + googlePlus: string; + + constructor( + private translateService: TranslateService, + private bottomSheetRef: MatBottomSheetRef, + @Inject(MAT_BOTTOM_SHEET_DATA) private data: ShareBottomSheetData, + ) { } + + ngOnInit(): void { + this.translateService.get([ + 'login.facebook', + 'login.twitter', + 'login.googlePlus']) + .subscribe(translations => { + this.facebook = translations['login.facebook']; + this.twitter = translations['login.twitter']; + this.googlePlus = translations['login.googlePlus']; + }); + } + + onClickClose(type: ArticleShare): void { + console.log(`[ShareBottomSheetComponent] onClickClose :: ArticleShare.${type}`); + + this.bottomSheetRef.dismiss({ + type: type, + }); + + const ourURL = 'http://54.180.12.41:4200/'; + switch (type) { + case ArticleShare.Facebook: + const snsFacebook = 'https://www.facebook.com/sharer/sharer.php?u=' + ourURL + 'article/p/' + this.data.aid; + window.open(snsFacebook, '', 'scrollbars=no, width=600,height = 600'); + break; + case ArticleShare.Twitter: + const snsTwitter = 'https://twitter.com/share?url=' + ourURL + 'article/p/' + this.data.aid; + window.open(snsTwitter, '', 'scrollbars=no, width=600,height = 600'); + break; + case ArticleShare.GooglePlus: + const snsGooglePlus = 'https://plus.google.com/share?url=' + ourURL + 'article/p/' + this.data.aid; + window.open(snsGooglePlus, '', 'scrollbars=no, width=600,height = 600'); + break; + case ArticleShare.Email: + break; + case ArticleShare.CopyLink: + break; + } + } +} diff --git a/src/modules/article/interface/saveable.interface.ts b/src/modules/article/interface/saveable.interface.ts new file mode 100755 index 0000000..84ca6ce --- /dev/null +++ b/src/modules/article/interface/saveable.interface.ts @@ -0,0 +1,8 @@ +import { Action } from '@ngrx/store'; + +export type CannotSaveMessages = Map; + +export interface Saveable { + canSave(): Promise; + save(): Promise; +} diff --git a/src/modules/article/service/article-best-comments-tag.service.ts b/src/modules/article/service/article-best-comments-tag.service.ts new file mode 100755 index 0000000..fe351e9 --- /dev/null +++ b/src/modules/article/service/article-best-comments-tag.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; + +import { AbstractRestService } from '../../common/util/service/abstract-rest.service'; +import { environment } from '../../../environments/environment.hmr'; +import { ArticleBestCommentsTag } from 'src/shared/article/model/article-best-comments-tag.model'; +import { Observable } from 'rxjs'; + +@Injectable() +export class ArticleBestCommentsTagService extends AbstractRestService { + + public constructor( + httpClient: HttpClient, + ) { + super(httpClient, environment.apiEntryPoint + '/article/best_comments_tag'); + } +} diff --git a/src/modules/article/service/article-bookmarks.service.ts b/src/modules/article/service/article-bookmarks.service.ts new file mode 100755 index 0000000..0315e88 --- /dev/null +++ b/src/modules/article/service/article-bookmarks.service.ts @@ -0,0 +1,22 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; + +import { AbstractRestService } from '../../common/util/service/abstract-rest.service'; +import { environment } from '../../../environments/environment.hmr'; +import { ArticleBookmarks } from '../../../shared/article/model/article-bookmarks.model'; +import { Observable } from 'rxjs'; +import { ArticleMongodb } from 'src/shared/article/model/article.mongodb.model'; + + +@Injectable() +export class ArticleBookmarksService extends AbstractRestService { + public constructor(httpClient: HttpClient) { + super(httpClient, environment.apiEntryPoint + '/article/bookmarks'); + } + + + public getAllArticleByUid(uid: string): Observable { + return this.httpClient.get(`${this.apiEntryPoint}/user/${uid}`); + } + +} diff --git a/src/modules/article/service/article-comments-tag.service.ts b/src/modules/article/service/article-comments-tag.service.ts new file mode 100755 index 0000000..b8b3e44 --- /dev/null +++ b/src/modules/article/service/article-comments-tag.service.ts @@ -0,0 +1,24 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; + +import { Observable } from 'rxjs'; +import { AbstractRestService } from '../../common/util/service/abstract-rest.service'; +import { environment } from '../../../environments/environment.hmr'; +import { httpOptions } from '../../common/util/service/abstract.service'; +import { ArticleCommentsTag } from '../../../shared/article/model/article-comments-tag.model'; +import { TagService } from '../../tag/type/tag-service.type'; + +@Injectable() +export class ArticleCommentsTagService extends AbstractRestService { + + public constructor( + httpClient: HttpClient, + ) { + super(httpClient, environment.apiEntryPoint + '/article/comments_tag'); + } + + public getAllByArticleId(aid: string): Observable { + return this.httpClient.get(`${this.apiEntryPoint}/list/${aid}`); + } + +} diff --git a/src/modules/article/service/article-like.service.ts b/src/modules/article/service/article-like.service.ts new file mode 100755 index 0000000..b1efa57 --- /dev/null +++ b/src/modules/article/service/article-like.service.ts @@ -0,0 +1,21 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; + +import { Observable } from 'rxjs'; +import { AbstractRestService } from '../../common/util/service/abstract-rest.service'; +import { environment } from '../../../environments/environment.hmr'; +import { httpOptions } from '../../common/util/service/abstract.service'; +import { ArticleTag } from '../../../shared/article/model/article-tag.model'; +import { TagService } from '../../tag/type/tag-service.type'; +import { ArticleLike } from '../../../shared/article/model/article-like.model'; + +@Injectable() +export class ArticleLikeService extends AbstractRestService { + + public constructor( + httpClient: HttpClient, + ) { + super(httpClient, environment.apiEntryPoint + '/article/like'); + } + +} diff --git a/src/modules/article/service/article-tag.service.ts b/src/modules/article/service/article-tag.service.ts new file mode 100755 index 0000000..5595a33 --- /dev/null +++ b/src/modules/article/service/article-tag.service.ts @@ -0,0 +1,38 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; + +import { Observable } from 'rxjs'; +import { AbstractRestService } from '../../common/util/service/abstract-rest.service'; +import { environment } from '../../../environments/environment.hmr'; +import { httpOptions } from '../../common/util/service/abstract.service'; +import { ArticleTag } from '../../../shared/article/model/article-tag.model'; +import { TagService } from '../../tag/type/tag-service.type'; + +@Injectable() +export class ArticleTagService extends AbstractRestService implements TagService { + + public constructor( + httpClient: HttpClient, + ) { + super(httpClient, environment.apiEntryPoint + '/article/tag'); + } + + public getAllByKeyword(keyword: string): Observable { + return this.httpClient.get(`${this.apiEntryPoint}/search/${keyword}`); + } + + public getAllByAid(aid: string): Observable { + const _httpOptions = { + ...httpOptions, + params: { + aid: aid, + }, + }; + return this.httpClient.get(this.apiEntryPoint, _httpOptions); + } + + public getAllPopular(): Observable { + return this.httpClient.get(`${this.apiEntryPoint}/popular`); + + } +} diff --git a/src/modules/article/service/article.get.service.ts b/src/modules/article/service/article.get.service.ts new file mode 100755 index 0000000..65d6cf5 --- /dev/null +++ b/src/modules/article/service/article.get.service.ts @@ -0,0 +1,31 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; + +import { Observable } from 'rxjs'; + +import { AbstractRestService } from '../../common/util/service/abstract-rest.service'; +import { environment } from '../../../environments/environment'; +import { ArticleMongodb } from '../../../shared/article/model/article.mongodb.model'; +import { ArticleBookmarks } from 'src/shared/article/model/article-bookmarks.model'; + +@Injectable() +export class ArticleGetService extends AbstractRestService { + + public constructor( + httpClient: HttpClient, + ) { + super(httpClient, environment.apiEntryPoint + '/article'); + } + + public getAllByUid(uid: string, timestamp: number, size: number): Observable { + return this.httpClient.get(`${this.apiEntryPoint}/user/${uid}/${timestamp}/${size}`); + } + + public getAllByTagName(tagName: string, timestamp: number, size: number): Observable { + console.log(`${this.apiEntryPoint}/tag/${tagName}/${timestamp}/${size}`); + return this.httpClient.get(`${this.apiEntryPoint}/tag/${tagName}/${timestamp}/${size}`); + } + + + +} diff --git a/src/modules/article/service/article.service.ts b/src/modules/article/service/article.service.ts new file mode 100755 index 0000000..702b6c1 --- /dev/null +++ b/src/modules/article/service/article.service.ts @@ -0,0 +1,19 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; + +import { Observable } from 'rxjs'; + +import { + AbstractRestService, +} from '../../common/util/service/abstract-rest.service'; +import { httpOptions } from '../../common/util/service/abstract.service'; +import { Article } from '../../../shared/article/model/article.model'; +import { environment } from '../../../environments/environment'; + +@Injectable() +export class ArticleService extends AbstractRestService { + public constructor(httpClient: HttpClient) { + super(httpClient, environment.apiEntryPoint + '/article'); + } + +} diff --git a/src/modules/article/service/index.ts b/src/modules/article/service/index.ts new file mode 100755 index 0000000..a80d96b --- /dev/null +++ b/src/modules/article/service/index.ts @@ -0,0 +1,17 @@ +import { ArticleBookmarksService } from './article-bookmarks.service'; +import { ArticleCommentsTagService } from './article-comments-tag.service'; +import { ArticleLikeService } from './article-like.service'; +import { ArticleTagService } from './article-tag.service'; +import { ArticleService } from './article.service'; +import { ArticleGetService } from './article.get.service'; +import { ArticleBestCommentsTagService } from './article-best-comments-tag.service'; + +export const SERVICES = [ + ArticleBookmarksService, + ArticleCommentsTagService, + ArticleLikeService, + ArticleTagService, + ArticleService, + ArticleGetService, + ArticleBestCommentsTagService +]; diff --git a/src/modules/article/store/index.ts b/src/modules/article/store/index.ts new file mode 100755 index 0000000..3a2085b --- /dev/null +++ b/src/modules/article/store/index.ts @@ -0,0 +1,18 @@ +import { + createSelector, + createFeatureSelector, +} from '@ngrx/store'; + +import * as RouterStore from './router'; + +// tslint:disable-next-line:no-empty-interface +export interface State { +} + +export const REDUCERS = { +}; + +export const EFFECTS = [ +]; + +export const selectState = createFeatureSelector('article'); diff --git a/src/modules/article/store/router/index.ts b/src/modules/article/store/router/index.ts new file mode 100755 index 0000000..286bfda --- /dev/null +++ b/src/modules/article/store/router/index.ts @@ -0,0 +1 @@ +export * from './router.action'; diff --git a/src/modules/article/store/router/router.action.ts b/src/modules/article/store/router/router.action.ts new file mode 100755 index 0000000..2c21468 --- /dev/null +++ b/src/modules/article/store/router/router.action.ts @@ -0,0 +1,15 @@ +import { Action } from '@ngrx/store'; + +export enum ActionType { + GotoSave = '[article.router] GotoSave', +} + +export class GotoSave implements Action { + readonly type = ActionType.GotoSave; + + constructor(public payload: { aid: string }) { } +} + +export type Actions = + | GotoSave + ; diff --git a/src/modules/article/util/article.util.ts b/src/modules/article/util/article.util.ts new file mode 100755 index 0000000..600aae2 --- /dev/null +++ b/src/modules/article/util/article.util.ts @@ -0,0 +1,58 @@ +import { Store, select } from '@ngrx/store'; + +import { of } from 'rxjs'; +import { take, map, catchError } from 'rxjs/operators'; + +import { User } from '../../../shared/user/model/user.model'; +import { AccountsUtil } from 'src/modules/accounts/util/accounts.util'; +import { ArticleBookmarks } from 'src/shared/article/model/article-bookmarks.model'; + + + + +export class ArticleUtil { + + private static bookmarksMap: Map = new Map(); + public static checkBookmarks(store: Store, user: User, aid: string): string { + + ArticleUtil.generateBookmarksMap(store, user); + + if (ArticleUtil.bookmarksMap.has(aid)) { + return ArticleUtil.bookmarksMap.get(aid).id; + } + + + return null; + } + + public static removeBookmarks(user: User, bid: string): void { + + let deleteIdx = -1; + user.articleBookmarksList.forEach((bookmarks: ArticleBookmarks, idx: number) => { + if (bookmarks.id === bid) { + deleteIdx = idx; + } + }); + + if (deleteIdx >= 0) { + user.articleBookmarksList.splice(deleteIdx, 1); + } + + } + + private static generateBookmarksMap(store: Store, user: User): void { + + if (ArticleUtil.bookmarksMap.size === user.articleBookmarksList.length) { + return; + } + + ArticleUtil.bookmarksMap.clear(); + + user.articleBookmarksList.forEach(bookmarks => { + ArticleUtil.bookmarksMap.set(bookmarks.articleId, bookmarks); + }); + + + } + +} diff --git a/src/modules/attachments/attachments.module.ts b/src/modules/attachments/attachments.module.ts new file mode 100755 index 0000000..4f83ff0 --- /dev/null +++ b/src/modules/attachments/attachments.module.ts @@ -0,0 +1,52 @@ +import { NgModule, ModuleWithProviders } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { TranslateModule } from '@ngx-translate/core'; + +import { SharedModule } from '../common/shared/shared.module'; + +import { COMPONENTS } from './component'; +import { DIALOGS } from './dialog'; +import { PIPES } from './pipe'; +import { SERVICES } from './service'; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + ReactiveFormsModule, + TranslateModule, + SharedModule, + ], + exports: [ + ...COMPONENTS, + ...PIPES, + ], + declarations: [ + ...COMPONENTS, + ...DIALOGS, + ...PIPES, + ], + entryComponents: [ + ...DIALOGS, + ], +}) +export class AttachmentsModule { + public static forRoot(): ModuleWithProviders { + return { + ngModule: AttachmentsRootModule, + providers: [ + ...SERVICES, + ], + }; + } +} + +@NgModule({ + imports: [ + ], + exports: [ + ], +}) +export class AttachmentsRootModule { +} diff --git a/src/modules/attachments/component/image-dataurl/image-dataurl.component.html b/src/modules/attachments/component/image-dataurl/image-dataurl.component.html new file mode 100755 index 0000000..8b4c38a --- /dev/null +++ b/src/modules/attachments/component/image-dataurl/image-dataurl.component.html @@ -0,0 +1,2 @@ + + diff --git a/src/modules/attachments/component/image-dataurl/image-dataurl.component.scss b/src/modules/attachments/component/image-dataurl/image-dataurl.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/modules/attachments/component/image-dataurl/image-dataurl.component.spec.ts b/src/modules/attachments/component/image-dataurl/image-dataurl.component.spec.ts new file mode 100755 index 0000000..b283919 --- /dev/null +++ b/src/modules/attachments/component/image-dataurl/image-dataurl.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ImageDataURLComponent } from './image-dataurl.component'; + +describe('ImageDataURLComponent', () => { + let component: ImageDataURLComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ImageDataURLComponent] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ImageDataURLComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/modules/attachments/component/image-dataurl/image-dataurl.component.ts b/src/modules/attachments/component/image-dataurl/image-dataurl.component.ts new file mode 100755 index 0000000..22a2d6f --- /dev/null +++ b/src/modules/attachments/component/image-dataurl/image-dataurl.component.ts @@ -0,0 +1,42 @@ +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; + +import { Attachments } from '../../../../shared/attachments/model/attachments.model'; +import { AttachmentsService } from '../../service/attachments.service'; +import { finalize } from 'rxjs/operators'; +import { FileItem } from '../../model/file-item.model'; + +@Component({ + selector: 'app-attachments-image-dataurl', + templateUrl: './image-dataurl.component.html', + styleUrls: ['./image-dataurl.component.scss'] +}) +export class ImageDataURLComponent implements OnInit { + @Input() + attachments: Attachments; + + @Output() + loaded = new EventEmitter(); + + fileItem: FileItem; + processing: boolean; + + constructor( + private attachmentsService: AttachmentsService, + ) { + } + + ngOnInit() { + if (this.attachments) { + this.processing = true; + const attachmentsDownload = this.attachmentsService.downloadImage(this.attachments); + attachmentsDownload.downloadingProgress.pipe( + finalize(() => { + this.fileItem = attachmentsDownload.fileItem; + this.fileItem.data = this.attachments; + this.processing = false; + this.loaded.emit(this.fileItem); + }) + ).subscribe(); + } + } +} diff --git a/src/modules/attachments/component/image/image.component.html b/src/modules/attachments/component/image/image.component.html new file mode 100755 index 0000000..5712d2d --- /dev/null +++ b/src/modules/attachments/component/image/image.component.html @@ -0,0 +1,2 @@ + + diff --git a/src/modules/attachments/component/image/image.component.scss b/src/modules/attachments/component/image/image.component.scss new file mode 100755 index 0000000..4b71044 --- /dev/null +++ b/src/modules/attachments/component/image/image.component.scss @@ -0,0 +1,5 @@ +img { + width: 100%; + height: auto; + display: block; +} diff --git a/src/modules/attachments/component/image/image.component.spec.ts b/src/modules/attachments/component/image/image.component.spec.ts new file mode 100755 index 0000000..7b80668 --- /dev/null +++ b/src/modules/attachments/component/image/image.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ImageComponent } from './image.component'; + +describe('ImageComponent', () => { + let component: ImageComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ImageComponent] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ImageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/modules/attachments/component/image/image.component.ts b/src/modules/attachments/component/image/image.component.ts new file mode 100755 index 0000000..1ed4c2e --- /dev/null +++ b/src/modules/attachments/component/image/image.component.ts @@ -0,0 +1,52 @@ +import { Component, OnInit, Input, Output, EventEmitter, ViewChild } from '@angular/core'; + +import { Attachments } from '../../../../shared/attachments/model/attachments.model'; +import { AttachmentsService } from '../../service/attachments.service'; +import { finalize } from 'rxjs/operators'; +import { FileItem } from '../../model/file-item.model'; + +@Component({ + selector: 'app-attachments-image', + templateUrl: './image.component.html', + styleUrls: ['./image.component.scss'] +}) +export class ImageComponent implements OnInit { + @Input() + attachments: Attachments; + + @Input() + configKeyList: string[]; + + @Output() + loaded = new EventEmitter(); + + processing: boolean; + + constructor( + private attachmentsService: AttachmentsService, + ) { + } + + ngOnInit() { + // if (this.attachments) { + // this.processing = true; + // const attachmentsDownload = this.attachmentsService.downloadImage(this.attachments); + // attachmentsDownload.downloadingProgress.pipe( + // finalize(() => { + // this.fileItem = attachmentsDownload.fileItem; + // this.fileItem.data = this.attachments; + // this.processing = false; + // this.loaded.emit(this.fileItem); + // }) + // ).subscribe(); + // } + if (this.attachments) { + this.processing = true; + } + } + + onLoadImage() { + this.processing = false; + this.loaded.emit(); + } +} diff --git a/src/modules/attachments/component/index.ts b/src/modules/attachments/component/index.ts new file mode 100755 index 0000000..6d6e37a --- /dev/null +++ b/src/modules/attachments/component/index.ts @@ -0,0 +1,7 @@ +import { ImageComponent } from './image/image.component'; +import { ImageDataURLComponent } from './image-dataurl/image-dataurl.component'; + +export const COMPONENTS = [ + ImageComponent, + ImageDataURLComponent, +]; diff --git a/src/modules/attachments/dialog/box/box.dialog.component.html b/src/modules/attachments/dialog/box/box.dialog.component.html new file mode 100755 index 0000000..b615c83 --- /dev/null +++ b/src/modules/attachments/dialog/box/box.dialog.component.html @@ -0,0 +1,26 @@ + +
+

Upload Files

+
+ +
+ + + + + +

{{fileItem.name}}

+ +
+
+
+ + + + + + + +
diff --git a/src/modules/attachments/dialog/box/box.dialog.component.scss b/src/modules/attachments/dialog/box/box.dialog.component.scss new file mode 100755 index 0000000..ac45c5c --- /dev/null +++ b/src/modules/attachments/dialog/box/box.dialog.component.scss @@ -0,0 +1,18 @@ +.add-files-btn { + float: right; +} + +:host { + height: 100%; + display: flex; + flex: 1; + flex-direction: column; +} + +.actions { + justify-content: flex-end; +} + +.container { + height: 100%; +} diff --git a/src/modules/attachments/dialog/box/box.dialog.component.ts b/src/modules/attachments/dialog/box/box.dialog.component.ts new file mode 100755 index 0000000..d89d116 --- /dev/null +++ b/src/modules/attachments/dialog/box/box.dialog.component.ts @@ -0,0 +1,70 @@ +import { Component, OnInit, ViewChild, ElementRef } from '@angular/core'; +import { MatDialogRef } from '@angular/material'; + +import { forkJoin, Observable, of } from 'rxjs'; +import { AttachmentsService } from '../../service/attachments.service'; +import { FileItem } from '../../model/file-item.model'; +import { take, tap, map, catchError, finalize } from 'rxjs/operators'; + +@Component({ + selector: 'app-attachments-box-dialog', + templateUrl: './box.dialog.component.html', + styleUrls: ['./box.dialog.component.scss'] +}) +export class BoxDialogComponent implements OnInit { + @ViewChild('fileInput') + fileInput: ElementRef; + + uploading = false; + cancelable = false; + fileItemList: FileItem[] = []; + + constructor( + private dialogRef: MatDialogRef, + private attachmentsService: AttachmentsService, ) { + + } + + ngOnInit() { } + + async onFileChange(event) { + if (event.target.files && event.target.files.length > 0) { + for (let index = 0; index < event.target.files.length; index++) { + const file: File = event.target.files[index]; + if (!isNaN(parseInt(file.name, 10))) { + const fileItem = await FileItem.fromFile(file); + this.fileItemList.push(fileItem); + } + } + } + } + + onClickAddFileButton() { + this.fileInput.nativeElement.click(); + } + + onClickUpload() { + if (0 === this.fileItemList.length) { + return; + } + + this.attachmentsService.upload(this.fileItemList).pipe( + take(1), + tap(() => { + this.uploading = true; + }), + map((user) => { + }), + catchError((err) => { + return of(err); + }), + finalize(() => { + this.uploading = false; + }) + ).subscribe(); + } + + onClickClose() { + this.dialogRef.close(); + } +} diff --git a/src/modules/attachments/dialog/index.ts b/src/modules/attachments/dialog/index.ts new file mode 100755 index 0000000..4f83ae3 --- /dev/null +++ b/src/modules/attachments/dialog/index.ts @@ -0,0 +1,5 @@ +import { BoxDialogComponent } from './box/box.dialog.component'; + +export const DIALOGS = [ + BoxDialogComponent, +]; diff --git a/src/modules/attachments/model/file-item.model.ts b/src/modules/attachments/model/file-item.model.ts new file mode 100755 index 0000000..b82a2b4 --- /dev/null +++ b/src/modules/attachments/model/file-item.model.ts @@ -0,0 +1,99 @@ +import { Subject, Observable } from 'rxjs'; + +import { FileItemStatus } from '../type/file-item-status.type'; +import { Attachments } from '../../../shared/attachments/model/attachments.model'; +import { BlobUtil } from '../../common/util/data/blob.util'; + +export class FileItem { + public name: string; + public blob: Blob; + public status: FileItemStatus; + public children: FileItem[]; + public attachments: Attachments; + public data: any; + public uploadTime: number; + public dataURL: string | ArrayBuffer; + + private _uploadingProgress: Subject; + private _uploadStartTime: number; + + public static async fromFile(file: File): Promise { + return new Promise(async (resolve, reject) => { + const fileItem = new FileItem(file.name, file); + await fileItem.setDataURL(); + return resolve(fileItem); + }); + } + + public static async fromBlob(name: string, blob: Blob): Promise { + return new Promise(async (resolve, reject) => { + const fileItem = new FileItem(name, blob); + await fileItem.setDataURL(); + return resolve(fileItem); + }); + } + + public static async fromDataURL(name: string, dataURL: string): Promise { + return new Promise((resolve, reject) => { + BlobUtil.fromDataURL(dataURL) + .then(async (blob) => { + const fileItem = new FileItem(name, blob); + await fileItem.setDataURL(); + return resolve(fileItem); + }) + .catch((reason) => { + return reject(reason); + }); + }); + } + + private constructor(name: string, blob: Blob) { + this.name = name; + this.blob = blob; + } + + public get uploadingProgress(): Observable { + if (!this._uploadingProgress || this._uploadingProgress.closed) { + return null; + } + return this._uploadingProgress.asObservable(); + } + + public uploadStart(): Subject { + this._uploadStartTime = new Date().getTime(); + this.status = FileItemStatus.UPLOADING; + this._uploadingProgress = new Subject(); + return this._uploadingProgress; + } + + public uploadComplete(attachments: Attachments) { + const endTime = new Date().getTime(); + this.attachments = attachments; + this.uploadTime = endTime - this._uploadStartTime; + this.status = FileItemStatus.UPLOADED; + this._uploadingProgress.complete(); + } + + public addChildren(...fileItemList: FileItem[]) { + if (!fileItemList || 0 === fileItemList.length) { + return; + } + if (!this.children) { + this.children = []; + } + this.children.push(...fileItemList); + } + + private setDataURL(): Promise { + return new Promise((resolve, reject) => { + BlobUtil.toDataURL(this.blob) + .then((dataURL) => { + this.dataURL = dataURL; + return resolve(); + }) + .catch((reason) => { + return reject(reason); + }); + }); + } +} diff --git a/src/modules/attachments/pipe/index.ts b/src/modules/attachments/pipe/index.ts new file mode 100755 index 0000000..25b5d0b --- /dev/null +++ b/src/modules/attachments/pipe/index.ts @@ -0,0 +1,5 @@ +import { URLPipe } from './url.pip'; + +export const PIPES = [ + URLPipe, +]; diff --git a/src/modules/attachments/pipe/url.pip.ts b/src/modules/attachments/pipe/url.pip.ts new file mode 100755 index 0000000..ef6b321 --- /dev/null +++ b/src/modules/attachments/pipe/url.pip.ts @@ -0,0 +1,27 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { Attachments } from '../../../shared/attachments/model/attachments.model'; + +import { environment } from '../../../environments/environment'; +import * as imagesConfig from '../../../config/images'; + + +@Pipe({ + name: 'attachmentsURL', +}) +export class URLPipe implements PipeTransform { + + transform(attachments: Attachments, configKeyList?: string[]): string { + if (!attachments || !attachments.id) { + return ''; + } + + let url = `${environment.attachmentsEntryPoint}/i/${attachments.id}`; + + if (configKeyList && 3 === configKeyList.length) { + const thumbnailsConfig = imagesConfig.imageThumbnailsConfig(configKeyList[0], configKeyList[1], configKeyList[2]); + url += `/${configKeyList[2]}/${thumbnailsConfig.width}/${thumbnailsConfig.height}`; + } + + return `${url}`; + } +} diff --git a/src/modules/attachments/service/attachments.service.ts b/src/modules/attachments/service/attachments.service.ts new file mode 100755 index 0000000..3d83402 --- /dev/null +++ b/src/modules/attachments/service/attachments.service.ts @@ -0,0 +1,98 @@ +import { Injectable } from '@angular/core'; +import { + HttpClient, + HttpRequest, + HttpEventType, + HttpResponse +} from '@angular/common/http'; + +import { Observable, forkJoin, Subject } from 'rxjs'; +import { environment } from '../../../environments/environment'; +import { Attachments } from '../../../shared/attachments/model/attachments.model'; +import { FileItem } from '../model/file-item.model'; +import { httpOptions } from '../../common/util/service/abstract.service'; + +export interface AttachmentsDownload { + downloadingProgress?: Observable; + fileItem?: FileItem; +} + +@Injectable() +export class AttachmentsService { + private readonly apiEntryPoint: string; + + public constructor(private httpClient: HttpClient) { + this.apiEntryPoint = environment.apiEntryPoint + '/attachments'; + } + + public upload(fileItemList: FileItem[]): Observable { + const allProgressObservables = []; + + for (let index = 0; index < fileItemList.length; index++) { + const fileItem = fileItemList[index]; + const progress = fileItem.uploadStart(); + + const formData: FormData = new FormData(); + formData.append('file', fileItem.blob, fileItem.name); + + const req = new HttpRequest('POST', this.apiEntryPoint, formData, { + reportProgress: true + }); + + allProgressObservables.push(progress); + + this.httpClient.request(req).subscribe((event) => { + if (event.type === HttpEventType.UploadProgress) { + const percentDone = Math.round((100 * event.loaded) / event.total); + progress.next(percentDone); + } else if (event instanceof HttpResponse) { + fileItem.uploadComplete(event.body); + } + }); + } + + return forkJoin(allProgressObservables); + } + + public downloadImage(attachments: Attachments, ...options: string[]): AttachmentsDownload { + let url = `${this.apiEntryPoint}/i/${attachments.id}`; + if (options && 0 < options.length) { + options.forEach((option) => { + url += `${url}/${option}`; + }); + } + return this.downloadBlob(url, attachments.name); + } + + public downloadBlob(url: string, name?: string): AttachmentsDownload { + const attachmentsDownload: AttachmentsDownload = {}; + const progress = new Subject(); + attachmentsDownload.downloadingProgress = progress.asObservable(); + + const req = new HttpRequest('GET', `${url}`, { + reportProgress: true, + responseType: 'blob', + }); + + this.httpClient.request(req).subscribe(async (event) => { + if (event.type === HttpEventType.DownloadProgress) { + const percentDone = Math.round((100 * event.loaded) / event.total); + progress.next(percentDone); + } else if (event instanceof HttpResponse) { + attachmentsDownload.fileItem = await FileItem.fromBlob(name ? name : '', event.body as Blob); + progress.complete(); + } + }); + + return attachmentsDownload; + } + + public deleteAttachFile(...idList: string[]): Observable { + let ids = ''; + if (idList && 0 < idList.length) { + ids = idList.join(','); + } + return this.httpClient.delete(`${this.apiEntryPoint}/?ids=${ids}`); + } + +} diff --git a/src/modules/attachments/service/index.ts b/src/modules/attachments/service/index.ts new file mode 100755 index 0000000..2a26dc8 --- /dev/null +++ b/src/modules/attachments/service/index.ts @@ -0,0 +1,5 @@ +import { AttachmentsService } from './attachments.service'; + +export const SERVICES = [ + AttachmentsService, +]; diff --git a/src/modules/attachments/type/file-item-status.type.ts b/src/modules/attachments/type/file-item-status.type.ts new file mode 100755 index 0000000..7c2e7a1 --- /dev/null +++ b/src/modules/attachments/type/file-item-status.type.ts @@ -0,0 +1,15 @@ +/** + * 파 일 명: file-item-status.type.ts + * 작성일자: 2019-01-09 + * 작 성 자: 박병준 + * 설 명: File Item Status 유형을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +export enum FileItemStatus { + NORMAL = 'NORMAL', + UPLOADING = 'UPLOADING', + UPLOADED = 'UPLOADED', +} diff --git a/src/modules/cartoons/cartoons-store.module.ts b/src/modules/cartoons/cartoons-store.module.ts new file mode 100755 index 0000000..e515a7f --- /dev/null +++ b/src/modules/cartoons/cartoons-store.module.ts @@ -0,0 +1,17 @@ +import { NgModule, ModuleWithProviders } from '@angular/core'; +import { StoreModule } from '@ngrx/store'; +import { EffectsModule } from '@ngrx/effects'; + +import { + REDUCERS, + EFFECTS, +} from './store'; + +@NgModule({ + imports: [ + StoreModule.forFeature('cartoons', REDUCERS), + EffectsModule.forFeature(EFFECTS), + ], +}) +export class CartoonsStoreModule { +} diff --git a/src/modules/cartoons/cartoons.module.ts b/src/modules/cartoons/cartoons.module.ts new file mode 100755 index 0000000..52a84c0 --- /dev/null +++ b/src/modules/cartoons/cartoons.module.ts @@ -0,0 +1,56 @@ +import { NgModule, ModuleWithProviders } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { TranslateModule } from '@ngx-translate/core'; + +import { SharedModule } from '../common/shared/shared.module'; + +import { ArticleModule } from '../article/article.module'; +import { AttachmentsModule } from '../attachments/attachments.module'; +import { TagModule } from '../tag/tag.module'; +import { UserModule } from '../user/user.module'; + +import { COMPONENTS } from './component'; +import { DIALOGS } from './dialog'; +import { SERVICES } from './service'; +import { CartoonsStoreModule } from './cartoons-store.module'; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + ReactiveFormsModule, + TranslateModule, + SharedModule, + ArticleModule, + AttachmentsModule, + TagModule, + UserModule + ], + exports: [ + ...COMPONENTS, + ], + declarations: [ + ...COMPONENTS, + ...DIALOGS, + ], + entryComponents: [ + ...DIALOGS, + ], +}) +export class CartoonsModule { + public static forRoot(): ModuleWithProviders { + return { + ngModule: CartoonsRootModule, + providers: [...SERVICES] + }; + } +} + +@NgModule({ + imports: [ + CartoonsStoreModule, + ], + exports: [] +}) +export class CartoonsRootModule { } diff --git a/src/modules/cartoons/component/details/details.component.html b/src/modules/cartoons/component/details/details.component.html new file mode 100755 index 0000000..8cea8aa --- /dev/null +++ b/src/modules/cartoons/component/details/details.component.html @@ -0,0 +1,13 @@ + + +
+ +
+
+
diff --git a/src/modules/cartoons/component/details/details.component.scss b/src/modules/cartoons/component/details/details.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/modules/cartoons/component/details/details.component.spec.ts b/src/modules/cartoons/component/details/details.component.spec.ts new file mode 100755 index 0000000..3521878 --- /dev/null +++ b/src/modules/cartoons/component/details/details.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DetailsComponent } from './details.component'; + +describe('DetailsComponent', () => { + let component: DetailsComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [DetailsComponent] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(DetailsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/modules/cartoons/component/details/details.component.ts b/src/modules/cartoons/component/details/details.component.ts new file mode 100755 index 0000000..1fe5057 --- /dev/null +++ b/src/modules/cartoons/component/details/details.component.ts @@ -0,0 +1,61 @@ +import { Component, OnInit, Input } from '@angular/core'; + +import { Cartoons } from '../../../../shared/cartoons/model/cartoons.model'; +import { CartoonsService } from '../../service/cartoons.service'; +import { + take, + tap, + map, + catchError, + finalize, + debounceTime +} from 'rxjs/operators'; +import { of, Subject } from 'rxjs'; + +@Component({ + selector: 'app-cartoons-details', + templateUrl: './details.component.html', + styleUrls: ['./details.component.scss'] +}) +export class DetailsComponent implements OnInit { + // tslint:disable-next-line:no-input-rename + @Input('article') + cartoons: Cartoons; + + modifySubject: Subject; + + constructor(private cartoonsService: CartoonsService) { } + + ngOnInit() { + if (!this.cartoons) { + this.cartoons = {}; + } + + console.log('cartoons', this.cartoons.createDate.getTime()); + + this.modifySubject = new Subject(); + this.modifySubject + .pipe( + debounceTime(1000), + map(async () => { + this.updateCartoons(); + }) + ) + .subscribe(); + } + + updateCartoons() { + this.cartoonsService + .update(this.cartoons) + .pipe( + take(1), + tap(() => { }), + map(cartoons => { }), + catchError(error => { + return of(error); + }), + finalize(() => { }) + ) + .subscribe(); + } +} diff --git a/src/modules/cartoons/component/display/card/card.component.html b/src/modules/cartoons/component/display/card/card.component.html new file mode 100755 index 0000000..09070ee --- /dev/null +++ b/src/modules/cartoons/component/display/card/card.component.html @@ -0,0 +1,7 @@ + + +
+ +
+
+
diff --git a/src/modules/cartoons/component/display/card/card.component.scss b/src/modules/cartoons/component/display/card/card.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/modules/cartoons/component/display/card/card.component.spec.ts b/src/modules/cartoons/component/display/card/card.component.spec.ts new file mode 100755 index 0000000..74236a1 --- /dev/null +++ b/src/modules/cartoons/component/display/card/card.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CardComponent } from './card.component'; + +describe('CardComponent', () => { + let component: CardComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [CardComponent] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CardComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/modules/cartoons/component/display/card/card.component.ts b/src/modules/cartoons/component/display/card/card.component.ts new file mode 100755 index 0000000..350dba0 --- /dev/null +++ b/src/modules/cartoons/component/display/card/card.component.ts @@ -0,0 +1,62 @@ +import { Component, OnInit, Input, OnDestroy } from '@angular/core'; + +import { of, Subject } from 'rxjs'; +import { + take, + tap, + map, + catchError, + finalize, + debounceTime +} from 'rxjs/operators'; + +import { Cartoons } from '../../../../../shared/cartoons/model/cartoons.model'; +import { CartoonsService } from '../../../service/cartoons.service'; + +@Component({ + selector: 'app-cartoons-display-card', + templateUrl: './card.component.html', + styleUrls: ['./card.component.scss'] +}) +export class CardComponent implements OnInit, OnDestroy { + // tslint:disable-next-line:no-input-rename + @Input('article') + cartoons: Cartoons; + + @Input() + authorFollowYn: boolean; + + modifySubject: Subject; + + constructor(private cartoonsService: CartoonsService) { } + + ngOnInit() { + if (!this.cartoons) { + this.cartoons = {}; + } + + this.modifySubject = new Subject(); + this.modifySubject.pipe( + debounceTime(1000), + map(async () => { + this.updateCartoons(); + }) + ).subscribe(); + } + + ngOnDestroy(): void { + this.modifySubject.complete(); + } + + updateCartoons() { + this.cartoonsService.update(this.cartoons).pipe( + take(1), + tap(() => { }), + map(cartoons => { }), + catchError(error => { + return of(error); + }), + finalize(() => { }) + ).subscribe(); + } +} diff --git a/src/modules/cartoons/component/display/cover/cover.component.html b/src/modules/cartoons/component/display/cover/cover.component.html new file mode 100755 index 0000000..2b19003 --- /dev/null +++ b/src/modules/cartoons/component/display/cover/cover.component.html @@ -0,0 +1,4 @@ + + + + diff --git a/src/modules/cartoons/component/display/cover/cover.component.scss b/src/modules/cartoons/component/display/cover/cover.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/modules/cartoons/component/display/cover/cover.component.spec.ts b/src/modules/cartoons/component/display/cover/cover.component.spec.ts new file mode 100755 index 0000000..dc90ccd --- /dev/null +++ b/src/modules/cartoons/component/display/cover/cover.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CoverComponent } from './cover.component'; + +describe('CoverComponent', () => { + let component: CoverComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [CoverComponent] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CoverComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/modules/cartoons/component/display/cover/cover.component.ts b/src/modules/cartoons/component/display/cover/cover.component.ts new file mode 100755 index 0000000..5a07f66 --- /dev/null +++ b/src/modules/cartoons/component/display/cover/cover.component.ts @@ -0,0 +1,79 @@ +import { Component, OnInit, Input } from '@angular/core'; +import { MatDialog } from '@angular/material'; + +import { of } from 'rxjs'; +import { take, tap, map, catchError, finalize } from 'rxjs/operators'; + +import { Cartoons } from '../../../../../shared/cartoons/model/cartoons.model'; +import { CartoonsService } from '../../../service/cartoons.service'; +import { CartoonsAttachments } from '../../../../../shared/cartoons/model/cartoons-attachments.model'; +import { ArticleImage } from '../../../../../shared/article/type/article-image.type'; +import { UIUtil } from '../../../../../modules/common/util/ui/dialog.util'; +import { + ViewerDialogComponent, + ViewerDialogData, + ViewerDialogResult, +} from '../../../../../modules/cartoons/dialog/viewer/viewer.dialog.component'; +import { ArticleDisplay } from '../../../../../shared/article/type/article-display.type'; + + + +@Component({ + selector: 'app-cartoons-display-cover', + templateUrl: './cover.component.html', + styleUrls: ['./cover.component.scss'] +}) +export class CoverComponent implements OnInit { + @Input() + cartoons: Cartoons; + + @Input() + display: ArticleDisplay; + + cartoonsAttachments: CartoonsAttachments; + imageConfigKeyList: string[]; + + constructor( + private cartoonsService: CartoonsService, + private matDialog: MatDialog, + ) { } + + ngOnInit() { + this.imageConfigKeyList = [ + 'article', + ArticleImage.Cover, + this.display, + ]; + + if (this.cartoons && this.cartoons.attachmentsList && 0 < this.cartoons.attachmentsList.length) { + for (let index = 0; index < this.cartoons.attachmentsList.length; index++) { + const cartoonsAttachments = this.cartoons.attachmentsList[index]; + if (ArticleImage.Cover === cartoonsAttachments.imageType) { + this.cartoonsAttachments = cartoonsAttachments; + break; + } + } + if (!this.cartoonsAttachments) { + this.cartoonsAttachments = this.cartoons.attachmentsList[0]; + } + } + } + + onClick() { + UIUtil.dialogOpen( + this.matDialog, ViewerDialogComponent, + { + panelClass: 'app-full-screen-dialog', + data: { + cartoons: this.cartoons, + } + }) + .then((result) => { + if (result.cartoons) { + } + }) + .catch((reason) => { + }); + } + +} diff --git a/src/modules/cartoons/component/display/grid/grid.component.html b/src/modules/cartoons/component/display/grid/grid.component.html new file mode 100755 index 0000000..734490f --- /dev/null +++ b/src/modules/cartoons/component/display/grid/grid.component.html @@ -0,0 +1,13 @@ + + +
+ + +
+ +
+
+
+
+
+
diff --git a/src/modules/cartoons/component/display/grid/grid.component.scss b/src/modules/cartoons/component/display/grid/grid.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/modules/cartoons/component/display/grid/grid.component.spec.ts b/src/modules/cartoons/component/display/grid/grid.component.spec.ts new file mode 100755 index 0000000..504366c --- /dev/null +++ b/src/modules/cartoons/component/display/grid/grid.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { GridComponent } from './grid.component'; + +describe('GridComponent', () => { + let component: GridComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [GridComponent] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(GridComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/modules/cartoons/component/display/grid/grid.component.ts b/src/modules/cartoons/component/display/grid/grid.component.ts new file mode 100755 index 0000000..7af2c66 --- /dev/null +++ b/src/modules/cartoons/component/display/grid/grid.component.ts @@ -0,0 +1,41 @@ +import { Component, OnInit, Input, OnDestroy } from '@angular/core'; + +import { of, Subject } from 'rxjs'; +import { + take, + tap, + map, + catchError, + finalize, + debounceTime +} from 'rxjs/operators'; + +import { Cartoons } from '../../../../../shared/cartoons/model/cartoons.model'; +import { CartoonsService } from '../../../service/cartoons.service'; + +@Component({ + selector: 'app-cartoons-display-grid', + templateUrl: './grid.component.html', + styleUrls: ['./grid.component.scss'] +}) +export class GridComponent implements OnInit, OnDestroy { + // tslint:disable-next-line:no-input-rename + @Input() + cartoonsList: Cartoons[]; + + @Input() + columnSize = 3; + + @Input() + showTitle = true; + + constructor(private cartoonsService: CartoonsService) { } + + ngOnInit() { + } + + ngOnDestroy(): void { + + } + +} diff --git a/src/modules/cartoons/component/display/index.ts b/src/modules/cartoons/component/display/index.ts new file mode 100755 index 0000000..0f1cd30 --- /dev/null +++ b/src/modules/cartoons/component/display/index.ts @@ -0,0 +1,11 @@ +import { CardComponent } from './card/card.component'; +import { CoverComponent } from './cover/cover.component'; +import { GridComponent } from './grid/grid.component'; +import { TileComponent } from './tile/tile.component'; + +export const DISPLAY_COMPONENTS = [ + CardComponent, + CoverComponent, + GridComponent, + TileComponent, +]; diff --git a/src/modules/cartoons/component/display/tile/tile.component.html b/src/modules/cartoons/component/display/tile/tile.component.html new file mode 100755 index 0000000..f58fb82 --- /dev/null +++ b/src/modules/cartoons/component/display/tile/tile.component.html @@ -0,0 +1,7 @@ + + +
+ +
+
+
diff --git a/src/modules/cartoons/component/display/tile/tile.component.scss b/src/modules/cartoons/component/display/tile/tile.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/modules/cartoons/component/display/tile/tile.component.spec.ts b/src/modules/cartoons/component/display/tile/tile.component.spec.ts new file mode 100755 index 0000000..5df480a --- /dev/null +++ b/src/modules/cartoons/component/display/tile/tile.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TileComponent } from './tile.component'; + +describe('TileComponent', () => { + let component: TileComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [TileComponent] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(TileComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/modules/cartoons/component/display/tile/tile.component.ts b/src/modules/cartoons/component/display/tile/tile.component.ts new file mode 100755 index 0000000..4772f97 --- /dev/null +++ b/src/modules/cartoons/component/display/tile/tile.component.ts @@ -0,0 +1,25 @@ +import { Component, OnInit, Input } from '@angular/core'; + +import { Cartoons } from '../../../../../shared/cartoons/model/cartoons.model'; +import { CartoonsService } from '../../../service/cartoons.service'; +import { take, tap, map, catchError, finalize } from 'rxjs/operators'; +import { of } from 'rxjs'; + +@Component({ + selector: 'app-cartoons-display-tile', + templateUrl: './tile.component.html', + styleUrls: ['./tile.component.scss'] +}) +export class TileComponent implements OnInit { + // tslint:disable-next-line:no-input-rename + @Input('article') + cartoons: Cartoons; + + constructor(private cartoonsService: CartoonsService) { } + + ngOnInit() { + if (!this.cartoons) { + this.cartoons = {}; + } + } +} diff --git a/src/modules/cartoons/component/index.ts b/src/modules/cartoons/component/index.ts new file mode 100755 index 0000000..e4f11ce --- /dev/null +++ b/src/modules/cartoons/component/index.ts @@ -0,0 +1,11 @@ +import { DetailsComponent } from './details/details.component'; +import { SERIES_COMPONENTS } from './series'; +import { WriteComponent } from './write/write.component'; +import { DISPLAY_COMPONENTS } from './display'; + +export const COMPONENTS = [ + DetailsComponent, + ...SERIES_COMPONENTS, + WriteComponent, + ...DISPLAY_COMPONENTS, +]; diff --git a/src/modules/cartoons/component/series/index.ts b/src/modules/cartoons/component/series/index.ts new file mode 100755 index 0000000..1865b04 --- /dev/null +++ b/src/modules/cartoons/component/series/index.ts @@ -0,0 +1,5 @@ +import { WriteComponent } from './write/write.component'; + +export const SERIES_COMPONENTS = [ + WriteComponent, +]; diff --git a/src/modules/cartoons/component/series/write/write.component.html b/src/modules/cartoons/component/series/write/write.component.html new file mode 100755 index 0000000..41f9249 --- /dev/null +++ b/src/modules/cartoons/component/series/write/write.component.html @@ -0,0 +1,18 @@ +
+
+

{{ 'myPage.series' | translate }}

+ + + + None + + {{_cartoonsSeries.title}} + + + + + +
+
diff --git a/src/modules/cartoons/component/series/write/write.component.scss b/src/modules/cartoons/component/series/write/write.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/modules/cartoons/component/series/write/write.component.spec.ts b/src/modules/cartoons/component/series/write/write.component.spec.ts new file mode 100755 index 0000000..a9636f8 --- /dev/null +++ b/src/modules/cartoons/component/series/write/write.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { WriteComponent } from './write.component'; + +describe('WriteComponent', () => { + let component: WriteComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [WriteComponent] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(WriteComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/modules/cartoons/component/series/write/write.component.ts b/src/modules/cartoons/component/series/write/write.component.ts new file mode 100755 index 0000000..9280528 --- /dev/null +++ b/src/modules/cartoons/component/series/write/write.component.ts @@ -0,0 +1,96 @@ +import { Component, OnInit, Input, Output, EventEmitter, ChangeDetectorRef } from '@angular/core'; +import { MatSelectChange, MatDialog } from '@angular/material'; +import { of } from 'rxjs'; +import { take, tap, map, catchError, finalize } from 'rxjs/operators'; +import { Store } from '@ngrx/store'; + +import { User } from '../../../../../shared/user/model/user.model'; + +import { CartoonsSeriesService } from '../../../service/cartoons-series.service'; +import { CartoonsSeries } from '../../../../../shared/cartoons/model/cartoons-series.model'; +import { WriteDialogComponent, WriteDialogData, WriteDialogResult } from '../../../dialog/series/write/write.dialog.component'; + +@Component({ + selector: 'app-cartoons-series-write', + templateUrl: './write.component.html', + styleUrls: ['./write.component.scss'] +}) +export class WriteComponent implements OnInit { + @Input() + user: User; + + @Input() + cartoonsSeries: CartoonsSeries; + + @Output() + selected = new EventEmitter(); + + + + cartoonsSeriesList: CartoonsSeries[]; + + constructor( + private cartoonsSeriesService: CartoonsSeriesService, + private matDialog: MatDialog, + private changeDetector: ChangeDetectorRef, + private store: Store, + ) { + } + + ngOnInit() { + this.cartoonsSeriesService.getAllByUid(this.user.id).pipe( + take(1), + tap(() => { + }), + map((cartoonsSeriesList: CartoonsSeries[]) => { + this.cartoonsSeriesList = cartoonsSeriesList; + }), + catchError((error) => { + return of(error); + }), + finalize(() => { + }) + ).subscribe(); + } + + compareCartoonsSeries(o1: CartoonsSeries | null, o2: CartoonsSeries | null): boolean { + if (null === o1 && null === o2) { + return true; + } else if (null !== o1 && null !== o2) { + return o1.id === o2.id; + } else { + return false; + } + } + + onSelectionChange(event: MatSelectChange) { + this.selected.emit(event.value); + } + + onClickAdd() { + const mediaDialogRef = this.matDialog.open( + WriteDialogComponent, + { + width: '300px', + data: { + user: this.user, + cartoonsSeriesList: this.cartoonsSeriesList + } + }); + + mediaDialogRef.afterClosed().pipe( + take(1), + map((result) => { + if (result.cartoonsSeries) { + this.cartoonsSeriesList.push(result.cartoonsSeries); + this.cartoonsSeries = result.cartoonsSeries; + this.selected.emit(result.cartoonsSeries); + this.changeDetector.markForCheck(); + } + }), + catchError((err) => { + return of(err); + }) + ).subscribe(); + } +} diff --git a/src/modules/cartoons/component/write/write.component.html b/src/modules/cartoons/component/write/write.component.html new file mode 100755 index 0000000..97dafb2 --- /dev/null +++ b/src/modules/cartoons/component/write/write.component.html @@ -0,0 +1,54 @@ + + + + +
+
+
+

{{ 'article.addFile' | translate }}

+
+ +
+
+
+ + +
+ + +
+

{{ cartoonsAttachments.attachments.name }}

+
+
+ + + + +
+ +
+
+ +
+

{{ articleFileItem.name }}

+
+
+
+
+
+
+ + +
+
+ + + + +
+
diff --git a/src/modules/cartoons/component/write/write.component.scss b/src/modules/cartoons/component/write/write.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/modules/cartoons/component/write/write.component.spec.ts b/src/modules/cartoons/component/write/write.component.spec.ts new file mode 100755 index 0000000..a9636f8 --- /dev/null +++ b/src/modules/cartoons/component/write/write.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { WriteComponent } from './write.component'; + +describe('WriteComponent', () => { + let component: WriteComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [WriteComponent] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(WriteComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/modules/cartoons/component/write/write.component.ts b/src/modules/cartoons/component/write/write.component.ts new file mode 100755 index 0000000..5486fa9 --- /dev/null +++ b/src/modules/cartoons/component/write/write.component.ts @@ -0,0 +1,322 @@ +import { + Component, + OnInit, + Input, + ViewChild, + ElementRef, + ChangeDetectorRef, + Output, + EventEmitter, + OnDestroy +} from '@angular/core'; +import { of, Observable, Observer } from 'rxjs'; +import { + take, + tap, + map, + catchError, + finalize +} from 'rxjs/operators'; +import { Store, } from '@ngrx/store'; + +import { User } from '../../../../shared/user/model/user.model'; +import { Cartoons } from '../../../../shared/cartoons/model/cartoons.model'; +import { CartoonsService } from '../../service/cartoons.service'; +import { + AttachmentsService, +} from '../../../attachments/service/attachments.service'; +import { CartoonsSeries } from '../../../../shared/cartoons/model/cartoons-series.model'; +import { ArticleImage } from '../../../../shared/article/type/article-image.type'; +import { MatDialog } from '@angular/material'; +import { MediaDialogComponent, MediaDialogData, MediaDialogResult } from '../../../common/shared/dialog/media/media.dialog.component'; +import { FileItem } from '../../../attachments/model/file-item.model'; + +import { CartoonsAttachments } from '../../../../shared/cartoons/model/cartoons-attachments.model'; + +import { UIUtil } from '../../../common/util/ui/dialog.util'; + +import { Article } from '../../../../shared/article/model/article.model'; +import { AccountsUtil } from '../../../../modules/accounts/util/accounts.util'; +import { Attachments } from '../../../../shared/attachments/model/attachments.model'; + + +@Component({ + selector: 'app-cartoons-write', + templateUrl: './write.component.html', + styleUrls: ['./write.component.scss'] +}) +export class WriteComponent implements OnInit, OnDestroy { + // tslint:disable-next-line:no-input-rename + @Input('article') + cartoons: Cartoons; + + @Output() + changedSaveObservable = new EventEmitter>(); + + @Output() + changedSaveDirty = new EventEmitter(); + + @ViewChild('fileInput') + fileInput: ElementRef; + + user: User; + + articleFileItemList: FileItem[]; + articleCoverFileItemIndex = 0; + + articleFileItemLoadingList: CartoonsAttachments[]; + articleFileItemLoading = false; + + uploading = false; + + private _canSaveArticle = false; + private _canSaveCartoons = false; + + private _saveDirtyArticle = false; + private _saveDirtyCartoons: boolean | undefined = undefined; + + private readonly _saveObservable = Observable.create(async (observer: Observer
) => { + if (this.articleFileItemList && 0 < this.articleFileItemList.length) { + const getAttachmentsIdList = (): string[] => { + const idList: string[] = []; + if (!this.cartoons.attachmentsList || 0 === this.cartoons.attachmentsList.length) { + return idList; + } + for (const cartoonsAttachments of this.cartoons.attachmentsList) { + idList.push(cartoonsAttachments.attachments.id); + } + return idList; + }; + + const deleteAttachmentsList: string[] = getAttachmentsIdList(); + const uploadFileItemList: FileItem[] = []; + + for (let index = 0; index < this.articleFileItemList.length; index++) { + const fileItem = this.articleFileItemList[index]; + const attachments: Attachments = fileItem.attachments; + if (attachments) { + const _i = deleteAttachmentsList.indexOf(attachments.id); + if (-1 < _i) { + deleteAttachmentsList.splice(_i, 1); + } + } else { + uploadFileItemList.push(fileItem); + } + } + if (0 < deleteAttachmentsList.length) { + await this.attachmentsService.deleteAttachFile(...deleteAttachmentsList); + } + if (0 < uploadFileItemList.length) { + await this.fileUpload(uploadFileItemList); + } + + if (this.articleFileItemList) { + const attachmentsList: CartoonsAttachments[] = []; + for (let index = 0; index < this.articleFileItemList.length; index++) { + const fileItem = this.articleFileItemList[index]; + let cartoonsAttachments: CartoonsAttachments = fileItem.data; + if (!cartoonsAttachments) { + cartoonsAttachments = { + }; + } + cartoonsAttachments.attachments = fileItem.attachments; + cartoonsAttachments.imageType = this.articleCoverFileItemIndex === index ? ArticleImage.Cover : ArticleImage.Contents; + cartoonsAttachments.sequence = index; + attachmentsList.push(cartoonsAttachments); + } + this.cartoons.attachmentsList = attachmentsList; + } + } else { + this.cartoons.attachmentsList = []; + } + + if (this.cartoons.id) { + this.cartoonsService.update(this.cartoons) + .pipe( + take(1), + map((cartoons) => { + observer.next(cartoons); + }), + catchError((error) => { + observer.error(error); + return of(); + }) + ) + .subscribe(); + + } else { + this.cartoonsService.add(this.cartoons) + .pipe( + take(1), + map((cartoons) => { + observer.next(cartoons); + }), + catchError((error) => { + observer.error(error); + return of(); + }) + ) + .subscribe(); + } + }); + + constructor( + private changeDetector: ChangeDetectorRef, + private matDialog: MatDialog, + private store: Store, + private cartoonsService: CartoonsService, + public attachmentsService: AttachmentsService, + ) { } + + ngOnInit() { + AccountsUtil.getUser(this.store) + .then((user) => { + this.user = user; + }) + .catch((reason) => { + console.log(reason); + }); + + if (!this.cartoons) { + this.cartoons = {}; + } else { + if (this.cartoons.attachmentsList && 0 < this.cartoons.attachmentsList.length) { + this.articleFileItemLoading = true; + this.articleFileItemList = []; + const { coverIndex, cartoonsAttachmentsList } = this.getCartoonsAttachments(this.cartoons.attachmentsList); + this.articleCoverFileItemIndex = coverIndex; + this.articleFileItemLoadingList = cartoonsAttachmentsList; + } + } + } + + ngOnDestroy(): void { + + } + + onLoadedCartoonsAttachments(index: number, fileItem: FileItem) { + this.articleFileItemList.splice(index, 0, fileItem); + fileItem.data = this.articleFileItemLoadingList[index]; + if (this.articleFileItemLoadingList.length === this.articleFileItemList.length) { + this.articleFileItemLoading = false; + this._canSaveCartoons = this.articleFileItemList && 0 < this.articleFileItemList.length; + this.emitCanSave(); + } + } + + getCartoonsAttachments(attachmentsList: CartoonsAttachments[]): { coverIndex: number, cartoonsAttachmentsList: CartoonsAttachments[] } { + if (!attachmentsList || 0 === attachmentsList.length) { + return null; + } + + const cartoonsAttachmentsList: CartoonsAttachments[] = []; + let coverCartoonsAttachments = null; + attachmentsList.forEach((attachments) => { + if (attachments.imageType === ArticleImage.Cover) { + coverCartoonsAttachments = attachments; + } + cartoonsAttachmentsList.splice(attachments.sequence, 0, attachments); + }); + + return { + coverIndex: coverCartoonsAttachments ? cartoonsAttachmentsList.indexOf(coverCartoonsAttachments) : 0, + cartoonsAttachmentsList: cartoonsAttachmentsList, + }; + } + + async onClickPreviewImage(index: number, fileItem: FileItem) { + if (this.articleCoverFileItemIndex !== index) { + this.articleCoverFileItemIndex = index; + if (!this._saveDirtyCartoons) { + this._saveDirtyCartoons = true; + this.emitSaveDirty(); + } + } + } + + onSelectedCartoonsSeries(cartoonsSeries: CartoonsSeries) { + this.cartoons.articleSeries = cartoonsSeries; + } + + onClickAddArticleFile() { + // 2019-01-24 수정 - 윤대훈 : fileItemList 가 레퍼런스로 참조하기 때문에 같은값 -> shallow copy 함 + const tempList = this.articleFileItemList ? this.articleFileItemList.slice(0) : []; + UIUtil.dialogOpen( + this.matDialog, MediaDialogComponent, + { + panelClass: 'app-full-screen-dialog', + data: { + multiSelect: true, + fileItemList: tempList, + } + } + ) + .then((result) => { + if (result.fileItemList) { + this.setArticleFileItemList(result.fileItemList); + this.changeDetector.detectChanges(); + } + }) + .catch((reason) => { + }); + } + + fileUpload(fileItemList: FileItem[]): Promise { + return new Promise((resolve, reject) => { + this.attachmentsService.upload(fileItemList).pipe( + take(1), + tap(() => { + }), + map(() => { + return resolve(); + }), + catchError((err) => { + reject(err); + return of(err); + }), + finalize(() => { + }) + ).subscribe(); + }); + } + + setArticleFileItemList(articleFileItemList: FileItem[]) { + if (!this._saveDirtyCartoons) { + if (JSON.stringify(this.articleFileItemList) !== JSON.stringify(articleFileItemList)) { + this._saveDirtyCartoons = true; + this.emitSaveDirty(); + } + } + this.articleFileItemList = articleFileItemList; + this._canSaveCartoons = this.articleFileItemList && 0 < this.articleFileItemList.length; + this.emitCanSave(); + } + + emitCanSave() { + this.changedSaveObservable.emit(this._canSaveArticle && this._canSaveCartoons ? this._saveObservable : undefined); + } + + onChangedCanSaveArticle(canSave: boolean) { + this._canSaveArticle = canSave; + this.emitCanSave(); + } + + emitSaveDirty() { + // 2019-01-24 수정 - 윤대훈 : 1가지라도 (article, cartoons) 등록/수정 시 경고 문구 출력 + this.changedSaveDirty.emit(this._saveDirtyArticle || this._saveDirtyCartoons); + } + + onChangedSaveDirtyArticle(saveDirty: boolean) { + this._saveDirtyArticle = saveDirty; + this.emitSaveDirty(); + } + + onClickDelete(index: number) { + this.articleFileItemList.splice(index, 1); + // 2019-01-25 수정 - 윤대훈 : 수정화면에서 파일을 모두 지울경우.. + if (0 >= this.articleFileItemList.length) { + this._canSaveCartoons = false; + this.emitCanSave(); + } + } +} diff --git a/src/modules/cartoons/dialog/index.ts b/src/modules/cartoons/dialog/index.ts new file mode 100755 index 0000000..2985c14 --- /dev/null +++ b/src/modules/cartoons/dialog/index.ts @@ -0,0 +1,7 @@ +import { SERIES_DIALOGS } from './series'; +import { ViewerDialogComponent } from './viewer/viewer.dialog.component'; + +export const DIALOGS = [ + ...SERIES_DIALOGS, + ViewerDialogComponent, +]; diff --git a/src/modules/cartoons/dialog/series/index.ts b/src/modules/cartoons/dialog/series/index.ts new file mode 100755 index 0000000..f5ff0be --- /dev/null +++ b/src/modules/cartoons/dialog/series/index.ts @@ -0,0 +1,5 @@ +import { WriteDialogComponent } from './write/write.dialog.component'; + +export const SERIES_DIALOGS = [ + WriteDialogComponent, +]; diff --git a/src/modules/cartoons/dialog/series/write/write.dialog.component.html b/src/modules/cartoons/dialog/series/write/write.dialog.component.html new file mode 100755 index 0000000..35560fe --- /dev/null +++ b/src/modules/cartoons/dialog/series/write/write.dialog.component.html @@ -0,0 +1,31 @@ +
+
+ + + {{title.value?.length || 0}}/{{max_catoons_series_title_leng}} + Title is required + You must enter a minimum of {{min_catoons_series_title_leng}} + characters for the title. + You must enter a maximum of {{max_catoons_series_title_leng}} + characters for the title. + Please enter a different title. + + + + + {{description.value?.length || 0}}/{{max_catoons_series_desc_leng}} + You must enter a minimum of {{min_catoons_series_desc_leng}} + characters for the description. + You must enter a maximum of {{max_catoons_series_desc_leng}} + characters for the description. + +
+ +
+
+
diff --git a/src/modules/cartoons/dialog/series/write/write.dialog.component.scss b/src/modules/cartoons/dialog/series/write/write.dialog.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/modules/cartoons/dialog/series/write/write.dialog.component.ts b/src/modules/cartoons/dialog/series/write/write.dialog.component.ts new file mode 100755 index 0000000..a7f6293 --- /dev/null +++ b/src/modules/cartoons/dialog/series/write/write.dialog.component.ts @@ -0,0 +1,168 @@ +import { Component, OnInit, Input, Output, EventEmitter, Inject } from '@angular/core'; +import { FormGroupDirective, AbstractControl, ValidatorFn, NgForm, FormControl, FormGroup, FormBuilder, Validators } from '@angular/forms'; +import { ErrorStateMatcher, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material'; +import { Observable, of } from 'rxjs'; +import { take, tap, map, catchError, switchMap, finalize } from 'rxjs/operators'; +import { Store } from '@ngrx/store'; +import { TranslateService } from '@ngx-translate/core'; +import { CartoonsSeries } from '../../../../../shared/cartoons/model/cartoons-series.model'; +import { User } from '../../../../../shared/user/model/user.model'; +import { CartoonsSeriesService } from '../../../../cartoons/service/cartoons-series.service'; +import * as EventsStore from '../../../../common/shared/store/events'; +import * as AppEventsStore from '../../../../../app/store/events'; + +export class ArticleSeriesErrorStateMatcher implements ErrorStateMatcher { + isErrorState(formControl: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { + const isSubmitted = form && form.submitted; + return !!(formControl && formControl.invalid && (formControl.dirty || formControl.touched || isSubmitted)); + } +} + +export function TitleValidator(series: CartoonsSeries[]): ValidatorFn { + return (c: AbstractControl) => { + if (c) { + const title = c.get('title').value; + if (series) { + series.map((cartooons) => { + if (cartooons.title === title) { + c.get('title').setErrors({ 'duplicatedValue': true }); + return; + } else { + return null; + } + }); + } + return null; + } + }; +} +export interface WriteDialogData { + user: User; + cartoonsSeries?: CartoonsSeries; + cartoonsSeriesList?: CartoonsSeries[]; +} + +export interface WriteDialogResult { + cartoonsSeries?: CartoonsSeries; +} + +@Component({ + selector: 'app-cartoons-series-write-dialog', + templateUrl: './write.dialog.component.html', + styleUrls: ['./write.dialog.component.scss'] +}) +export class WriteDialogComponent implements OnInit { + user: User; + cartoonsSeries: CartoonsSeries; + cartoonsSeriesList: CartoonsSeries[]; + + cartoonsSeriesForm: FormGroup; + errorStateMatcher = new ArticleSeriesErrorStateMatcher(); + + titleValidated = false; + + min_catoons_series_title_leng = 2; + max_catoons_series_title_leng = 32; + min_catoons_series_desc_leng = 2; + max_catoons_series_desc_leng = 100; + + cartoonsSeriesLabel: string; + cartoonsSeriesSaveSuccessMsg: string; + cartoonsSeriesSaveFailMsg: string; + cartoonsSeriesUpdateSuccessMsg: string; + cartoonsSeriesUpdateFailMsg: string; + + get titleFormControl() { return this.cartoonsSeriesForm.get('title'); } + get descriptionFormControl() { return this.cartoonsSeriesForm.get('description'); } + + constructor( + private translateService: TranslateService, + private formBuilder: FormBuilder, + private cartoonsSeriesService: CartoonsSeriesService, + private store: Store, + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) private data: WriteDialogData, + ) { + this.user = data.user; + this.cartoonsSeries = data.cartoonsSeries ? data.cartoonsSeries : {}; + this.cartoonsSeriesList = data.cartoonsSeriesList ? data.cartoonsSeriesList : []; + + this.translateService.get('article.cartoonsSeries').subscribe( + value => { + this.cartoonsSeriesLabel = value; + }); + this.translateService.get('article.cartoonsSeriesSaveSuccessMsg').subscribe( + value => { + this.cartoonsSeriesSaveSuccessMsg = value; + }); + this.translateService.get('article.cartoonsSeriesSaveFailMsg').subscribe( + value => { + this.cartoonsSeriesSaveFailMsg = value; + }); + this.translateService.get('article.cartoonsSeriesUpdateSuccessMsg').subscribe( + value => { + this.cartoonsSeriesUpdateSuccessMsg = value; + }); + this.translateService.get('article.cartoonsSeriesUpdateFailMsg').subscribe( + value => { + this.cartoonsSeriesUpdateFailMsg = value; + }); + } + + ngOnInit() { + this.cartoonsSeriesForm = this.formBuilder.group({ + 'title': new FormControl('', [ + Validators.required, + Validators.minLength(this.min_catoons_series_title_leng), + Validators.maxLength(this.max_catoons_series_title_leng), + ]), + 'description': new FormControl('', [ + Validators.minLength(this.min_catoons_series_desc_leng), + Validators.maxLength(this.max_catoons_series_desc_leng), + ]), + }, { validator: TitleValidator(this.cartoonsSeriesList) }); + } + + onClickSave() { + this.cartoonsSeries.title = this.titleFormControl.value; + this.cartoonsSeries.description = this.descriptionFormControl.value; + + const isUpdate = this.cartoonsSeries.id ? true : false; + + let _observable: Observable; + if (isUpdate) { + _observable = this.cartoonsSeriesService.update(this.cartoonsSeries); + } else { + _observable = this.cartoonsSeriesService.add(this.cartoonsSeries); + } + + _observable.pipe( + take(1), + tap(() => { + this.store.dispatch(new EventsStore.Execution()); + }), + map((cartoonsSeries) => { + this.store.dispatch(new EventsStore.ExecutionSuccess({ + title: this.cartoonsSeriesLabel, + message: isUpdate ? this.cartoonsSeriesUpdateSuccessMsg : this.cartoonsSeriesSaveSuccessMsg, + })); + + this.dialogRef.close({ + cartoonsSeries: cartoonsSeries, + }); + }), + catchError((error) => { + this.store.dispatch(new EventsStore.ExecutionFailure({ + title: this.cartoonsSeriesLabel, + message: isUpdate ? this.cartoonsSeriesUpdateFailMsg : this.cartoonsSeriesSaveFailMsg, + error: error, + })); + + return of(error); + }), + finalize(() => { + this.store.dispatch(new AppEventsStore.HideProgressBar()); + }), + ).subscribe(); + } +} diff --git a/src/modules/cartoons/dialog/viewer/viewer.dialog.component.html b/src/modules/cartoons/dialog/viewer/viewer.dialog.component.html new file mode 100755 index 0000000..d7337a3 --- /dev/null +++ b/src/modules/cartoons/dialog/viewer/viewer.dialog.component.html @@ -0,0 +1,17 @@ + + + + +
+
+
+ + +
+
+
+
+ +
+
diff --git a/src/modules/cartoons/dialog/viewer/viewer.dialog.component.scss b/src/modules/cartoons/dialog/viewer/viewer.dialog.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/modules/cartoons/dialog/viewer/viewer.dialog.component.ts b/src/modules/cartoons/dialog/viewer/viewer.dialog.component.ts new file mode 100755 index 0000000..b9b8ac6 --- /dev/null +++ b/src/modules/cartoons/dialog/viewer/viewer.dialog.component.ts @@ -0,0 +1,52 @@ +import { Component, OnInit, Inject } from '@angular/core'; +import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material'; +import { Store } from '@ngrx/store'; + +import { CartoonsSeriesService } from '../../../cartoons/service/cartoons-series.service'; +import { Cartoons } from '../../../../shared/cartoons/model/cartoons.model'; +import { CartoonsService } from '../../service/cartoons.service'; +import { ArticleImage } from '../../../../shared/article/type/article-image.type'; +import { ArticleDisplay } from '../../../../shared/article/type/article-display.type'; + +export interface ViewerDialogData { + cartoons: Cartoons; +} + +export interface ViewerDialogResult { + cartoons?: Cartoons; +} + +@Component({ + selector: 'app-cartoons-viewer-dialog', + templateUrl: './viewer.dialog.component.html', + styleUrls: ['./viewer.dialog.component.scss'] +}) +export class ViewerDialogComponent implements OnInit { + cartoons: Cartoons; + imageConfigKeyList: string[]; + + constructor( + private cartoonsService: CartoonsService, + private cartoonsSeriesService: CartoonsSeriesService, + private store: Store, + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) private data: ViewerDialogData, + ) { + this.cartoons = data.cartoons ? data.cartoons : {}; + } + + ngOnInit() { + this.imageConfigKeyList = [ + 'article', + ArticleImage.Contents, + ArticleDisplay.Viewer, + ]; + } + + onCloseViewer() { + this.dialogRef.close({ + cartoons: this.cartoons, + }); + } + +} diff --git a/src/modules/cartoons/service/article-user.service.ts b/src/modules/cartoons/service/article-user.service.ts new file mode 100755 index 0000000..d1de9a2 --- /dev/null +++ b/src/modules/cartoons/service/article-user.service.ts @@ -0,0 +1,251 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; + +import { environment } from '../../../environments/environment'; +import { User } from '../../../shared/user/model/user.model'; +import { AbstractRestService } from '../../common/util/service/abstract-rest.service'; +import { UserFollowers } from './../../../shared/user/model/user-followers.model'; + +@Injectable() +export class ArticleUserService extends AbstractRestService { + userDummy: User; + + followerDummy: UserFollowers[]; + followingDummy: UserFollowers[]; + + public constructor(httpClient: HttpClient) { + super(httpClient, environment.apiEntryPoint + '/article'); + } + + // mockup data + getUserInfo(paramId): User { + // 사용자 정보 가져오기 + console.log('>>>>>>>>>>>>>>>paramId: ', paramId); + this.userDummy = { + id: 'testID', + nickname: 'Michelle Lam', + userAnaliArticle: { + uid: 'testID', + cartoonCount: 10, + illustCount: 20, + novelCount: 100, + totalCount: 130 + }, + followerCount: 5455, + followingCount: 422, + profileImageAttachments: { + id: 'sadfksafksalafsdkf' + }, + backgroundImageAttachments: { + id: 'safsdfsdfsafdsfdsafg' + }, + introduction: + 'story Artist at Glen Keane Productions / Netflix, CalArts Class of 2K18' + }; + + return this.userDummy; + } + + // mockup data + // getUserFollowYN(sessinId, paramId): boolean { + // // 팔로우 유무를 가져온다. + // const followYn = true; + // return followYn; + // } + + // mockup data + // getArticleGridList(paramId): ArticleGrid[] { + // // 컨텐츤 목록을 가져온다. (그리드 리스트) + // this.articleGridDummy = [ + // { + // aid: '1', + // imgUrl: + // 'https://img.insight.co.kr/static/2018/01/01/700/44pmr8p54ap2856th7t6.jpg', + // imgTitle: 'title_01' + // }, + // { + // aid: '2', + // imgUrl: + // 'https://img.insight.co.kr/static/2018/01/01/700/44pmr8p54ap2856th7t6.jpg', + // imgTitle: 'title_02' + // }, + // { + // aid: '3', + // imgUrl: + // 'https://img.insight.co.kr/static/2018/01/01/700/44pmr8p54ap2856th7t6.jpg', + // imgTitle: 'title_03' + // }, + // { + // aid: '4', + // imgUrl: + // 'https://img.insight.co.kr/static/2018/01/01/700/44pmr8p54ap2856th7t6.jpg', + // imgTitle: 'title_04' + // }, + // { + // aid: '5', + // imgUrl: + // 'https://img.insight.co.kr/static/2018/01/01/700/44pmr8p54ap2856th7t6.jpg', + // imgTitle: 'title_05' + // }, + // { + // aid: '6', + // imgUrl: + // 'https://img.insight.co.kr/static/2018/01/01/700/44pmr8p54ap2856th7t6.jpg', + // imgTitle: 'title_06' + // }, + // { + // aid: '7', + // imgUrl: + // 'https://img.insight.co.kr/static/2018/01/01/700/44pmr8p54ap2856th7t6.jpg', + // imgTitle: 'title_07' + // }, + // { + // aid: '8', + // imgUrl: + // 'https://img.insight.co.kr/static/2018/01/01/700/44pmr8p54ap2856th7t6.jpg', + // imgTitle: 'title_08' + // }, + // { + // aid: '9', + // imgUrl: + // 'https://img.insight.co.kr/static/2018/01/01/700/44pmr8p54ap2856th7t6.jpg', + // imgTitle: 'title_09' + // }, + // { + // aid: '10', + // imgUrl: + // 'https://img.insight.co.kr/static/2018/01/01/700/44pmr8p54ap2856th7t6.jpg', + // imgTitle: 'title_10' + // }, + // { + // aid: '11', + // imgUrl: + // 'https://img.insight.co.kr/static/2018/01/01/700/44pmr8p54ap2856th7t6.jpg', + // imgTitle: 'title_11' + // } + // ]; + // return this.articleGridDummy; + // } + + // // mockup data + // getUserFollowerList(paramId): UserFollow[] { + // this.followerDummy = [ + // { + // uid: 'testID', + // followUid: 'dummy01', + // followNickName: '스펀지밥01', + // fProfileImgUrl: + // 'https://img.insight.co.kr/static/2018/01/01/700/44pmr8p54ap2856th7t6.jpg', + // fIntroduction: 'dummy01 소개글', + // followYn: false + // }, + // { + // uid: 'testID', + // followUid: 'dummy02', + // followNickName: '뚱이02', + // fProfileImgUrl: + // 'https://img.insight.co.kr/static/2016/06/13/2000/s2q33j23pj02068k8j7v.jpg', + // fIntroduction: 'dummy02 소개글', + // followYn: false + // }, + // { + // uid: 'testID', + // followUid: 'dummy03', + // followNickName: '징징이03', + // fProfileImgUrl: + // 'https://pbs.twimg.com/profile_images/565080330218897409/DCTWrcEO.jpeg', + // fIntroduction: 'dummy03 소개글', + // followYn: false + // }, + // { + // uid: 'testID', + // followUid: 'dummy04', + // followNickName: '집게사장04', + // fProfileImgUrl: + // 'https://pbs.twimg.com/profile_images/980079928971440129/u5gC5Xvc_400x400.jpg', + // fIntroduction: 'dummy04 소개글', + // followYn: false + // }, + // { + // uid: 'testID', + // followUid: 'dummy05', + // followNickName: '플랑크톤05', + // fProfileImgUrl: + // 'https://i.pinimg.com/originals/c7/3e/26/c73e26d1d203d0a92d8d2a1745dfce07.jpg', + // fIntroduction: 'dummy05 소개글', + // followYn: false + // }, + // { + // uid: 'testID', + // followUid: 'dummy06', + // followNickName: '게리', + // fProfileImgUrl: + // 'https://pbs.twimg.com/profile_images/939518831357075456/g5u4AUPT_400x400.jpg', + // fIntroduction: 'dummy06 소개글', + // followYn: true + // } + // ]; + // return this.followerDummy; + // } + + // // mockup data + // getUserFollowingList(paramId): UserFollow[] { + // this.followingDummy = [ + // { + // uid: 'testID', + // followUid: 'dummy01', + // followNickName: '스펀지밥', + // fProfileImgUrl: + // 'https://img.insight.co.kr/static/2018/01/01/700/44pmr8p54ap2856th7t6.jpg', + // fIntroduction: 'dummy01 소개글', + // followYn: true + // }, + // { + // uid: 'testID', + // followUid: 'dummy02', + // followNickName: '뚱이', + // fProfileImgUrl: + // 'https://img.insight.co.kr/static/2016/06/13/2000/s2q33j23pj02068k8j7v.jpg', + // fIntroduction: 'dummy02 소개글', + // followYn: true + // }, + // { + // uid: 'testID', + // followUid: 'dummy03', + // followNickName: '징징이', + // fProfileImgUrl: + // 'https://pbs.twimg.com/profile_images/565080330218897409/DCTWrcEO.jpeg', + // fIntroduction: 'dummy03 소개글', + // followYn: true + // }, + // { + // uid: 'testID', + // followUid: 'dummy04', + // followNickName: '집게사장', + // fProfileImgUrl: + // 'https://pbs.twimg.com/profile_images/980079928971440129/u5gC5Xvc_400x400.jpg', + // fIntroduction: 'dummy04 소개글', + // followYn: true + // }, + // { + // uid: 'testID', + // followUid: 'dummy05', + // followNickName: '플랑크톤', + // fProfileImgUrl: + // 'https://i.pinimg.com/originals/c7/3e/26/c73e26d1d203d0a92d8d2a1745dfce07.jpg', + // fIntroduction: 'dummy05 소개글', + // followYn: true + // }, + // { + // uid: 'testID', + // followUid: 'dummy06', + // followNickName: '게리', + // fProfileImgUrl: + // 'https://pbs.twimg.com/profile_images/939518831357075456/g5u4AUPT_400x400.jpg', + // fIntroduction: 'dummy06 소개글', + // followYn: true + // } + // ]; + // return this.followingDummy; + // } +} diff --git a/src/modules/cartoons/service/cartoons-series.service.ts b/src/modules/cartoons/service/cartoons-series.service.ts new file mode 100755 index 0000000..02dc016 --- /dev/null +++ b/src/modules/cartoons/service/cartoons-series.service.ts @@ -0,0 +1,18 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; + +import { environment } from '../../../environments/environment'; +import { AbstractRestService } from '../../common/util/service/abstract-rest.service'; +import { CartoonsSeries } from '../../../shared/cartoons/model/cartoons-series.model'; +import { Observable } from 'rxjs'; + +@Injectable() +export class CartoonsSeriesService extends AbstractRestService { + public constructor(httpClient: HttpClient) { + super(httpClient, environment.apiEntryPoint + '/cartoons_series'); + } + + public getAllByUid(uid: string): Observable { + return this.httpClient.get(`${this.apiEntryPoint}/${uid}`); + } +} diff --git a/src/modules/cartoons/service/cartoons.service.ts b/src/modules/cartoons/service/cartoons.service.ts new file mode 100755 index 0000000..688e620 --- /dev/null +++ b/src/modules/cartoons/service/cartoons.service.ts @@ -0,0 +1,16 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; + +import { ArticleService } from '../../article/service/article.service'; +import { Cartoons } from '../../../shared/cartoons/model/cartoons.model'; +import { environment } from '../../../environments/environment'; +import { ArticleImage } from '../../../shared/article/type/article-image.type'; +import { ArticleType } from '../../../shared/article/type/article-type.type'; + +@Injectable() +export class CartoonsService extends ArticleService { + public constructor(httpClient: HttpClient) { + super(httpClient); + this.apiEntryPoint = environment.apiEntryPoint + '/cartoons'; + } +} diff --git a/src/modules/cartoons/service/index.ts b/src/modules/cartoons/service/index.ts new file mode 100755 index 0000000..b5b3f5e --- /dev/null +++ b/src/modules/cartoons/service/index.ts @@ -0,0 +1,9 @@ +import { CartoonsService } from './cartoons.service'; +import { CartoonsSeriesService } from './cartoons-series.service'; +import { ArticleUserService } from './article-user.service'; + +export const SERVICES = [ + CartoonsService, + CartoonsSeriesService, + ArticleUserService +]; diff --git a/src/modules/cartoons/store/index.ts b/src/modules/cartoons/store/index.ts new file mode 100755 index 0000000..b56920e --- /dev/null +++ b/src/modules/cartoons/store/index.ts @@ -0,0 +1,18 @@ +import { + createSelector, + createFeatureSelector, +} from '@ngrx/store'; + +import * as SaveStore from './save'; + +// tslint:disable-next-line:no-empty-interface +export interface State { +} + +export const REDUCERS = { +}; + +export const EFFECTS = [ +]; + +export const selectState = createFeatureSelector('cartoons'); diff --git a/src/modules/cartoons/store/save/index.ts b/src/modules/cartoons/store/save/index.ts new file mode 100755 index 0000000..8926e90 --- /dev/null +++ b/src/modules/cartoons/store/save/index.ts @@ -0,0 +1 @@ +export * from './save.action'; diff --git a/src/modules/cartoons/store/save/save.action.ts b/src/modules/cartoons/store/save/save.action.ts new file mode 100755 index 0000000..2b50939 --- /dev/null +++ b/src/modules/cartoons/store/save/save.action.ts @@ -0,0 +1,16 @@ +import { Action } from '@ngrx/store'; +import { Cartoons } from '../../../../shared/cartoons/model/cartoons.model'; + +export enum ActionType { + SaveSuccess = '[cartoons.save] SaveSuccess', +} + +export class SaveSuccess implements Action { + readonly type = ActionType.SaveSuccess; + + constructor(public payload: { cartoons: Cartoons }) { } +} + +export type Actions = + | SaveSuccess + ; diff --git a/src/modules/common/shared/component/index.ts b/src/modules/common/shared/component/index.ts new file mode 100755 index 0000000..05c8a2f --- /dev/null +++ b/src/modules/common/shared/component/index.ts @@ -0,0 +1,5 @@ +import { ScrollingComponent } from './scrolling/scrolling.component'; + +export const COMPONENTS = [ + ScrollingComponent, +]; diff --git a/src/modules/common/shared/component/scrolling/scrolling.component.html b/src/modules/common/shared/component/scrolling/scrolling.component.html new file mode 100755 index 0000000..dd89887 --- /dev/null +++ b/src/modules/common/shared/component/scrolling/scrolling.component.html @@ -0,0 +1,14 @@ +
+ + + +
diff --git a/src/modules/common/shared/component/scrolling/scrolling.component.scss b/src/modules/common/shared/component/scrolling/scrolling.component.scss new file mode 100755 index 0000000..8856204 --- /dev/null +++ b/src/modules/common/shared/component/scrolling/scrolling.component.scss @@ -0,0 +1,28 @@ +.container { + width: 96%; + margin: 10px auto; + + .example-viewport { + height: 220px; + width: 100%; + border: 1px solid black; + background: white; + border: solid 1px #999; + } + + .example-item { + height: 160px; + padding: 5px 10px; + color: #999; + + .img-item { + float: left; + width: 25%; + } + + .p-item { + float: left; + width: 75%; + } + } +} diff --git a/src/modules/common/shared/component/scrolling/scrolling.component.ts b/src/modules/common/shared/component/scrolling/scrolling.component.ts new file mode 100755 index 0000000..876db1d --- /dev/null +++ b/src/modules/common/shared/component/scrolling/scrolling.component.ts @@ -0,0 +1,31 @@ +import { Component, Input, Output, EventEmitter, AfterViewInit } from '@angular/core'; + +interface Photos { + title: string; + thumbnailUrl: string; +} + +@Component({ + selector: 'app-scrolling', + templateUrl: './scrolling.component.html', + styleUrls: ['./scrolling.component.scss'] +}) +export class ScrollingComponent implements AfterViewInit { + photos: Photos[]; + + constructor( + + ) { + this.photos = []; + for (let index = 0; index < 10; index++) { + this.photos.push({ + title: 'title', + thumbnailUrl: '', + }); + } + } + + ngAfterViewInit(): void { + } + +} diff --git a/src/modules/common/shared/dialog/age-limit/age-limit.dialog.component.html b/src/modules/common/shared/dialog/age-limit/age-limit.dialog.component.html new file mode 100755 index 0000000..a2642c9 --- /dev/null +++ b/src/modules/common/shared/dialog/age-limit/age-limit.dialog.component.html @@ -0,0 +1,16 @@ +

+ {{ data.title | translate }} +

+ +
+
+ + {{ ageLimit.label | translate }} + +
+
+ + + + + diff --git a/src/modules/common/shared/dialog/age-limit/age-limit.dialog.component.scss b/src/modules/common/shared/dialog/age-limit/age-limit.dialog.component.scss new file mode 100755 index 0000000..6b2c7dd --- /dev/null +++ b/src/modules/common/shared/dialog/age-limit/age-limit.dialog.component.scss @@ -0,0 +1,6 @@ +.example-section { + display: flex; + align-content: center; + align-items: center; + height: 40px; +} diff --git a/src/modules/common/shared/dialog/age-limit/age-limit.dialog.component.spec.ts b/src/modules/common/shared/dialog/age-limit/age-limit.dialog.component.spec.ts new file mode 100755 index 0000000..37405c3 --- /dev/null +++ b/src/modules/common/shared/dialog/age-limit/age-limit.dialog.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AgeLimitDialogComponent } from './age-limit.dialog.component'; + +describe('AgeLimitDialogComponent', () => { + let component: AgeLimitDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [AgeLimitDialogComponent] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AgeLimitDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/modules/common/shared/dialog/age-limit/age-limit.dialog.component.ts b/src/modules/common/shared/dialog/age-limit/age-limit.dialog.component.ts new file mode 100755 index 0000000..bbbea65 --- /dev/null +++ b/src/modules/common/shared/dialog/age-limit/age-limit.dialog.component.ts @@ -0,0 +1,67 @@ +/** + * 파 일 명: age-limit.dialog.component.ts + * 작성일자: 2018-12-18 + * 작 성 자: 최지련 + * 설 명: Age Limit Dialog component를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Component, OnInit, Inject, Input, Output, EventEmitter } from '@angular/core'; +import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material'; + +export interface DialogAgeLimitData { + title: string; + ageLimits: Array; +} + +@Component({ + selector: 'app-age-limit-dialog', + templateUrl: './age-limit.dialog.component.html', + styleUrls: [ + './age-limit.dialog.component.scss', + ], +}) +export class AgeLimitDialogComponent implements OnInit { + + tempAgeLimits = []; + + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: DialogAgeLimitData + ) { + // 배열 참조없는 복사 + this.tempAgeLimits = JSON.parse(JSON.stringify(data.ageLimits)); + } + + ngOnInit(): void { + } + + onNoClick(): void { + this.dialogRef.close(); + } + + onChange(type) { + // console.log(`[AppDialogAgeLimitComponent] onChange :: ${type} :: ${this.tempAgeLimits[type].checked}`); + // console.log(`[AppDialogAgeLimitComponent] onChange - before :: ${JSON.stringify(this.tempAgeLimits)}`); + + if (!this.tempAgeLimits[type].checked) { + if (type === 0) { + for (const key in this.tempAgeLimits) { + if (key !== '0') { + this.tempAgeLimits[key].checked = false; + } + } + + } else { + this.tempAgeLimits[0].checked = false; + } + } + + // 선택값 checked toggle + this.tempAgeLimits[type].checked = !this.tempAgeLimits[type].checked; + + // console.log(`[AppDialogAgeLimitComponent] onChange - result :: ${JSON.stringify(this.tempAgeLimits)}`); + } +} diff --git a/src/modules/common/shared/dialog/image-cropper/image-cropper.dialog.component.html b/src/modules/common/shared/dialog/image-cropper/image-cropper.dialog.component.html new file mode 100755 index 0000000..c32bcdc --- /dev/null +++ b/src/modules/common/shared/dialog/image-cropper/image-cropper.dialog.component.html @@ -0,0 +1,17 @@ + + + + {{title}} +
+ + +
+
+ +
+ + + +
diff --git a/src/modules/common/shared/dialog/image-cropper/image-cropper.dialog.component.scss b/src/modules/common/shared/dialog/image-cropper/image-cropper.dialog.component.scss new file mode 100755 index 0000000..adbd040 --- /dev/null +++ b/src/modules/common/shared/dialog/image-cropper/image-cropper.dialog.component.scss @@ -0,0 +1,297 @@ +// 임시로 적용하는 스타일 시작 +.cropper-wrap-box { + display: none +} + +// 임시로 적용하는 스타일 끝 +.cropper { + &-container { + direction: ltr; + font-size: 0; + line-height: 0; + position: relative; + touch-action: none; + user-select: none; + + img { + display: block; + height: 100%; + image-orientation: 0deg; + max-height: none !important; + max-width: none !important; + min-height: 0 !important; + min-width: 0 !important; + width: 100%; + } + } + + &-wrap-box, + &-canvas, + &-drag-box, + &-crop-box, + &-modal { + bottom: 0; + left: 0; + position: absolute; + right: 0; + top: 0; + } + + &-wrap-box, + &-canvas { + overflow: hidden; + } + + &-drag-box { + background-color: #fff; + opacity: 0; + } + + &-modal { + background-color: #000; + opacity: .5; + } + + &-view-box { + display: block; + height: 100%; + outline-color: rgba(51, 153, 255, 0.75); + outline: 1px solid #39f; + overflow: hidden; + width: 100%; + } + + &-dashed { + border: 0 dashed #eee; + display: block; + opacity: .5; + position: absolute; + + &.dashed-h { + border-bottom-width: 1px; + border-top-width: 1px; + height: calc(100% / 3); + left: 0; + top: calc(100% / 3); + width: 100%; + } + + &.dashed-v { + border-left-width: 1px; + border-right-width: 1px; + height: 100%; + left: calc(100% / 3); + top: 0; + width: calc(100% / 3); + } + } + + &-center { + display: block; + height: 0; + left: 50%; + opacity: .75; + position: absolute; + top: 50%; + width: 0; + + &:before, + &:after { + background-color: #eee; + content: ' '; + display: block; + position: absolute; + } + + &:before { + height: 1px; + left: -3px; + top: 0; + width: 7px; + } + + &:after { + height: 7px; + left: 0; + top: -3px; + width: 1px; + } + } + + &-face, + &-line, + &-point { + display: block; + height: 100%; + opacity: .1; + position: absolute; + width: 100%; + } + + &-face { + background-color: #fff; + left: 0; + top: 0; + } + + &-line { + background-color: #39f; + + &.line-e { + cursor: ew-resize; + right: -3px; + top: 0; + width: 5px; + } + + &.line-n { + cursor: ns-resize; + height: 5px; + left: 0; + top: -3px; + } + + &.line-w { + cursor: ew-resize; + left: -3px; + top: 0; + width: 5px; + } + + &.line-s { + bottom: -3px; + cursor: ns-resize; + height: 5px; + left: 0; + } + } + + &-point { + background-color: #39f; + height: 5px; + opacity: .75; + width: 5px; + + &.point-e { + cursor: ew-resize; + margin-top: -3px; + right: -3px; + top: 50%; + } + + &.point-n { + cursor: ns-resize; + left: 50%; + margin-left: -3px; + top: -3px; + } + + &.point-w { + cursor: ew-resize; + left: -3px; + margin-top: -3px; + top: 50%; + } + + &.point-s { + bottom: -3px; + cursor: s-resize; + left: 50%; + margin-left: -3px; + } + + &.point-ne { + cursor: nesw-resize; + right: -3px; + top: -3px; + } + + &.point-nw { + cursor: nwse-resize; + left: -3px; + top: -3px; + } + + &.point-sw { + bottom: -3px; + cursor: nesw-resize; + left: -3px; + } + + &.point-se { + bottom: -3px; + cursor: nwse-resize; + height: 20px; + opacity: 1; + right: -3px; + width: 20px; + + @media (min-width: 768px) { + height: 15px; + width: 15px; + } + + @media (min-width: 992px) { + height: 10px; + width: 10px; + } + + @media (min-width: 1200px) { + height: 5px; + opacity: .75; + width: 5px; + } + } + + &.point-se:before { + background-color: #39f; + bottom: -50%; + content: ' '; + display: block; + height: 200%; + opacity: 0; + position: absolute; + right: -50%; + width: 200%; + } + } + + &-invisible { + opacity: 0; + } + + &-bg { + background-image: url('../../../../../assets/img/background/cropper-background.png'); + } + + &-hide { + display: block; + height: 0; + position: absolute; + width: 0; + } + + &-hidden { + display: none !important; + } + + &-move { + cursor: move; + } + + &-crop { + cursor: crosshair; + } + + &-disabled &-drag-box, + &-disabled &-face, + &-disabled &-line, + &-disabled &-point { + cursor: not-allowed; + } +} + +img { + /* This rule is very important, please do not ignore this! */ + max-width: 100%; +} diff --git a/src/modules/common/shared/dialog/image-cropper/image-cropper.dialog.component.ts b/src/modules/common/shared/dialog/image-cropper/image-cropper.dialog.component.ts new file mode 100755 index 0000000..d6163b8 --- /dev/null +++ b/src/modules/common/shared/dialog/image-cropper/image-cropper.dialog.component.ts @@ -0,0 +1,64 @@ +import { Component, Inject, ViewChild, OnInit } from '@angular/core'; +import { + MatDialogRef, + MAT_DIALOG_DATA, +} from '@angular/material'; +import { FileItem } from '../../../../attachments/model/file-item.model'; +import { ImageCroppedEvent, ImageCropperComponent } from 'ngx-image-cropper'; + +export interface ImageCropperDialogData { + fileItem: FileItem; + title?: string; +} + +export interface ImageCropperDialogResult { + fileItem: FileItem; +} + +@Component({ + selector: 'app-article-image-cropper-dialog', + templateUrl: './image-cropper.dialog.component.html', + styleUrls: ['./image-cropper.dialog.component.scss'] +}) +export class ImageCropperDialogComponent implements OnInit { + + @ViewChild('cropper') cropper: ImageCropperComponent; + + croppedImage: any; + title = 'Image'; + + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) private data: ImageCropperDialogData, + ) { + } + + ngOnInit(): void { + + } + + + onClickCancel() { + this.dialogRef.close(); + } + + async onClickDone() { + const fileItem = await FileItem.fromBlob('NewProfileImage', this.croppedImage.file); + this.dialogRef.close({ + fileItem: fileItem + }); + } + + onCroped(event: CustomEvent) { + console.log(event); + } + + imageCropped(event: ImageCroppedEvent) { + this.croppedImage = event; + } + loadImageFailed() { + alert('Failed to load.'); + } + + +} diff --git a/src/modules/common/shared/dialog/index.ts b/src/modules/common/shared/dialog/index.ts new file mode 100755 index 0000000..ab962ec --- /dev/null +++ b/src/modules/common/shared/dialog/index.ts @@ -0,0 +1,9 @@ +import { AgeLimitDialogComponent } from './age-limit/age-limit.dialog.component'; +import { ImageCropperDialogComponent } from './image-cropper/image-cropper.dialog.component'; +import { MediaDialogComponent } from './media/media.dialog.component'; + +export const DIALOGS = [ + AgeLimitDialogComponent, + ImageCropperDialogComponent, + MediaDialogComponent, +]; diff --git a/src/modules/common/shared/dialog/media/media.dialog.component.html b/src/modules/common/shared/dialog/media/media.dialog.component.html new file mode 100755 index 0000000..b92f853 --- /dev/null +++ b/src/modules/common/shared/dialog/media/media.dialog.component.html @@ -0,0 +1,88 @@ + + + + {{title}} +
+ + +
+
+ Files: {{fileItemList?.length}} +
+ + + + +
+
+ +
+
+ +
+

{{ fileItem.name }}

+
+ + upload + +
+
+ + + + + + GIF + + + {{ 'article.msgCreateGIF' | translate }} + + + + + + + + + GIF Preview + + + + + + + + + + + + + Interval + + + + Number of Frames + + + + Duration of frame + + + + Interval of Sample + + + + + + + + + +
diff --git a/src/modules/common/shared/dialog/media/media.dialog.component.scss b/src/modules/common/shared/dialog/media/media.dialog.component.scss new file mode 100755 index 0000000..0ffaf5e --- /dev/null +++ b/src/modules/common/shared/dialog/media/media.dialog.component.scss @@ -0,0 +1,9 @@ +.preview-img {} + +.selected { + background-color: aqua; +} + +.selected-cover { + background-color: blue; +} diff --git a/src/modules/common/shared/dialog/media/media.dialog.component.ts b/src/modules/common/shared/dialog/media/media.dialog.component.ts new file mode 100755 index 0000000..de362b8 --- /dev/null +++ b/src/modules/common/shared/dialog/media/media.dialog.component.ts @@ -0,0 +1,236 @@ +import { Component, Inject, ViewChild, ElementRef, ChangeDetectorRef } from '@angular/core'; +import { + MatDialogRef, + MAT_DIALOG_DATA, + MatSliderChange, + MatExpansionPanel, + MatAccordion, +} from '@angular/material'; +import { Subject } from 'rxjs'; +import { debounceTime, map } from 'rxjs/operators'; +import { FileItem } from '../../../../attachments/model/file-item.model'; + +const gifshot = require('gifshot'); + +export interface MediaDialogData { + title?: string; + multiSelect?: boolean; + fileItemList?: FileItem[]; +} + +export interface MediaDialogResult { + fileItemList: FileItem[]; +} + +export interface GIFOption { + gifWidth: number; + gifHeight: number; + interval: number; + numFrames: number; + frameDuration: number; + sampleInterval: number; +} + +const gifOptionDefault: GIFOption = { + gifWidth: 690, + gifHeight: 400, + interval: 0.1, + numFrames: 10, + frameDuration: 1, + sampleInterval: 10, +}; + +@Component({ + selector: 'app-article-media-dialog', + templateUrl: './media.dialog.component.html', + styleUrls: ['./media.dialog.component.scss'] +}) +export class MediaDialogComponent { + @ViewChild('fileInput') + fileInput: ElementRef; + + @ViewChild('gifPreviewImg') + gifPreviewImg: ElementRef; + + @ViewChild('gifAccordion') + gifAccordion: MatAccordion; + + fileItemList: FileItem[]; + selectedGifFileItemList: FileItem[] = []; + + title = 'Media'; + + gifMode = false; + gifSubject: Subject | null = null; + gifProcessing = false; + gifDataURL: string; + gifOption: GIFOption = { + ...gifOptionDefault, + }; + + constructor( + private changeDetector: ChangeDetectorRef, + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) private data: MediaDialogData, + ) { + this.fileItemList = data.fileItemList ? data.fileItemList : []; + } + + onClickCancel() { + this.dialogRef.close(); + } + + onClickDone() { + this.dialogRef.close({ + fileItemList: this.fileItemList, + }); + } + + onClickDelete(index: number) { + this.fileItemList.splice(index, 1); + } + + onClickUpload() { + this.fileInput.nativeElement.click(); + } + + async onChangeFile(event) { + if (event.target.files && event.target.files.length > 0) { + for (let index = 0; index < event.target.files.length; index++) { + this.fileItemList.push(await FileItem.fromFile(event.target.files[index])); + } + this.changeDetector.markForCheck(); + } + } + + async onClickPreviewImage(fileItem: FileItem) { + this.gifAccordion.openAll(); + await this.initGifGeneration(); + + const selectedIndex = this.selectedGifFileItemList.indexOf(fileItem); + if (-1 < selectedIndex) { + this.selectedGifFileItemList.splice(selectedIndex, 1); + this.gifSubject.next(); + return; + } + + this.selectedGifFileItemList.push(fileItem); + this.gifSubject.next(); + } + + onLoadPreviewImg(event, fileItem: FileItem) { + // const target = event.target; + // console.log(event, fileItem, target, target.naturalWidth); + } + + async onOpenedGIF() { + await this.initGifGeneration(); + } + + onClosedGIF() { + this.gifMode = false; + this.selectedGifFileItemList = []; + if (this.gifSubject) { + this.gifSubject.complete(); + this.gifSubject = null; + } + } + + async initGifGeneration(): Promise { + return new Promise((resolve, reject) => { + if (this.gifMode) { + return resolve(); + } + this.gifMode = true; + this.gifSubject = new Subject(); + this.gifSubject.pipe( + debounceTime(500), + map(async () => { + if (!this.selectedGifFileItemList || 0 === this.selectedGifFileItemList.length) { + return; + } + + const images = []; + + for (const fileItem of this.selectedGifFileItemList) { + images.push(fileItem.dataURL); + } + + this.gifProcessing = true; + gifshot.createGIF({ + ...this.gifOption, + 'images': images + }, + (obj) => { + if (!obj.error) { + console.log('createGIF'); + this.gifProcessing = false; + this.gifDataURL = obj.image; + this.gifPreviewImg.nativeElement.src = this.gifDataURL; + this.changeDetector.detectChanges(); + } + }); + }), + ).subscribe(); + this.gifApply(); + return resolve(); + }); + } + + async onClickGIFCreate(gifExpansionPanel: MatExpansionPanel, event: Event) { + event.stopPropagation(); + + if (!this.gifDataURL || !this.selectedGifFileItemList || 0 === this.selectedGifFileItemList.length) { + return; + } + + const name = this.selectedGifFileItemList[0].name; + const listIndex = this.fileItemList.indexOf(this.selectedGifFileItemList[0]); + const gifFileItem = await FileItem.fromDataURL(name, this.gifDataURL); + + gifFileItem.addChildren(...this.selectedGifFileItemList); + + this.fileItemList.splice(listIndex, 1); + this.fileItemList.splice(listIndex, 0, gifFileItem); + + for (let index = 1; index < this.selectedGifFileItemList.length; index++) { + const selectedGifFileItem = this.selectedGifFileItemList[index]; + this.fileItemList.splice(this.fileItemList.indexOf(selectedGifFileItem), 1); + } + gifExpansionPanel.close(); + } + + gifApply() { + if (this.gifSubject) { + this.gifSubject.next(); + } + } + + onClickGIFReset() { + this.gifOption = { + ...gifOptionDefault, + }; + this.gifApply(); + } + + onChangeGIFInterval(event: MatSliderChange) { + this.gifOption.interval = event.value; + this.gifApply(); + } + + onChangeGIFFrames(event: MatSliderChange) { + this.gifOption.numFrames = event.value; + this.gifApply(); + } + + onChangeGIFDuration(event: MatSliderChange) { + this.gifOption.frameDuration = event.value; + this.gifApply(); + } + + onChangeGIFSampleInterval(event: MatSliderChange) { + this.gifOption.sampleInterval = event.value; + this.gifApply(); + } + +} diff --git a/src/modules/common/shared/directive/imgratio64.directive.ts b/src/modules/common/shared/directive/imgratio64.directive.ts new file mode 100755 index 0000000..2f0e648 --- /dev/null +++ b/src/modules/common/shared/directive/imgratio64.directive.ts @@ -0,0 +1,58 @@ +import { + Directive, + ElementRef, + AfterViewChecked, + Input, + HostListener +} from '@angular/core'; + +@Directive({ + selector: + 'app-imgratio64-height, [app-imgratio64-height], [appImgRatio64Height]' +}) +export class ImgRatio64HeightDirective implements AfterViewChecked { + // class name to match height + @Input() + appImgratio64Height: any; + + constructor(private el: ElementRef) {} + + ngAfterViewChecked() { + this.imgratio64Height(this.el.nativeElement); + } + + @HostListener('window:resize') + onResize() { + this.imgratio64Height(this.el.nativeElement); + } + + imgratio64Height(ximgratio64: HTMLElement) { + if (!ximgratio64) { + return; + } + const children = ximgratio64.getElementsByTagName('img'); + if (!children) { + return; + } + // reset all children height + /* + Array.from(children).forEach((x: HTMLElement) => { + x.style.height = 'initial'; + }); + + // gather all height + const itemHeights = Array.from(children).map( + x => x.getBoundingClientRect().height + ); + + // find max height + const maxHeight = itemHeights.reduce((prev, curr) => { + return curr > prev ? curr : prev; + }, 0); + console.log(maxHeight); + */ + + const xHeight = ximgratio64.getBoundingClientRect().width * 0.66666; + ximgratio64.style.height = `${xHeight}px`; + } +} diff --git a/src/modules/common/shared/directive/index.ts b/src/modules/common/shared/directive/index.ts new file mode 100755 index 0000000..6b750e3 --- /dev/null +++ b/src/modules/common/shared/directive/index.ts @@ -0,0 +1,8 @@ +import { MatchHeightDirective } from './match-height.directive'; +import { SquareHeightDirective } from './saqure.directive'; +import { ImgRatio64HeightDirective } from './imgratio64.directive'; +export const DIRECTIVES = [ + MatchHeightDirective, + SquareHeightDirective, + ImgRatio64HeightDirective +]; diff --git a/src/modules/common/shared/directive/match-height.directive.ts b/src/modules/common/shared/directive/match-height.directive.ts new file mode 100755 index 0000000..32f9fdc --- /dev/null +++ b/src/modules/common/shared/directive/match-height.directive.ts @@ -0,0 +1,57 @@ +import { + Directive, + ElementRef, + AfterViewChecked, + Input, + HostListener +} from '@angular/core'; + +@Directive({ + selector: 'app-match-height, [app-match-height], [appMatchHeight]' +}) +export class MatchHeightDirective implements AfterViewChecked { + // class name to match height + @Input() + appMatchHeight: any; + + constructor(private el: ElementRef) {} + + ngAfterViewChecked() { + this.matchHeight(this.el.nativeElement, this.appMatchHeight); + } + + @HostListener('window:resize') + onResize() { + this.matchHeight(this.el.nativeElement, this.appMatchHeight); + } + + matchHeight(parent: HTMLElement, className: string) { + if (!parent) { + return; + } + const children = parent.getElementsByClassName(className); + if (!children) { + return; + } + + // reset all children height + Array.from(children).forEach((x: HTMLElement) => { + x.style.height = 'initial'; + }); + + // gather all height + const itemHeights = Array.from(children).map( + x => x.getBoundingClientRect().height + ); + + // find max height + const maxHeight = itemHeights.reduce((prev, curr) => { + return curr > prev ? curr : prev; + }, 0); + + // apply max height + Array.from(children).forEach( + (x: HTMLElement) => (x.style.height = `${maxHeight}px`) + ); + } +} diff --git a/src/modules/common/shared/directive/saqure.directive.ts b/src/modules/common/shared/directive/saqure.directive.ts new file mode 100755 index 0000000..12d129e --- /dev/null +++ b/src/modules/common/shared/directive/saqure.directive.ts @@ -0,0 +1,35 @@ +import { + Directive, + ElementRef, + AfterViewChecked, + Input, + HostListener +} from '@angular/core'; + +@Directive({ + selector: 'app-square-height, [app-square-height], [appSquareHeight]' +}) +export class SquareHeightDirective implements AfterViewChecked { + // class name to match square height + @Input() + appSquareHeight: any; + + constructor(private el: ElementRef) {} + + ngAfterViewChecked() { + this.SquareHeight(this.el.nativeElement); + } + + @HostListener('window:resize') + onResize() { + this.SquareHeight(this.el.nativeElement); + } + + SquareHeight(xsquare: HTMLElement) { + if (!xsquare) { + return; + } + const xHeight = xsquare.getBoundingClientRect().width; + xsquare.style.height = `${xHeight}px`; + } +} diff --git a/src/modules/common/shared/shared-material.module.ts b/src/modules/common/shared/shared-material.module.ts new file mode 100755 index 0000000..e3c3095 --- /dev/null +++ b/src/modules/common/shared/shared-material.module.ts @@ -0,0 +1,95 @@ +/** + * 파 일 명: shared-material.module.ts + * 작성일자: 2018-12-17 + * 작 성 자: 박병준 + * 설 명: angular material ui의 module을 import한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { DragDropModule } from '@angular/cdk/drag-drop'; +import { ScrollingModule } from '@angular/cdk/scrolling'; +import { CdkTableModule } from '@angular/cdk/table'; +import { CdkTreeModule } from '@angular/cdk/tree'; +import { ScrollingModule as ExperimentalScrollingModule } from '@angular/cdk-experimental/scrolling'; +import { + MatAutocompleteModule, + MatBadgeModule, + MatBottomSheetModule, + MatButtonModule, + MatButtonToggleModule, + MatCardModule, + MatCheckboxModule, + MatChipsModule, + MatDatepickerModule, + MatDialogModule, + MatDividerModule, + MatExpansionModule, + MatGridListModule, + MatIconModule, + MatInputModule, + MatListModule, + MatMenuModule, + MatNativeDateModule, + MatPaginatorModule, + MatProgressBarModule, + MatProgressSpinnerModule, + MatRadioModule, + MatRippleModule, + MatSelectModule, + MatSidenavModule, + MatSliderModule, + MatSlideToggleModule, + MatSnackBarModule, + MatSortModule, + MatStepperModule, + MatTableModule, + MatTabsModule, + MatToolbarModule, + MatTooltipModule, + MatTreeModule, +} from '@angular/material'; + +export const MAT_MODULES = [ + CdkTableModule, + CdkTreeModule, + DragDropModule, + MatAutocompleteModule, + MatBadgeModule, + MatBottomSheetModule, + MatButtonModule, + MatButtonToggleModule, + MatCardModule, + MatCheckboxModule, + MatChipsModule, + MatStepperModule, + MatDatepickerModule, + MatDialogModule, + MatDividerModule, + MatExpansionModule, + MatGridListModule, + MatIconModule, + MatInputModule, + MatListModule, + MatMenuModule, + MatNativeDateModule, + MatPaginatorModule, + MatProgressBarModule, + MatProgressSpinnerModule, + MatRadioModule, + MatRippleModule, + MatSelectModule, + MatSidenavModule, + MatSliderModule, + MatSlideToggleModule, + MatSnackBarModule, + MatSortModule, + MatTableModule, + MatTabsModule, + MatToolbarModule, + MatTooltipModule, + MatTreeModule, + ScrollingModule, + ExperimentalScrollingModule, +]; diff --git a/src/modules/common/shared/shared-store.module.ts b/src/modules/common/shared/shared-store.module.ts new file mode 100755 index 0000000..62a298f --- /dev/null +++ b/src/modules/common/shared/shared-store.module.ts @@ -0,0 +1,17 @@ +import { NgModule, ModuleWithProviders } from '@angular/core'; +import { StoreModule } from '@ngrx/store'; +import { EffectsModule } from '@ngrx/effects'; + +import { + REDUCERS, + EFFECTS, +} from './store'; + +@NgModule({ + imports: [ + StoreModule.forFeature('shared', REDUCERS), + EffectsModule.forFeature(EFFECTS), + ], +}) +export class SharedStoreModule { +} diff --git a/src/modules/common/shared/shared.module.ts b/src/modules/common/shared/shared.module.ts new file mode 100755 index 0000000..8f52b57 --- /dev/null +++ b/src/modules/common/shared/shared.module.ts @@ -0,0 +1,64 @@ +/** + * 파 일 명: shared.module.ts + * 작성일자: 2018-12-17 + * 작 성 자: 박병준 + * 설 명: 공통으로 사용될 shard module을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { NgModule, ModuleWithProviders } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +import { TranslateModule } from '@ngx-translate/core'; + +import { MAT_MODULES } from './shared-material.module'; +import { VirtualScrollerModule } from 'ngx-virtual-scroller'; + +import { COMPONENTS } from './component'; +import { DIRECTIVES } from './directive'; +import { DIALOGS } from './dialog'; +import { ImageCropperModule } from 'ngx-image-cropper'; + +@NgModule({ + imports: [ + CommonModule, + TranslateModule, + ...MAT_MODULES, + VirtualScrollerModule, + ImageCropperModule + ], + exports: [ + ...COMPONENTS, + ...DIRECTIVES, + ...MAT_MODULES, + VirtualScrollerModule, + ], + declarations: [ + ...COMPONENTS, + ...DIRECTIVES, + ...DIALOGS, + ], + entryComponents: [ + ...DIALOGS, + ], +}) +export class SharedModule { + public static forRoot(): ModuleWithProviders { + return { + ngModule: SharedRootModule, + providers: [ + ], + }; + } +} + +@NgModule({ + imports: [ + ], + exports: [ + ], +}) +export class SharedRootModule { +} diff --git a/src/modules/common/shared/store/events/events.action.ts b/src/modules/common/shared/store/events/events.action.ts new file mode 100755 index 0000000..0cd085e --- /dev/null +++ b/src/modules/common/shared/store/events/events.action.ts @@ -0,0 +1,48 @@ +import { Action } from '@ngrx/store'; + +export enum ActionType { + Execution = '[shared.events] Execution', + ExecutionFailure = '[shared.events] ExecutionFailure', + ExecutionSuccess = '[shared.events] ExecutionSuccess', + ExecutionComplete = '[shared.events] ExecutionComplete', +} + +export interface ExecutionParam { + title?: string; + message?: string; + error?: Error; + routing?: { + query?: any; + }; +} + +export class Execution implements Action { + readonly type = ActionType.Execution; + + constructor() { } +} + +export class ExecutionFailure implements Action { + readonly type = ActionType.ExecutionFailure; + + constructor(public payload: ExecutionParam) { } +} + +export class ExecutionSuccess implements Action { + readonly type = ActionType.ExecutionSuccess; + + constructor(public payload: ExecutionParam) { } +} + +export class ExecutionComplete implements Action { + readonly type = ActionType.ExecutionComplete; + + constructor() { } +} + +export type Actions = + | Execution + | ExecutionFailure + | ExecutionSuccess + | ExecutionComplete + ; diff --git a/src/modules/common/shared/store/events/index.ts b/src/modules/common/shared/store/events/index.ts new file mode 100755 index 0000000..1bff8bd --- /dev/null +++ b/src/modules/common/shared/store/events/index.ts @@ -0,0 +1 @@ +export * from './events.action'; diff --git a/src/modules/common/shared/store/index.ts b/src/modules/common/shared/store/index.ts new file mode 100755 index 0000000..35546e5 --- /dev/null +++ b/src/modules/common/shared/store/index.ts @@ -0,0 +1,18 @@ +import { + createSelector, + createFeatureSelector, +} from '@ngrx/store'; + +import * as EventsStore from './events'; + +// tslint:disable-next-line:no-empty-interface +export interface State { +} + +export const REDUCERS = { +}; + +export const EFFECTS = [ +]; + +export const selectState = createFeatureSelector('shared'); diff --git a/src/modules/common/util/data/blob.util.ts b/src/modules/common/util/data/blob.util.ts new file mode 100755 index 0000000..50ddb42 --- /dev/null +++ b/src/modules/common/util/data/blob.util.ts @@ -0,0 +1,41 @@ +export class BlobUtil { + private static readonly dataURLPattern = /^data:((.*?)(;charset=.*?)?)(;base64)?,/; + + public static async fromDataURL(dataURL: string): Promise { + return new Promise((resolve, reject) => { + const matches = dataURL.match(BlobUtil.dataURLPattern); + if (!matches) { + return reject(new Error('invalid dataURL')); + } + + const mimeType = matches[2] ? matches[1] : 'text/plain' + (matches[3] || ';charset=utf-8'); + + const isBase64 = !!matches[4]; + const dataString = dataURL.slice(matches[0].length); + const byteString = isBase64 ? atob(dataString) : decodeURIComponent(dataString); + + const array = []; + for (let i = 0; i < byteString.length; i++) { + array.push(byteString.charCodeAt(i)); + } + + return resolve(new Blob([new Uint8Array(array)], { type: mimeType })); + }); + } + + public static toDataURL(blob: Blob): Promise { + return new Promise((resolve, reject) => { + const fileReader = new FileReader(); + fileReader.readAsDataURL(blob); + + fileReader.onload = () => { + return resolve(fileReader.result); + }; + fileReader.onerror = (event: ProgressEvent) => { + console.log('toDataURL', fileReader.error); + fileReader.abort(); + return reject(fileReader.error); + }; + }); + } +} diff --git a/src/modules/common/util/data/date.util.ts b/src/modules/common/util/data/date.util.ts new file mode 100755 index 0000000..d78ed0b --- /dev/null +++ b/src/modules/common/util/data/date.util.ts @@ -0,0 +1,9 @@ +export class DateUtil { + public static toDate(date: string | Date): Date { + return new Date(date); + } + + public static toString(date: Date): string { + return date.toUTCString(); + } +} diff --git a/src/modules/common/util/service/abstract-rest.service.ts b/src/modules/common/util/service/abstract-rest.service.ts new file mode 100755 index 0000000..a179589 --- /dev/null +++ b/src/modules/common/util/service/abstract-rest.service.ts @@ -0,0 +1,58 @@ +/** + * 파 일 명: abstract-rest.service.ts + * 작성일자: 2018-12-17 + * 작 성 자: 박병준 + * 설 명: angular에서 사용되는 RESTful Service의 공통 사항을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { Observable, throwError } from 'rxjs'; +import { AbstractService, httpOptions } from './abstract.service'; +import { Base } from '../../../../shared/common/model/base.model'; + +export abstract class AbstractRestService extends AbstractService { + public constructor(httpClient: HttpClient, apiEntryPoint: string) { + super(httpClient, apiEntryPoint); + } + + public getAll(): Observable { + return this.httpClient.get(this.apiEntryPoint); + } + + public get(id: string | number): Observable { + return this.httpClient.get(this.apiEntryPoint + `/${id}`, httpOptions); + } + + public save(model: T): Observable { + return model.id ? this.update(model) : this.add(model); + } + + public add(model: T): Observable { + return this.httpClient.put( + this.apiEntryPoint, + JSON.stringify(model), + httpOptions + ); + } + + public update(model: T): Observable { + if (!model.id) { + throwError('id of model is not valid'); + } + return this.httpClient.post( + `${this.apiEntryPoint}`, + JSON.stringify(model), + httpOptions + ); + } + + public delete(id: string | number): Observable { + return this.httpClient.delete( + `${this.apiEntryPoint}/${id}`, + httpOptions + ); + } +} diff --git a/src/modules/common/util/service/abstract.service.ts b/src/modules/common/util/service/abstract.service.ts new file mode 100755 index 0000000..186724a --- /dev/null +++ b/src/modules/common/util/service/abstract.service.ts @@ -0,0 +1,28 @@ +/** + * 파 일 명: abstract.service.ts + * 작성일자: 2018-12-17 + * 작 성 자: 박병준 + * 설 명: angular에서 사용되는 Service의 공통 사항을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { Base } from '../../../../shared/common/model/base.model'; + +export const httpOptions = { + headers: new HttpHeaders({ + 'Content-Type': 'application/json' + }) +}; + +export abstract class AbstractService { + + public constructor( + protected httpClient: HttpClient, + protected apiEntryPoint: string, + ) { + + } +} diff --git a/src/modules/common/util/ui/dialog.util.ts b/src/modules/common/util/ui/dialog.util.ts new file mode 100755 index 0000000..8509db0 --- /dev/null +++ b/src/modules/common/util/ui/dialog.util.ts @@ -0,0 +1,54 @@ +import { MatDialog, MatDialogConfig, MatBottomSheet, MatBottomSheetConfig } from '@angular/material'; +import { TemplateRef } from '@angular/core'; +import { ComponentType } from '@angular/cdk/portal'; + +import { of } from 'rxjs'; +import { take, map, catchError } from 'rxjs/operators'; + +export class UIUtil { + + public static dialogOpen( + matDialog: MatDialog, + componentOrTemplateRef: ComponentType | TemplateRef, + config?: MatDialogConfig + ): Promise { + return new Promise((resolve, reject) => { + const dialogRef = matDialog.open(componentOrTemplateRef, config); + + dialogRef.afterClosed().pipe( + take(1), + map((result) => { + return resolve(result); + }), + catchError((err) => { + return of(reject(err)); + }) + ).subscribe(); + }); + } + + public static bottomSheetOpen( + matBottomSheet: MatBottomSheet, + componentOrTemplateRef: ComponentType, + config?: MatBottomSheetConfig + ): Promise; + public static bottomSheetOpen( + matBottomSheet: MatBottomSheet, + componentOrTemplateRef: ComponentType, + config?: MatBottomSheetConfig + ): Promise { + return new Promise((resolve, reject) => { + const bottomSheetRef = matBottomSheet.open(componentOrTemplateRef, config); + + bottomSheetRef.afterDismissed().pipe( + take(1), + map((result) => { + return resolve(result); + }), + catchError((err) => { + return of(reject(err)); + }) + ).subscribe(); + }); + } +} diff --git a/src/modules/common/util/ui/route.util.ts b/src/modules/common/util/ui/route.util.ts new file mode 100755 index 0000000..4244852 --- /dev/null +++ b/src/modules/common/util/ui/route.util.ts @@ -0,0 +1,28 @@ +import { ActivatedRouteSnapshot, Router } from '@angular/router'; + + +export class RouteUtil { + + public static setShouldReuseRoute(router: Router): void { + router.routeReuseStrategy.shouldReuseRoute = RouteUtil.shouldReuseRoute; + } + + private static shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean { + return RouteUtil.getURL(curr) === RouteUtil.getURL(future); + } + + + private static getURL(route: ActivatedRouteSnapshot) { + let next = route; + let url = ''; + while (next) { + if (next.url) { + url = next.url.join('/'); + } + next = next.firstChild; + } + url = '/' + url; + return url; + } + +} diff --git a/src/modules/contents/component/index.ts b/src/modules/contents/component/index.ts new file mode 100755 index 0000000..b9c59c1 --- /dev/null +++ b/src/modules/contents/component/index.ts @@ -0,0 +1,2 @@ +export const COMPONENTS = [ +]; diff --git a/src/modules/contents/contents.module.ts b/src/modules/contents/contents.module.ts new file mode 100755 index 0000000..49c920f --- /dev/null +++ b/src/modules/contents/contents.module.ts @@ -0,0 +1,44 @@ +import { NgModule, ModuleWithProviders } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { TranslateModule } from '@ngx-translate/core'; + +import { SharedModule } from '../common/shared/shared.module'; + +import { COMPONENTS } from './component'; +import { SERVICES } from './service'; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + ReactiveFormsModule, + TranslateModule, + SharedModule, + ], + exports: [ + ...COMPONENTS + ], + declarations: [ + ...COMPONENTS + ], +}) +export class ContentsModule { + public static forRoot(): ModuleWithProviders { + return { + ngModule: ContentsRootModule, + providers: [ + ...SERVICES, + ], + }; + } +} + +@NgModule({ + imports: [ + ], + exports: [ + ], +}) +export class ContentsRootModule { +} diff --git a/src/modules/contents/service/contents.service.ts b/src/modules/contents/service/contents.service.ts new file mode 100755 index 0000000..1f56c90 --- /dev/null +++ b/src/modules/contents/service/contents.service.ts @@ -0,0 +1,95 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpParams } from '@angular/common/http'; + +import { Observable } from 'rxjs'; + +import { AbstractRestService } from '../../common/util/service/abstract-rest.service'; +import { Contents } from '../../../shared/contents/model/contents.model'; +import { environment } from '../../../environments/environment'; +import { httpOptions } from '../../common/util/service/abstract.service'; +import { ContentsRequest } from '../../../shared/contents/type/contents-request.type'; +import { ContentsRequestUser } from '../../../shared/contents/type/contents-request-user.type'; +import { ArticleType } from '../../../shared/article/type/article-type.type'; +import { User } from '../../../shared/user/model/user.model'; +import { ContentsSize } from '../../../shared/contents/model/contents-size.model'; + + +@Injectable() +export class ContentsService extends AbstractRestService { + + public constructor( + httpClient: HttpClient, + ) { + super(httpClient, environment.apiEntryPoint + '/contents'); + } + + public updateMongodbArticle(): Observable { + return this.httpClient.post( + `${this.apiEntryPoint}/mongodb/article`, + '', + httpOptions, + ); + } + + + public updateRandomArticle(size: number): Observable { + return this.httpClient.post( + `${this.apiEntryPoint}/random/article/${size}`, + '', + httpOptions, + ); + } + + public updateRandomAuthor(size: number): Observable { + return this.httpClient.post( + `${this.apiEntryPoint}/random/author/${size}`, + '', + httpOptions, + ); + } + + public getAllByOptions( + contentsRequest: ContentsRequest, + articleType: ArticleType, + user: User, + page: number, + contentsSizeList: ContentsSize[], + ): Observable { + + let url = `${this.apiEntryPoint}/${contentsRequest}`; + if (user) { + url = `${url}/${ContentsRequestUser.User}/${user.id}`; + } else { + url = `${url}/${ContentsRequestUser.Guest}`; + } + + if (articleType) { + url = `${url}/${articleType}`; + } + url = `${url}/${page}`; + + const _httpOptions = { + ...httpOptions, + params: new HttpParams().set('contentsSizeList', JSON.stringify(contentsSizeList)), + }; + + return this.httpClient.get(`${url}`, _httpOptions); + } + + public getAllBySearchArticleTag( + tagName: string, + page: number, + contentsSizeList: ContentsSize[], + ): Observable { + const _httpOptions = { + ...httpOptions, + params: new HttpParams().set('contentsSizeList', JSON.stringify(contentsSizeList)), + }; + + return this.httpClient.get(`${this.apiEntryPoint}/search/tag/${tagName}/${page}`, _httpOptions); + } + + public isEnded(contentsList: Contents[], contentsSizeList: ContentsSize[]) { + + } +} diff --git a/src/modules/contents/service/index.ts b/src/modules/contents/service/index.ts new file mode 100755 index 0000000..95b02ee --- /dev/null +++ b/src/modules/contents/service/index.ts @@ -0,0 +1,5 @@ +import { ContentsService } from './contents.service'; + +export const SERVICES = [ + ContentsService, +]; diff --git a/src/modules/illustrations/component/details/details.component.html b/src/modules/illustrations/component/details/details.component.html new file mode 100755 index 0000000..eef700f --- /dev/null +++ b/src/modules/illustrations/component/details/details.component.html @@ -0,0 +1 @@ +Details diff --git a/src/modules/illustrations/component/details/details.component.scss b/src/modules/illustrations/component/details/details.component.scss new file mode 100755 index 0000000..e3ab2cc --- /dev/null +++ b/src/modules/illustrations/component/details/details.component.scss @@ -0,0 +1,3 @@ +.example-card { + background-color: lightgrey; +} diff --git a/src/modules/illustrations/component/details/details.component.spec.ts b/src/modules/illustrations/component/details/details.component.spec.ts new file mode 100755 index 0000000..3521878 --- /dev/null +++ b/src/modules/illustrations/component/details/details.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DetailsComponent } from './details.component'; + +describe('DetailsComponent', () => { + let component: DetailsComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [DetailsComponent] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(DetailsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/modules/illustrations/component/details/details.component.ts b/src/modules/illustrations/component/details/details.component.ts new file mode 100755 index 0000000..583f7d7 --- /dev/null +++ b/src/modules/illustrations/component/details/details.component.ts @@ -0,0 +1,17 @@ +import { Component, OnInit, Input } from '@angular/core'; +import { Illustrations } from '../../../../shared/illustrations/model/illustrations.model'; + +@Component({ + selector: 'app-illustrations-details', + templateUrl: './details.component.html', + styleUrls: ['./details.component.scss'] +}) +export class DetailsComponent implements OnInit { + // tslint:disable-next-line:no-input-rename + @Input('article') + illustrations: Illustrations; + + constructor() { } + + ngOnInit() { } +} diff --git a/src/modules/illustrations/component/display/card/card.component.html b/src/modules/illustrations/component/display/card/card.component.html new file mode 100755 index 0000000..7dd5fba --- /dev/null +++ b/src/modules/illustrations/component/display/card/card.component.html @@ -0,0 +1,8 @@ + + +
+ + +
+
+
diff --git a/src/modules/illustrations/component/display/card/card.component.scss b/src/modules/illustrations/component/display/card/card.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/modules/illustrations/component/display/card/card.component.spec.ts b/src/modules/illustrations/component/display/card/card.component.spec.ts new file mode 100755 index 0000000..74236a1 --- /dev/null +++ b/src/modules/illustrations/component/display/card/card.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CardComponent } from './card.component'; + +describe('CardComponent', () => { + let component: CardComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [CardComponent] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CardComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/modules/illustrations/component/display/card/card.component.ts b/src/modules/illustrations/component/display/card/card.component.ts new file mode 100755 index 0000000..62cf717 --- /dev/null +++ b/src/modules/illustrations/component/display/card/card.component.ts @@ -0,0 +1,26 @@ +import { Component, OnInit, Input } from '@angular/core'; + +import { of } from 'rxjs'; +import { take, tap, map, catchError, finalize } from 'rxjs/operators'; + +import { Illustrations } from '../../../../../shared/illustrations/model/illustrations.model'; +import { IllustrationsService } from '../../../service/illustrations.service'; + +@Component({ + selector: 'app-illustrations-display-card', + templateUrl: './card.component.html', + styleUrls: ['./card.component.scss'] +}) +export class CardComponent implements OnInit { + // tslint:disable-next-line:no-input-rename + @Input('article') + illustrations: Illustrations; + + constructor(private illustrationsService: IllustrationsService) { } + + ngOnInit() { + if (!this.illustrations) { + this.illustrations = {}; + } + } +} diff --git a/src/modules/illustrations/component/display/cover/cover.component.html b/src/modules/illustrations/component/display/cover/cover.component.html new file mode 100755 index 0000000..0c253f9 --- /dev/null +++ b/src/modules/illustrations/component/display/cover/cover.component.html @@ -0,0 +1,2 @@ + + diff --git a/src/modules/illustrations/component/display/cover/cover.component.scss b/src/modules/illustrations/component/display/cover/cover.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/modules/illustrations/component/display/cover/cover.component.spec.ts b/src/modules/illustrations/component/display/cover/cover.component.spec.ts new file mode 100755 index 0000000..dc90ccd --- /dev/null +++ b/src/modules/illustrations/component/display/cover/cover.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CoverComponent } from './cover.component'; + +describe('CoverComponent', () => { + let component: CoverComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [CoverComponent] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CoverComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/modules/illustrations/component/display/cover/cover.component.ts b/src/modules/illustrations/component/display/cover/cover.component.ts new file mode 100755 index 0000000..5591c05 --- /dev/null +++ b/src/modules/illustrations/component/display/cover/cover.component.ts @@ -0,0 +1,38 @@ +import { Component, OnInit, Input } from '@angular/core'; + +import { of } from 'rxjs'; +import { take, tap, map, catchError, finalize } from 'rxjs/operators'; + +import { Illustrations } from '../../../../../shared/illustrations/model/illustrations.model'; +import { IllustrationsService } from '../../../service/illustrations.service'; +import { IllustrationsAttachments } from '../../../../../shared/illustrations/model/illustrations-attachments.model'; +import { ArticleImage } from 'src/shared/article/type/article-image.type'; + +@Component({ + selector: 'app-illustrations-display-cover', + templateUrl: './cover.component.html', + styleUrls: ['./cover.component.scss'] +}) +export class CoverComponent implements OnInit { + @Input() + illustrations: Illustrations; + + illustrationsAttachments: IllustrationsAttachments; + + constructor(private illustrationsService: IllustrationsService) { } + + ngOnInit() { + if (this.illustrations && this.illustrations.attachmentsList && 0 < this.illustrations.attachmentsList.length) { + for (let index = 0; index < this.illustrations.attachmentsList.length; index++) { + const illustrationsAttachments = this.illustrations.attachmentsList[index]; + if (ArticleImage.Cover === illustrationsAttachments.imageType) { + this.illustrationsAttachments = illustrationsAttachments; + break; + } + } + if (!this.illustrationsAttachments) { + this.illustrationsAttachments = this.illustrations.attachmentsList[0]; + } + } + } +} diff --git a/src/modules/illustrations/component/display/index.ts b/src/modules/illustrations/component/display/index.ts new file mode 100755 index 0000000..58eb4c3 --- /dev/null +++ b/src/modules/illustrations/component/display/index.ts @@ -0,0 +1,9 @@ +import { CardComponent } from './card/card.component'; +import { CoverComponent } from './cover/cover.component'; +import { TileComponent } from './tile/tile.component'; + +export const DISPLAY_COMPONENTS = [ + CardComponent, + CoverComponent, + TileComponent, +]; diff --git a/src/modules/illustrations/component/display/tile/tile.component.html b/src/modules/illustrations/component/display/tile/tile.component.html new file mode 100755 index 0000000..092aea1 --- /dev/null +++ b/src/modules/illustrations/component/display/tile/tile.component.html @@ -0,0 +1,8 @@ + + +
+ + +
+
+
diff --git a/src/modules/illustrations/component/display/tile/tile.component.scss b/src/modules/illustrations/component/display/tile/tile.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/modules/illustrations/component/display/tile/tile.component.spec.ts b/src/modules/illustrations/component/display/tile/tile.component.spec.ts new file mode 100755 index 0000000..5df480a --- /dev/null +++ b/src/modules/illustrations/component/display/tile/tile.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TileComponent } from './tile.component'; + +describe('TileComponent', () => { + let component: TileComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [TileComponent] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(TileComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/modules/illustrations/component/display/tile/tile.component.ts b/src/modules/illustrations/component/display/tile/tile.component.ts new file mode 100755 index 0000000..bb811f1 --- /dev/null +++ b/src/modules/illustrations/component/display/tile/tile.component.ts @@ -0,0 +1,26 @@ +import { Component, OnInit, Input } from '@angular/core'; + +import { of } from 'rxjs'; +import { take, tap, map, catchError, finalize } from 'rxjs/operators'; + +import { Illustrations } from '../../../../../shared/illustrations/model/illustrations.model'; +import { IllustrationsService } from '../../../service/illustrations.service'; + +@Component({ + selector: 'app-illustrations-display-tile', + templateUrl: './tile.component.html', + styleUrls: ['./tile.component.scss'] +}) +export class TileComponent implements OnInit { + // tslint:disable-next-line:no-input-rename + @Input('article') + illustrations: Illustrations; + + constructor(private illustrationsService: IllustrationsService) { } + + ngOnInit() { + if (!this.illustrations) { + this.illustrations = {}; + } + } +} diff --git a/src/modules/illustrations/component/index.ts b/src/modules/illustrations/component/index.ts new file mode 100755 index 0000000..ee8c913 --- /dev/null +++ b/src/modules/illustrations/component/index.ts @@ -0,0 +1,11 @@ +import { DetailsComponent } from './details/details.component'; +import { SeriesComponent } from './series/series.component'; +import { WriteComponent } from './write/write.component'; +import { DISPLAY_COMPONENTS } from './display'; + +export const COMPONENTS = [ + DetailsComponent, + SeriesComponent, + WriteComponent, + ...DISPLAY_COMPONENTS, +]; diff --git a/src/modules/illustrations/component/series/series.component.html b/src/modules/illustrations/component/series/series.component.html new file mode 100755 index 0000000..4ab91d3 --- /dev/null +++ b/src/modules/illustrations/component/series/series.component.html @@ -0,0 +1,34 @@ +
+
+

시리즈

+ + +
+ + + {{title.value?.length || 0}}/32 + + + + + {{desc.value?.length || 0}}/300 + +
+
+ + + + None + series 1 + series 2 + series 3 + + + + + + + +
+
+
diff --git a/src/modules/illustrations/component/series/series.component.scss b/src/modules/illustrations/component/series/series.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/modules/illustrations/component/series/series.component.spec.ts b/src/modules/illustrations/component/series/series.component.spec.ts new file mode 100755 index 0000000..38e3216 --- /dev/null +++ b/src/modules/illustrations/component/series/series.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SeriesComponent } from './series.component'; + +describe('SeriesComponent', () => { + let component: SeriesComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [SeriesComponent] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SeriesComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/modules/illustrations/component/series/series.component.ts b/src/modules/illustrations/component/series/series.component.ts new file mode 100755 index 0000000..ead4526 --- /dev/null +++ b/src/modules/illustrations/component/series/series.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-illustrations-series', + templateUrl: './series.component.html', + styleUrls: ['./series.component.scss'] +}) +export class SeriesComponent implements OnInit { + + constructor() { } + + ngOnInit() { + } + +} diff --git a/src/modules/illustrations/component/write/write.component.html b/src/modules/illustrations/component/write/write.component.html new file mode 100755 index 0000000..cd1fca5 --- /dev/null +++ b/src/modules/illustrations/component/write/write.component.html @@ -0,0 +1,9 @@ + + +

Illustrations 업로드

+
+ + + Illustrations + +
diff --git a/src/modules/illustrations/component/write/write.component.scss b/src/modules/illustrations/component/write/write.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/modules/illustrations/component/write/write.component.spec.ts b/src/modules/illustrations/component/write/write.component.spec.ts new file mode 100755 index 0000000..a9636f8 --- /dev/null +++ b/src/modules/illustrations/component/write/write.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { WriteComponent } from './write.component'; + +describe('WriteComponent', () => { + let component: WriteComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [WriteComponent] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(WriteComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/modules/illustrations/component/write/write.component.ts b/src/modules/illustrations/component/write/write.component.ts new file mode 100755 index 0000000..d018b73 --- /dev/null +++ b/src/modules/illustrations/component/write/write.component.ts @@ -0,0 +1,19 @@ +import { Component, OnInit, Input } from '@angular/core'; +import { Illustrations } from '../../../../shared/illustrations/model/illustrations.model'; + +@Component({ + selector: 'app-illustrations-write', + templateUrl: './write.component.html', + styleUrls: ['./write.component.scss'] +}) +export class WriteComponent implements OnInit { + // tslint:disable-next-line:no-input-rename + @Input('article') + illustrations: Illustrations; + + constructor() { } + + ngOnInit() { + } + +} diff --git a/src/modules/illustrations/illustrations.module.ts b/src/modules/illustrations/illustrations.module.ts new file mode 100755 index 0000000..c890941 --- /dev/null +++ b/src/modules/illustrations/illustrations.module.ts @@ -0,0 +1,51 @@ +import { NgModule, ModuleWithProviders } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { TranslateModule } from '@ngx-translate/core'; + +import { SharedModule } from '../common/shared/shared.module'; + +import { ArticleModule } from '../article/article.module'; +import { AttachmentsModule } from '../attachments/attachments.module'; +import { TagModule } from '../tag/tag.module'; + +import { COMPONENTS } from './component'; +import { SERVICES } from './service'; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + ReactiveFormsModule, + TranslateModule, + SharedModule, + ArticleModule, + AttachmentsModule, + TagModule, + ], + exports: [ + ...COMPONENTS + ], + declarations: [ + ...COMPONENTS + ], +}) +export class IllustrationsModule { + public static forRoot(): ModuleWithProviders { + return { + ngModule: IllustrationsRootModule, + providers: [ + ...SERVICES, + ], + }; + } +} + +@NgModule({ + imports: [ + ], + exports: [ + ], +}) +export class IllustrationsRootModule { +} diff --git a/src/modules/illustrations/service/illustrations.service.ts b/src/modules/illustrations/service/illustrations.service.ts new file mode 100755 index 0000000..46bff1c --- /dev/null +++ b/src/modules/illustrations/service/illustrations.service.ts @@ -0,0 +1,18 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; + +import { ArticleService } from '../../article/service/article.service'; +import { Illustrations } from '../../../shared/illustrations/model/illustrations.model'; +import { environment } from '../../../environments/environment'; + +@Injectable() +export class IllustrationsService extends ArticleService { + + public constructor( + httpClient: HttpClient, + ) { + super(httpClient); + this.apiEntryPoint = environment.apiEntryPoint + '/illustrations'; + } + +} diff --git a/src/modules/illustrations/service/index.ts b/src/modules/illustrations/service/index.ts new file mode 100755 index 0000000..2670d0a --- /dev/null +++ b/src/modules/illustrations/service/index.ts @@ -0,0 +1,5 @@ +import { IllustrationsService } from './illustrations.service'; + +export const SERVICES = [ + IllustrationsService, +]; diff --git a/src/modules/meta/component/index.ts b/src/modules/meta/component/index.ts new file mode 100755 index 0000000..b9c59c1 --- /dev/null +++ b/src/modules/meta/component/index.ts @@ -0,0 +1,2 @@ +export const COMPONENTS = [ +]; diff --git a/src/modules/meta/meta-store.module.ts b/src/modules/meta/meta-store.module.ts new file mode 100755 index 0000000..e68b639 --- /dev/null +++ b/src/modules/meta/meta-store.module.ts @@ -0,0 +1,17 @@ +import { NgModule, ModuleWithProviders } from '@angular/core'; +import { StoreModule } from '@ngrx/store'; +import { EffectsModule } from '@ngrx/effects'; + +import { + REDUCERS, + EFFECTS, +} from './store'; + +@NgModule({ + imports: [ + StoreModule.forFeature('meta', REDUCERS), + EffectsModule.forFeature(EFFECTS), + ], +}) +export class MetaStoreModule { +} diff --git a/src/modules/meta/meta.module.ts b/src/modules/meta/meta.module.ts new file mode 100755 index 0000000..519d6ae --- /dev/null +++ b/src/modules/meta/meta.module.ts @@ -0,0 +1,46 @@ +import { NgModule, ModuleWithProviders } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { TranslateModule } from '@ngx-translate/core'; + +import { SharedModule } from '../common/shared/shared.module'; +import { MetaStoreModule } from './meta-store.module'; + +import { COMPONENTS } from './component'; +import { SERVICES } from './service'; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + ReactiveFormsModule, + TranslateModule, + SharedModule, + ], + exports: [ + ...COMPONENTS + ], + declarations: [ + ...COMPONENTS + ], +}) +export class MetaModule { + public static forRoot(): ModuleWithProviders { + return { + ngModule: MetaRootModule, + providers: [ + ...SERVICES, + ], + }; + } +} + +@NgModule({ + imports: [ + MetaStoreModule, + ], + exports: [ + ], +}) +export class MetaRootModule { +} diff --git a/src/modules/meta/service/index.ts b/src/modules/meta/service/index.ts new file mode 100755 index 0000000..a884c55 --- /dev/null +++ b/src/modules/meta/service/index.ts @@ -0,0 +1,5 @@ +import { MetaCommentsTagService } from './meta-comments-tag.service'; + +export const SERVICES = [ + MetaCommentsTagService, +]; diff --git a/src/modules/meta/service/meta-comments-tag.service.ts b/src/modules/meta/service/meta-comments-tag.service.ts new file mode 100755 index 0000000..5063024 --- /dev/null +++ b/src/modules/meta/service/meta-comments-tag.service.ts @@ -0,0 +1,23 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpParams } from '@angular/common/http'; + +import { Observable } from 'rxjs'; + +import { AbstractRestService } from '../../common/util/service/abstract-rest.service'; +import { Contents } from '../../../shared/contents/model/contents.model'; +import { environment } from '../../../environments/environment'; +import { httpOptions } from '../../common/util/service/abstract.service'; +import { User } from '../../../shared/user/model/user.model'; +import { MetaCommentsTag } from '../../../shared/meta/model/meta-comments-tag.model'; + + +@Injectable() +export class MetaCommentsTagService extends AbstractRestService { + + public constructor( + httpClient: HttpClient, + ) { + super(httpClient, environment.apiEntryPoint + '/meta/comments_tag'); + } + +} diff --git a/src/modules/meta/store/comments-tag/comments-tag.action.ts b/src/modules/meta/store/comments-tag/comments-tag.action.ts new file mode 100755 index 0000000..370e245 --- /dev/null +++ b/src/modules/meta/store/comments-tag/comments-tag.action.ts @@ -0,0 +1,38 @@ +import { Action } from '@ngrx/store'; +import { MetaCommentsTag } from '../../../../shared/meta/model/meta-comments-tag.model'; +import { MetaCommentsTagTarget } from '../../../../shared/meta/type/meta-comments-tag-target.type'; + +export enum ActionType { + Load = '[meta.comments-tag] Load', + + LoadSuccess = '[account.auth] LoadSuccess', + LoadFailure = '[account.auth] LoadFailure', +} + +export class Load implements Action { + readonly type = ActionType.Load; + + constructor() { } +} + +export class LoadSuccess implements Action { + readonly type = ActionType.LoadSuccess; + + constructor(public payload: { + commentsTagList: MetaCommentsTag[], + commentsTagListMap: Map, + }) { } +} + +export class LoadFailure implements Action { + readonly type = ActionType.LoadFailure; + + constructor() { } +} + + +export type Actions = + | Load + | LoadSuccess + | LoadFailure + ; diff --git a/src/modules/meta/store/comments-tag/comments-tag.effect.ts b/src/modules/meta/store/comments-tag/comments-tag.effect.ts new file mode 100755 index 0000000..7298c89 --- /dev/null +++ b/src/modules/meta/store/comments-tag/comments-tag.effect.ts @@ -0,0 +1,52 @@ +import { Injectable } from '@angular/core'; + +import { Effect, Actions, ofType } from '@ngrx/effects'; +import { map, tap, exhaustMap, catchError } from 'rxjs/operators'; + +import { + Load, + LoadSuccess, + LoadFailure, + ActionType, +} from './comments-tag.action'; +import { MetaCommentsTagService } from '../../service/meta-comments-tag.service'; +import { MetaCommentsTag } from '../../../../shared/meta/model/meta-comments-tag.model'; +import { of } from 'rxjs'; +import { MetaCommentsTagTarget } from 'src/shared/meta/type/meta-comments-tag-target.type'; + +@Injectable() +export class Effects { + constructor( + private actions$: Actions, + private metaCommentsTagService: MetaCommentsTagService, + ) { } + + @Effect() + load$ = this.actions$.pipe( + ofType(ActionType.Load), + exhaustMap(() => { + return this.metaCommentsTagService.getAll().pipe( + map((commentsTagList: MetaCommentsTag[]) => { + const commentsTagListMap: Map = new Map(); + for (const commentsTag of commentsTagList) { + let list = commentsTagListMap.get(commentsTag.type); + if (!list) { + list = []; + commentsTagListMap.set(commentsTag.type, list); + } + list.push(commentsTag); + } + + return new LoadSuccess({ + commentsTagList: commentsTagList, + commentsTagListMap: commentsTagListMap, + }); + }), + catchError(err => { + console.log(err); + return of(new LoadFailure()); + }) + ); + }) + ); +} diff --git a/src/modules/meta/store/comments-tag/comments-tag.reducer.ts b/src/modules/meta/store/comments-tag/comments-tag.reducer.ts new file mode 100755 index 0000000..a48baec --- /dev/null +++ b/src/modules/meta/store/comments-tag/comments-tag.reducer.ts @@ -0,0 +1,27 @@ +import { Actions, ActionType } from './comments-tag.action'; + +import { State, initialState } from './comments-tag.state'; + +export function reducer(state: State = initialState, action: Actions): State { + switch (action.type) { + case ActionType.LoadSuccess: { + return { + ...state, + commentsTagList: action.payload.commentsTagList, + commentsTagListMap: action.payload.commentsTagListMap, + }; + } + + case ActionType.LoadFailure: { + return { + ...state, + commentsTagList: null, + commentsTagListMap: null, + }; + } + + default: { + return state; + } + } +} diff --git a/src/modules/meta/store/comments-tag/comments-tag.state.ts b/src/modules/meta/store/comments-tag/comments-tag.state.ts new file mode 100755 index 0000000..3cff273 --- /dev/null +++ b/src/modules/meta/store/comments-tag/comments-tag.state.ts @@ -0,0 +1,20 @@ +import { Selector, createSelector } from '@ngrx/store'; +import { MetaCommentsTag } from '../../../../shared/meta/model/meta-comments-tag.model'; +import { MetaCommentsTagTarget } from '../../../../shared/meta/type/meta-comments-tag-target.type'; + +export interface State { + commentsTagList: MetaCommentsTag[] | null; + commentsTagListMap: Map | null; +} + +export const initialState: State = { + commentsTagList: null, + commentsTagListMap: null, +}; + +export function getSelectors(selector: Selector) { + return { + selectCommentsTagList: createSelector(selector, (state: State) => state.commentsTagList), + selectCommentsTagListMap: createSelector(selector, (state: State) => state.commentsTagListMap), + }; +} diff --git a/src/modules/meta/store/comments-tag/index.ts b/src/modules/meta/store/comments-tag/index.ts new file mode 100755 index 0000000..e34ce16 --- /dev/null +++ b/src/modules/meta/store/comments-tag/index.ts @@ -0,0 +1,4 @@ +export * from './comments-tag.action'; +export * from './comments-tag.effect'; +export * from './comments-tag.reducer'; +export * from './comments-tag.state'; diff --git a/src/modules/meta/store/index.ts b/src/modules/meta/store/index.ts new file mode 100755 index 0000000..eca1470 --- /dev/null +++ b/src/modules/meta/store/index.ts @@ -0,0 +1,25 @@ +import { + createSelector, + createFeatureSelector, +} from '@ngrx/store'; + +import * as CommentsTagStore from './comments-tag'; + +export interface State { + commentsTag: CommentsTagStore.State; +} + +export const REDUCERS = { + commentsTag: CommentsTagStore.reducer, +}; + +export const EFFECTS = [ + CommentsTagStore.Effects, +]; + +export const selectState = createFeatureSelector('meta'); + +export const CommentsTagSelector = CommentsTagStore.getSelectors(createSelector( + selectState, + (state: State) => state.commentsTag, +)); diff --git a/src/modules/novel/component/details/details.component.html b/src/modules/novel/component/details/details.component.html new file mode 100755 index 0000000..eef700f --- /dev/null +++ b/src/modules/novel/component/details/details.component.html @@ -0,0 +1 @@ +Details diff --git a/src/modules/novel/component/details/details.component.scss b/src/modules/novel/component/details/details.component.scss new file mode 100755 index 0000000..e3ab2cc --- /dev/null +++ b/src/modules/novel/component/details/details.component.scss @@ -0,0 +1,3 @@ +.example-card { + background-color: lightgrey; +} diff --git a/src/modules/novel/component/details/details.component.spec.ts b/src/modules/novel/component/details/details.component.spec.ts new file mode 100755 index 0000000..3521878 --- /dev/null +++ b/src/modules/novel/component/details/details.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DetailsComponent } from './details.component'; + +describe('DetailsComponent', () => { + let component: DetailsComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [DetailsComponent] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(DetailsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/modules/novel/component/details/details.component.ts b/src/modules/novel/component/details/details.component.ts new file mode 100755 index 0000000..f065375 --- /dev/null +++ b/src/modules/novel/component/details/details.component.ts @@ -0,0 +1,17 @@ +import { Component, OnInit, Input } from '@angular/core'; +import { Novel } from '../../../../shared/novel/model/novel.model'; + +@Component({ + selector: 'app-novel-details', + templateUrl: './details.component.html', + styleUrls: ['./details.component.scss'] +}) +export class DetailsComponent implements OnInit { + // tslint:disable-next-line:no-input-rename + @Input('article') + novel: Novel; + + constructor() { } + + ngOnInit() { } +} diff --git a/src/modules/novel/component/display/card/card.component.html b/src/modules/novel/component/display/card/card.component.html new file mode 100755 index 0000000..70e3b7a --- /dev/null +++ b/src/modules/novel/component/display/card/card.component.html @@ -0,0 +1,8 @@ + + +
+ + +
+
+
diff --git a/src/modules/novel/component/display/card/card.component.scss b/src/modules/novel/component/display/card/card.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/modules/novel/component/display/card/card.component.spec.ts b/src/modules/novel/component/display/card/card.component.spec.ts new file mode 100755 index 0000000..74236a1 --- /dev/null +++ b/src/modules/novel/component/display/card/card.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CardComponent } from './card.component'; + +describe('CardComponent', () => { + let component: CardComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [CardComponent] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CardComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/modules/novel/component/display/card/card.component.ts b/src/modules/novel/component/display/card/card.component.ts new file mode 100755 index 0000000..50d7d3c --- /dev/null +++ b/src/modules/novel/component/display/card/card.component.ts @@ -0,0 +1,26 @@ +import { Component, OnInit, Input } from '@angular/core'; + +import { of } from 'rxjs'; +import { take, tap, map, catchError, finalize } from 'rxjs/operators'; + +import { Novel } from '../../../../../shared/novel/model/novel.model'; +import { NovelService } from '../../../service/novel.service'; + +@Component({ + selector: 'app-novel-display-card', + templateUrl: './card.component.html', + styleUrls: ['./card.component.scss'] +}) +export class CardComponent implements OnInit { + // tslint:disable-next-line:no-input-rename + @Input('article') + novel: Novel; + + constructor(private novelService: NovelService) { } + + ngOnInit() { + if (!this.novel) { + this.novel = {}; + } + } +} diff --git a/src/modules/novel/component/display/cover/cover.component.html b/src/modules/novel/component/display/cover/cover.component.html new file mode 100755 index 0000000..38caca9 --- /dev/null +++ b/src/modules/novel/component/display/cover/cover.component.html @@ -0,0 +1,2 @@ + + diff --git a/src/modules/novel/component/display/cover/cover.component.scss b/src/modules/novel/component/display/cover/cover.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/modules/novel/component/display/cover/cover.component.spec.ts b/src/modules/novel/component/display/cover/cover.component.spec.ts new file mode 100755 index 0000000..dc90ccd --- /dev/null +++ b/src/modules/novel/component/display/cover/cover.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CoverComponent } from './cover.component'; + +describe('CoverComponent', () => { + let component: CoverComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [CoverComponent] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CoverComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/modules/novel/component/display/cover/cover.component.ts b/src/modules/novel/component/display/cover/cover.component.ts new file mode 100755 index 0000000..c32b741 --- /dev/null +++ b/src/modules/novel/component/display/cover/cover.component.ts @@ -0,0 +1,38 @@ +import { Component, OnInit, Input } from '@angular/core'; + +import { of } from 'rxjs'; +import { take, tap, map, catchError, finalize } from 'rxjs/operators'; + +import { Novel } from '../../../../../shared/novel/model/novel.model'; +import { NovelService } from '../../../service/novel.service'; +import { NovelAttachments } from '../../../../../shared/novel/model/novel-attachments.model'; +import { ArticleImage } from 'src/shared/article/type/article-image.type'; + +@Component({ + selector: 'app-novel-display-cover', + templateUrl: './cover.component.html', + styleUrls: ['./cover.component.scss'] +}) +export class CoverComponent implements OnInit { + @Input() + novel: Novel; + + novelAttachments: NovelAttachments; + + constructor(private novelService: NovelService) { } + + ngOnInit() { + if (this.novel && this.novel.attachmentsList && 0 < this.novel.attachmentsList.length) { + for (let index = 0; index < this.novel.attachmentsList.length; index++) { + const novelAttachments = this.novel.attachmentsList[index]; + if (ArticleImage.Cover === novelAttachments.imageType) { + this.novelAttachments = novelAttachments; + break; + } + } + if (!this.novelAttachments) { + this.novelAttachments = this.novel.attachmentsList[0]; + } + } + } +} diff --git a/src/modules/novel/component/display/index.ts b/src/modules/novel/component/display/index.ts new file mode 100755 index 0000000..58eb4c3 --- /dev/null +++ b/src/modules/novel/component/display/index.ts @@ -0,0 +1,9 @@ +import { CardComponent } from './card/card.component'; +import { CoverComponent } from './cover/cover.component'; +import { TileComponent } from './tile/tile.component'; + +export const DISPLAY_COMPONENTS = [ + CardComponent, + CoverComponent, + TileComponent, +]; diff --git a/src/modules/novel/component/display/tile/tile.component.html b/src/modules/novel/component/display/tile/tile.component.html new file mode 100755 index 0000000..deba612 --- /dev/null +++ b/src/modules/novel/component/display/tile/tile.component.html @@ -0,0 +1,8 @@ + + +
+ + +
+
+
diff --git a/src/modules/novel/component/display/tile/tile.component.scss b/src/modules/novel/component/display/tile/tile.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/modules/novel/component/display/tile/tile.component.spec.ts b/src/modules/novel/component/display/tile/tile.component.spec.ts new file mode 100755 index 0000000..5df480a --- /dev/null +++ b/src/modules/novel/component/display/tile/tile.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TileComponent } from './tile.component'; + +describe('TileComponent', () => { + let component: TileComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [TileComponent] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(TileComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/modules/novel/component/display/tile/tile.component.ts b/src/modules/novel/component/display/tile/tile.component.ts new file mode 100755 index 0000000..06773c2 --- /dev/null +++ b/src/modules/novel/component/display/tile/tile.component.ts @@ -0,0 +1,26 @@ +import { Component, OnInit, Input } from '@angular/core'; + +import { of } from 'rxjs'; +import { take, tap, map, catchError, finalize } from 'rxjs/operators'; + +import { Novel } from '../../../../../shared/novel/model/novel.model'; +import { NovelService } from '../../../service/novel.service'; + +@Component({ + selector: 'app-novel-display-tile', + templateUrl: './tile.component.html', + styleUrls: ['./tile.component.scss'] +}) +export class TileComponent implements OnInit { + // tslint:disable-next-line:no-input-rename + @Input('article') + novel: Novel; + + constructor(private novelService: NovelService) { } + + ngOnInit() { + if (!this.novel) { + this.novel = {}; + } + } +} diff --git a/src/modules/novel/component/index.ts b/src/modules/novel/component/index.ts new file mode 100755 index 0000000..ee8c913 --- /dev/null +++ b/src/modules/novel/component/index.ts @@ -0,0 +1,11 @@ +import { DetailsComponent } from './details/details.component'; +import { SeriesComponent } from './series/series.component'; +import { WriteComponent } from './write/write.component'; +import { DISPLAY_COMPONENTS } from './display'; + +export const COMPONENTS = [ + DetailsComponent, + SeriesComponent, + WriteComponent, + ...DISPLAY_COMPONENTS, +]; diff --git a/src/modules/novel/component/series/series.component.html b/src/modules/novel/component/series/series.component.html new file mode 100755 index 0000000..7ceb394 --- /dev/null +++ b/src/modules/novel/component/series/series.component.html @@ -0,0 +1,33 @@ +
+

시리즈

+ + +
+ + + {{title.value?.length || 0}}/32 + + + + + {{desc.value?.length || 0}}/300 + +
+
+ + + + None + series 1 + series 2 + series 3 + + + + + + + + +
+
diff --git a/src/modules/novel/component/series/series.component.scss b/src/modules/novel/component/series/series.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/modules/novel/component/series/series.component.spec.ts b/src/modules/novel/component/series/series.component.spec.ts new file mode 100755 index 0000000..38e3216 --- /dev/null +++ b/src/modules/novel/component/series/series.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SeriesComponent } from './series.component'; + +describe('SeriesComponent', () => { + let component: SeriesComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [SeriesComponent] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SeriesComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/modules/novel/component/series/series.component.ts b/src/modules/novel/component/series/series.component.ts new file mode 100755 index 0000000..577bd66 --- /dev/null +++ b/src/modules/novel/component/series/series.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-novel-series', + templateUrl: './series.component.html', + styleUrls: ['./series.component.scss'] +}) +export class SeriesComponent implements OnInit { + + constructor() { } + + ngOnInit() { + } + +} diff --git a/src/modules/novel/component/write/write.component.html b/src/modules/novel/component/write/write.component.html new file mode 100755 index 0000000..7ceca81 --- /dev/null +++ b/src/modules/novel/component/write/write.component.html @@ -0,0 +1,9 @@ + + +

Novel 업로드

+
+ + + + +
diff --git a/src/modules/novel/component/write/write.component.scss b/src/modules/novel/component/write/write.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/modules/novel/component/write/write.component.spec.ts b/src/modules/novel/component/write/write.component.spec.ts new file mode 100755 index 0000000..a9636f8 --- /dev/null +++ b/src/modules/novel/component/write/write.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { WriteComponent } from './write.component'; + +describe('WriteComponent', () => { + let component: WriteComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [WriteComponent] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(WriteComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/modules/novel/component/write/write.component.ts b/src/modules/novel/component/write/write.component.ts new file mode 100755 index 0000000..5a962cd --- /dev/null +++ b/src/modules/novel/component/write/write.component.ts @@ -0,0 +1,19 @@ +import { Component, OnInit, Input } from '@angular/core'; +import { Novel } from '../../../../shared/novel/model/novel.model'; + +@Component({ + selector: 'app-novel-write', + templateUrl: './write.component.html', + styleUrls: ['./write.component.scss'] +}) +export class WriteComponent implements OnInit { + // tslint:disable-next-line:no-input-rename + @Input('article') + novel: Novel; + + constructor() { } + + ngOnInit() { + } + +} diff --git a/src/modules/novel/novel.module.ts b/src/modules/novel/novel.module.ts new file mode 100755 index 0000000..7345aaa --- /dev/null +++ b/src/modules/novel/novel.module.ts @@ -0,0 +1,51 @@ +import { NgModule, ModuleWithProviders } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { TranslateModule } from '@ngx-translate/core'; + +import { SharedModule } from '../common/shared/shared.module'; + +import { ArticleModule } from '../article/article.module'; +import { AttachmentsModule } from '../attachments/attachments.module'; +import { TagModule } from '../tag/tag.module'; + +import { COMPONENTS } from './component'; +import { SERVICES } from './service'; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + ReactiveFormsModule, + TranslateModule, + SharedModule, + ArticleModule, + AttachmentsModule, + TagModule, + ], + exports: [ + ...COMPONENTS + ], + declarations: [ + ...COMPONENTS + ], +}) +export class NovelModule { + public static forRoot(): ModuleWithProviders { + return { + ngModule: NovelRootModule, + providers: [ + ...SERVICES, + ], + }; + } +} + +@NgModule({ + imports: [ + ], + exports: [ + ], +}) +export class NovelRootModule { +} diff --git a/src/modules/novel/service/index.ts b/src/modules/novel/service/index.ts new file mode 100755 index 0000000..0a65761 --- /dev/null +++ b/src/modules/novel/service/index.ts @@ -0,0 +1,5 @@ +import { NovelService } from './novel.service'; + +export const SERVICES = [ + NovelService, +]; diff --git a/src/modules/novel/service/novel.service.ts b/src/modules/novel/service/novel.service.ts new file mode 100755 index 0000000..4ec746b --- /dev/null +++ b/src/modules/novel/service/novel.service.ts @@ -0,0 +1,18 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; + +import { ArticleService } from '../../article/service/article.service'; +import { Novel } from '../../../shared/novel/model/novel.model'; +import { environment } from '../../../environments/environment'; + +@Injectable() +export class NovelService extends ArticleService { + + public constructor( + httpClient: HttpClient, + ) { + super(httpClient); + this.apiEntryPoint = environment.apiEntryPoint + '/novel'; + } + +} diff --git a/src/modules/tag/component/box/box.component.html b/src/modules/tag/component/box/box.component.html new file mode 100755 index 0000000..17c5f6f --- /dev/null +++ b/src/modules/tag/component/box/box.component.html @@ -0,0 +1,7 @@ +
+ +
+ diff --git a/src/modules/tag/component/box/box.component.scss b/src/modules/tag/component/box/box.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/modules/tag/component/box/box.component.spec.ts b/src/modules/tag/component/box/box.component.spec.ts new file mode 100755 index 0000000..9f273dc --- /dev/null +++ b/src/modules/tag/component/box/box.component.spec.ts @@ -0,0 +1,24 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { BoxComponent } from './box.component'; + +describe('BoxComponent', () => { + let component: BoxComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [BoxComponent] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(BoxComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/modules/tag/component/box/box.component.ts b/src/modules/tag/component/box/box.component.ts new file mode 100755 index 0000000..1a7449e --- /dev/null +++ b/src/modules/tag/component/box/box.component.ts @@ -0,0 +1,37 @@ +/** + * 파 일 명: box.component.ts + * 작성일자: 2018-12-21 + * 작 성 자: 김태경 + * 설 명: Tag box component를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; +import { Tag } from '../../../../shared/tag/model/tag.model'; + +@Component({ + selector: 'app-tag-box', + templateUrl: './box.component.html', + styleUrls: ['./box.component.scss'] +}) +export class BoxComponent implements OnInit { + @Input() + tagList: Tag[]; + + @Output() + searchTag = new EventEmitter(); + + constructor() {} + + ngOnInit() {} + + clickSearch(title: string) { + if (this.searchTag) { + console.log('clickSearch'); + console.log('title >> ' + title); + this.searchTag.emit(title); + } + } +} diff --git a/src/modules/tag/component/index.ts b/src/modules/tag/component/index.ts new file mode 100755 index 0000000..2aa82f8 --- /dev/null +++ b/src/modules/tag/component/index.ts @@ -0,0 +1,7 @@ +import { BoxComponent } from './box/box.component'; +import { WriteComponent } from './write/write.component'; + +export const COMPONENTS = [ + BoxComponent, + WriteComponent, +]; diff --git a/src/modules/tag/component/write/write.component.html b/src/modules/tag/component/write/write.component.html new file mode 100755 index 0000000..9bc3856 --- /dev/null +++ b/src/modules/tag/component/write/write.component.html @@ -0,0 +1,18 @@ + + + + {{ tag.tagName }} + cancel + + + {{tagList?.length || 0}}/{{max_tag_num}} + {{errorMessage}} + + + + + {{ tag.tagName }} + + + diff --git a/src/modules/tag/component/write/write.component.scss b/src/modules/tag/component/write/write.component.scss new file mode 100755 index 0000000..05600da --- /dev/null +++ b/src/modules/tag/component/write/write.component.scss @@ -0,0 +1,3 @@ +.mat-hint { + font-size: 75% +} diff --git a/src/modules/tag/component/write/write.component.spec.ts b/src/modules/tag/component/write/write.component.spec.ts new file mode 100755 index 0000000..a9636f8 --- /dev/null +++ b/src/modules/tag/component/write/write.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { WriteComponent } from './write.component'; + +describe('WriteComponent', () => { + let component: WriteComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [WriteComponent] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(WriteComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/modules/tag/component/write/write.component.ts b/src/modules/tag/component/write/write.component.ts new file mode 100755 index 0000000..169f038 --- /dev/null +++ b/src/modules/tag/component/write/write.component.ts @@ -0,0 +1,165 @@ +/** + * 파 일 명: write.component.ts + * 작성일자: 2018-12-20 + * 작 성 자: 최지련 + * 설 명: Tag write component를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { COMMA, ENTER } from '@angular/cdk/keycodes'; +import { Component, OnInit, ElementRef, ViewChild, Input, } from '@angular/core'; +import { FormControl } from '@angular/forms'; +import { MatAutocompleteSelectedEvent, MatChipInputEvent, MatAutocomplete } from '@angular/material'; +import { Observable, of, } from 'rxjs'; +import { tap, debounceTime, switchMap, finalize, map } from 'rxjs/operators'; + +import { Tag } from '../../../../shared/tag/model/tag.model'; +import { TagService } from '../../type/tag-service.type'; + +@Component({ + selector: 'app-tag-write', + templateUrl: './write.component.html', + styleUrls: ['./write.component.scss'] +}) +export class WriteComponent implements OnInit { + @Input() + tagService: TagService; + + @Input() + tagList: Tag[]; + + @ViewChild('tagInput') + tagInput: ElementRef; + + @ViewChild('auto') + matAutocomplete: MatAutocomplete; + + errorMessage: string | null = null; + + visible = true; + selectable = true; + removable = true; + addOnBlur = true; + separatorKeysCodes: number[] = [ENTER, COMMA]; + tagCtrl = new FormControl(); + filteredTags: Observable; + max_tag_num = 10; + alertDuration = 2000; + + max_text_length = 64; + + constructor( + ) { + } + + ngOnInit(): void { + if (!this.tagList) { + this.tagList = []; + } + + this.filteredTags = this.tagCtrl.valueChanges.pipe( + debounceTime(300), + tap(() => { + }), + switchMap((tagName: string) => { + if (!tagName || 2 > tagName.length) { + return of([]); + } + return this.tagService.getAllByKeyword(tagName) + .pipe( + tap(() => { + }), + map((tagList: Tag[]) => { + return tagList; + }), + finalize(() => { + }), + ); + }) + ); + } + + isExistedTag(insertedTag): boolean { + // console.log(`[isExistedTag] insertedTag :: ${insertedTag}`); + + let resultYN = false; + + for (let idx = 0; idx < this.tagList.length; idx++) { + if (this.tagList[idx].tagName === insertedTag) { + resultYN = true; + break; + } + } + + return resultYN; + } + + addTag(insertedTag: string) { + // 글자 길이 제한 + if (insertedTag.length > this.max_text_length) { + this.showErrorMessage(`최대 ${this.max_text_length}자까지 입력가능합니다.`); + return; + } + + // 태그 개수 제한 + if (this.tagList.length === this.max_tag_num) { + this.showErrorMessage(`Tag는 ${this.max_tag_num}개 까지 입력가능합니다.`); + } else { + if (!this.isExistedTag(insertedTag)) { + this.tagList.push({ + tagName: insertedTag, + }); + + } else { + this.showErrorMessage(`중복 Tag는 입력이 불가능합니다.`); + } + } + } + + onInputTokenEndTag(event: MatChipInputEvent): void { + // Add tag only when MatAutocomplete is not open + // To make sure this does not conflict with OptionSelected Event + // if (!this.matAutocomplete.isOpen) { + const input = event.input; + const value = event.value; + + console.log(`[WriteComponent] add :: ${input} :: ${value}`); + + // Add our tag + if ((value || '').trim()) { + this.addTag(value.trim()); + } + + // Reset the input value + if (input) { + input.value = ''; + } + + this.tagCtrl.setValue(null); + // } + } + + onRemovedTag(tag: Tag): void { + const index = this.tagList.indexOf(tag); + + if (index >= 0) { + this.tagList.splice(index, 1); + } + } + + onOptionSelectedTag(event: MatAutocompleteSelectedEvent): void { + this.addTag(event.option.viewValue); + + this.tagInput.nativeElement.value = ''; + this.tagCtrl.setValue(null); + } + + showErrorMessage(errorMessage: string) { + this.errorMessage = errorMessage; + setTimeout(() => { + this.errorMessage = null; + }, this.alertDuration); + } +} diff --git a/src/modules/tag/service/index.ts b/src/modules/tag/service/index.ts new file mode 100755 index 0000000..589178b --- /dev/null +++ b/src/modules/tag/service/index.ts @@ -0,0 +1,2 @@ +export const SERVICES = [ +]; diff --git a/src/modules/tag/tag.module.ts b/src/modules/tag/tag.module.ts new file mode 100755 index 0000000..7f57908 --- /dev/null +++ b/src/modules/tag/tag.module.ts @@ -0,0 +1,44 @@ +import { NgModule, ModuleWithProviders } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { TranslateModule } from '@ngx-translate/core'; + +import { SharedModule } from '../common/shared/shared.module'; + +import { COMPONENTS } from './component'; +import { SERVICES } from './service'; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + ReactiveFormsModule, + TranslateModule, + SharedModule, + ], + exports: [ + ...COMPONENTS + ], + declarations: [ + ...COMPONENTS + ], +}) +export class TagModule { + public static forRoot(): ModuleWithProviders { + return { + ngModule: TagRootModule, + providers: [ + ...SERVICES, + ], + }; + } +} + +@NgModule({ + imports: [ + ], + exports: [ + ], +}) +export class TagRootModule { +} diff --git a/src/modules/tag/type/tag-service.type.ts b/src/modules/tag/type/tag-service.type.ts new file mode 100755 index 0000000..6f8bb38 --- /dev/null +++ b/src/modules/tag/type/tag-service.type.ts @@ -0,0 +1,7 @@ +import { Observable } from 'rxjs'; +import { Tag } from '../../../shared/tag/model/tag.model'; + +export interface TagService { + getAllByKeyword(keyword: string): Observable; + getAllPopular(): Observable; +} diff --git a/src/modules/user-analysis/component/favor-tag/favor-tag.component.html b/src/modules/user-analysis/component/favor-tag/favor-tag.component.html new file mode 100755 index 0000000..b78fef2 --- /dev/null +++ b/src/modules/user-analysis/component/favor-tag/favor-tag.component.html @@ -0,0 +1,26 @@ +
+
+

+ {{ 'favorite.msgFirstTime1' | translate }}
+ +

+

+ + {{ 'favorite.msgSelectMyFavorites' | translate }} +

+
+
+
+ + + + + + +
+ +
+
diff --git a/src/modules/user-analysis/component/favor-tag/favor-tag.component.scss b/src/modules/user-analysis/component/favor-tag/favor-tag.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/modules/user-analysis/component/favor-tag/favor-tag.component.spec.ts b/src/modules/user-analysis/component/favor-tag/favor-tag.component.spec.ts new file mode 100755 index 0000000..45a1a26 --- /dev/null +++ b/src/modules/user-analysis/component/favor-tag/favor-tag.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FavorTagComponent } from './favor-tag.component'; + +describe('FavorTagComponent', () => { + let component: FavorTagComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [FavorTagComponent] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(FavorTagComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/modules/user-analysis/component/favor-tag/favor-tag.component.ts b/src/modules/user-analysis/component/favor-tag/favor-tag.component.ts new file mode 100755 index 0000000..cd45847 --- /dev/null +++ b/src/modules/user-analysis/component/favor-tag/favor-tag.component.ts @@ -0,0 +1,189 @@ +/** + * 취향 분석 페이지 + */ +import { + Component, + OnInit, + Output, + EventEmitter, + Input, +} from '@angular/core'; +import { Store } from '@ngrx/store'; +import { TranslateService } from '@ngx-translate/core'; +import { Observable, of, Observer } from 'rxjs'; +import { + take, + map, + catchError, +} from 'rxjs/operators'; + +import { UserAnalysisFavorTagService } from '../../service/user-analysis-favor-tag.service'; +import { UserAnalysisFavorTag } from '../../../../shared/user-analysis/model/user-analysis-favor-tag.model'; +import { User } from '../../../../shared/user/model/user.model'; +import { AccountsUtil } from '../../../accounts/util/accounts.util'; +import { UserFavorTagService } from '../../../user/service/user-favor-tag.service'; +import { UserFavorTag } from '../../../../shared/user/model/user-favor-tag.model'; +import { UserService } from '../../../../modules/user/service/user.service'; +import * as AuthStore from '../../../accounts/store/auth'; + +@Component({ + selector: 'app-user-analysis-favor-tag', + templateUrl: './favor-tag.component.html', + styleUrls: ['./favor-tag.component.scss'] +}) +export class FavorTagComponent implements OnInit { + @Input() + selectMinCount: number; + + @Output() + changedSaveObservable = new EventEmitter>(); + @Output() + changedSaveDirty = new EventEmitter(); + @Output() + save = new EventEmitter(); + @Output() + cancel = new EventEmitter(); + + user: User; + userAnalysisFavorTagList: UserAnalysisFavorTag[]; + addedUserFavorTagList: UserFavorTag[]; + deletedUserFavorTagList: UserFavorTag[] = []; + + canComplete: boolean; + + msgFirstTime3: string; + + private _saveDirty = false; + + private readonly _saveObservable = Observable.create(async (observer: Observer) => { + this.userFavorTagService.updateAll({ added: this.addedUserFavorTagList, deleted: this.deletedUserFavorTagList }) + .pipe( + take(1), + map((count) => { + this.user.favorShowedYN = true; + this.userService.update(this.user) + .pipe( + take(1), + map((user) => { + this.changedSaveDirty.emit(false); + this.store.dispatch(new AuthStore.ChangeUser({ user })); + observer.next(user); + }), + catchError((error) => { + observer.error(error); + return of(error); + }), + ).subscribe(); + }), + catchError((error) => { + return of(error); + }), + ).subscribe(); + }); + + constructor( + private store: Store, + private translateService: TranslateService, + private userFavorTagService: UserFavorTagService, + private userService: UserService, + private userAnalysisFavorTagService: UserAnalysisFavorTagService, + ) { + } + + ngOnInit(): void { + AccountsUtil.getUser(this.store) + .then((user) => { + this.user = user; + this.showFavorTag(); + }) + .catch((reason) => { + console.log(reason); + }); + + this.translateService.get([ + 'favorite.msgFirstTime3']) + .subscribe(translations => { + this.msgFirstTime3 = translations['favorite.msgFirstTime3']; + }); + } + + showFavorTag() { + this.userAnalysisFavorTagService.getAll().pipe( + take(1), + map((userAnalysisFavorTagList) => { + this.userAnalysisFavorTagList = userAnalysisFavorTagList; + }), + catchError((err) => { + console.log(err); + return of(err); + }), + ).subscribe(); + + this.userFavorTagService.getAllByUid(this.user.id).pipe( + take(1), + map((userFavorTagList) => { + this.addedUserFavorTagList = userFavorTagList ? userFavorTagList : []; + this.changedFavorTag(true); + }), + catchError((err) => { + console.log(err); + return of(err); + }), + ).subscribe(); + } + + private changedFavorTag(init: boolean = false) { + this.canComplete = this.selectMinCount <= this.addedUserFavorTagList.length; + this.changedSaveObservable.emit(this.canComplete ? this._saveObservable : undefined); + + if (!init && !this._saveDirty) { + this._saveDirty = true; + this.changedSaveDirty.emit(this._saveDirty); + } + } + + getFavorTagIndex(userFavorTagList: UserFavorTag[], userAnalysisFavorTag: UserAnalysisFavorTag): number { + if (!userFavorTagList || 0 === userFavorTagList.length) { + return -1; + } + for (let index = 0; index < userFavorTagList.length; index++) { + const userFavorTag = userFavorTagList[index]; + if (userFavorTag.userAnalysisFavorTag.id === userAnalysisFavorTag.id) { + return index; + } + } + return -1; + } + + onClickFavorTag(userAnalysisFavorTag: UserAnalysisFavorTag) { + const addedIndex = this.getFavorTagIndex(this.addedUserFavorTagList, userAnalysisFavorTag); + const deletedIndex = this.getFavorTagIndex(this.deletedUserFavorTagList, userAnalysisFavorTag); + + if (-1 === addedIndex && -1 === deletedIndex) { + this.addedUserFavorTagList.push({ + userAnalysisFavorTag: userAnalysisFavorTag, + }); + } else if (-1 === addedIndex && -1 !== deletedIndex) { + const _userFavorTag = this.deletedUserFavorTagList[deletedIndex]; + this.deletedUserFavorTagList.splice(addedIndex, 1); + this.addedUserFavorTagList.push(_userFavorTag); + } else if (-1 !== addedIndex && -1 === deletedIndex) { + const _userFavorTag = this.addedUserFavorTagList[addedIndex]; + if (_userFavorTag.id) { + this.deletedUserFavorTagList.push(_userFavorTag); + } + this.addedUserFavorTagList.splice(addedIndex, 1); + } else { + return; + } + this.changedFavorTag(); + } + + onClickComplete() { + if (this._saveDirty) { + this.save.emit(); + } else { + this.cancel.emit(this.user); + } + } +} diff --git a/src/modules/user-analysis/component/index.ts b/src/modules/user-analysis/component/index.ts new file mode 100755 index 0000000..3990bdd --- /dev/null +++ b/src/modules/user-analysis/component/index.ts @@ -0,0 +1,5 @@ +import { FavorTagComponent } from './favor-tag/favor-tag.component'; + +export const COMPONENTS = [ + FavorTagComponent, +]; diff --git a/src/modules/user-analysis/service/index.ts b/src/modules/user-analysis/service/index.ts new file mode 100755 index 0000000..722ce87 --- /dev/null +++ b/src/modules/user-analysis/service/index.ts @@ -0,0 +1,5 @@ +import { UserAnalysisFavorTagService } from './user-analysis-favor-tag.service'; + +export const SERVICES = [ + UserAnalysisFavorTagService, +]; diff --git a/src/modules/user-analysis/service/user-analysis-favor-tag.service.ts b/src/modules/user-analysis/service/user-analysis-favor-tag.service.ts new file mode 100755 index 0000000..3f02302 --- /dev/null +++ b/src/modules/user-analysis/service/user-analysis-favor-tag.service.ts @@ -0,0 +1,23 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; + +import { + AbstractRestService, +} from '../../common/util/service/abstract-rest.service'; +import { httpOptions } from '../../common/util/service/abstract.service'; +import { UserAnalysisFavorTag } from '../../../shared/user-analysis/model/user-analysis-favor-tag.model'; +import { environment } from '../../../environments/environment'; +import { Observable } from 'rxjs'; + +@Injectable() +export class UserAnalysisFavorTagService extends AbstractRestService { + dummy: UserAnalysisFavorTag[]; + public constructor(httpClient: HttpClient) { + super(httpClient, environment.apiEntryPoint + '/user/analysis/favor_tag'); + } + + addAll(tagNameLIst: string[]): Observable { + return this.httpClient.put(this.apiEntryPoint, { tagNameLIst: tagNameLIst }, httpOptions); + } + +} diff --git a/src/modules/user-analysis/user-analysis.module.ts b/src/modules/user-analysis/user-analysis.module.ts new file mode 100755 index 0000000..d179dc8 --- /dev/null +++ b/src/modules/user-analysis/user-analysis.module.ts @@ -0,0 +1,47 @@ +/** + * 파 일 명: user-analysis.module.ts + * 작성일자: 2018-12-21 + * 작 성 자: 박병준 + * 설 명: User analysis module을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { NgModule, ModuleWithProviders } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { TranslateModule } from '@ngx-translate/core'; + +import { SharedModule } from '../common/shared/shared.module'; + +import { COMPONENTS } from './component'; +import { SERVICES } from './service'; +import { UserSupportModule } from '../../modules/user-support/user-support.module'; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + ReactiveFormsModule, + TranslateModule, + SharedModule, + UserSupportModule + ], + exports: [...COMPONENTS], + declarations: [...COMPONENTS] +}) +export class UserAnalysisModule { + public static forRoot(): ModuleWithProviders { + return { + ngModule: UserAnalysisRootModule, + providers: [...SERVICES] + }; + } +} + +@NgModule({ + imports: [], + exports: [] +}) +export class UserAnalysisRootModule { } diff --git a/src/modules/user-support/component/event/display/index.ts b/src/modules/user-support/component/event/display/index.ts new file mode 100755 index 0000000..a1fa7e4 --- /dev/null +++ b/src/modules/user-support/component/event/display/index.ts @@ -0,0 +1,5 @@ +import { ListComponent } from './list/list.component'; + +export const DISPLAY_COMPONENTS = [ + ListComponent, +]; diff --git a/src/modules/user-support/component/event/display/list/list.component.html b/src/modules/user-support/component/event/display/list/list.component.html new file mode 100755 index 0000000..4d70d5f --- /dev/null +++ b/src/modules/user-support/component/event/display/list/list.component.html @@ -0,0 +1,20 @@ +
+
    +
  • +
    +
    + + +
    +
    +

    + {{event.contents}} +

    +

    + {{event.startDate | date: 'MM/dd'}} ~ {{event.endDate | date: 'MM/dd'}} +

    +
    +
    +
  • +
+
diff --git a/src/modules/user-support/component/event/display/list/list.component.scss b/src/modules/user-support/component/event/display/list/list.component.scss new file mode 100755 index 0000000..8a952a9 --- /dev/null +++ b/src/modules/user-support/component/event/display/list/list.component.scss @@ -0,0 +1,14 @@ +@mixin calc($property, $expression) { + #{$property}: -moz-calc(#{$expression}); + #{$property}: -webkit-calc(#{$expression}); + #{$property}: calc(#{$expression}); +} + +.event { + padding: 0; + + .dataDL-data { + $rest: 90px; + @include calc(width, '100% -'$rest); + } +} diff --git a/src/modules/user-support/component/event/display/list/list.component.ts b/src/modules/user-support/component/event/display/list/list.component.ts new file mode 100755 index 0000000..aef0135 --- /dev/null +++ b/src/modules/user-support/component/event/display/list/list.component.ts @@ -0,0 +1,16 @@ +import { Component, OnInit, Input } from '@angular/core'; +import { UserSupportEvent } from '../../../../../../shared/user-support/model/user-support-event.model'; + + +@Component({ + selector: 'app-user-support-event-display-list', + templateUrl: './list.component.html', + styleUrls: ['./list.component.scss'] +}) +export class ListComponent implements OnInit { + @Input() + event: UserSupportEvent; + + constructor() { } + ngOnInit() { } +} diff --git a/src/modules/user-support/component/event/index.ts b/src/modules/user-support/component/event/index.ts new file mode 100755 index 0000000..e398e8a --- /dev/null +++ b/src/modules/user-support/component/event/index.ts @@ -0,0 +1,5 @@ +import { DISPLAY_COMPONENTS } from './display'; + +export const EVENT_COMPONENTS = [ + ...DISPLAY_COMPONENTS, +]; diff --git a/src/modules/user-support/component/index.ts b/src/modules/user-support/component/index.ts new file mode 100755 index 0000000..8c2d9d8 --- /dev/null +++ b/src/modules/user-support/component/index.ts @@ -0,0 +1,7 @@ +import { EVENT_COMPONENTS } from './event'; +import { TutorialComponent } from './tutorial/tutorial.component'; + +export const COMPONENTS = [ + ...EVENT_COMPONENTS, + TutorialComponent, +]; diff --git a/src/modules/user-support/component/tutorial/tutorial.component.html b/src/modules/user-support/component/tutorial/tutorial.component.html new file mode 100755 index 0000000..c2d54ce --- /dev/null +++ b/src/modules/user-support/component/tutorial/tutorial.component.html @@ -0,0 +1,12 @@ +
+
+
    +
  • 제목제목제목

  • +
+
+
+
    +
  • 내용내용내용내용

  • +
+
+
diff --git a/src/modules/user-support/component/tutorial/tutorial.component.scss b/src/modules/user-support/component/tutorial/tutorial.component.scss new file mode 100755 index 0000000..10e2a8f --- /dev/null +++ b/src/modules/user-support/component/tutorial/tutorial.component.scss @@ -0,0 +1,45 @@ +.tutorial-wrap { + width: 100%; + min-height: 100px; + position: fixed; + top: 0; + z-index: 9000; + background-color: #000; + opacity: 0.8; + margin: 0; + padding: 0; +} +.tutorial-wrap .tutorial-header { + width: 80%; + margin: 0 auto; +} +.tutorial-wrap .tutorial-header ul { + padding: 0; + margin: 0; +} +.tutorial-wrap .tutorial-header ul li { +} +.tutorial-wrap .tutorial-header ul li p { + color: #fff; + font-weight: 700; + margin-top: 30px; + word-break: break-all; + text-align: center; +} +.tutorial-wrap .tutorial-content { + width: 60%; + margin: 0 auto; +} +.tutorial-wrap .tutorial-content ul { + padding: 0; + margin: 0; +} +.tutorial-wrap .tutorial-content ul li { +} +.tutorial-wrap .tutorial-content ul li p { + margin: 10px 0; + color: #c2c2c2; + font-weight: 200; + word-break: break-all; + font-size: 12px; +} diff --git a/src/modules/user-support/component/tutorial/tutorial.component.spec.ts b/src/modules/user-support/component/tutorial/tutorial.component.spec.ts new file mode 100755 index 0000000..1bde898 --- /dev/null +++ b/src/modules/user-support/component/tutorial/tutorial.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TutorialComponent } from './tutorial.component'; + +describe('TutorialComponent', () => { + let component: TutorialComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ TutorialComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(TutorialComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/modules/user-support/component/tutorial/tutorial.component.ts b/src/modules/user-support/component/tutorial/tutorial.component.ts new file mode 100755 index 0000000..5459133 --- /dev/null +++ b/src/modules/user-support/component/tutorial/tutorial.component.ts @@ -0,0 +1,40 @@ +/** + * 듀토리얼 레이어 컴포넌트 + * new TutoreialModel(타이틀:string, 콘텐츠:string, 노출미노출:boolean) + */ +import { Component, OnInit, Input, AfterContentInit } from '@angular/core'; +import { Observable } from 'rxjs'; +import { UserSupportTutorialService } from '../../service/user-support-tutorial.service'; +import { UserSupportTutorial } from '../../../../shared/user-support/model/user-support-tutorial.model'; + +@Component({ + selector: 'app-user-support-tutorial', + templateUrl: './tutorial.component.html', + styleUrls: ['./tutorial.component.scss'] +}) +export class TutorialComponent implements AfterContentInit { + // 튜토리얼 컴포넌트 재사용을 위한 변수 추후 (정책 결정 필요) + pageName: string; // 컴포넌트 리스트 데이터의 키 + type: string; // 현재 접속된 디바이스 기기 타입. p:PCWEB m:MOBILEWEB + + /* _id: any; + tid: string; + pageName: string; + type: string; + title: string; + content: string; + attachImgUrl: string; + page: number; */ + + public tutorial$: Observable; + + constructor(private userSupportTutorialService: UserSupportTutorialService) { + this.pageName = 'favor'; + this.type = 'm'; + } + ngAfterContentInit(): void { + this.tutorial$ = this.userSupportTutorialService.getAll(); + } + + tutorialHide() {} +} diff --git a/src/modules/user-support/dialog/index.ts b/src/modules/user-support/dialog/index.ts new file mode 100755 index 0000000..93f0a74 --- /dev/null +++ b/src/modules/user-support/dialog/index.ts @@ -0,0 +1,7 @@ +import { ReportDialogComponent } from './report/report.dialog.component'; +import { ShareBottomSheetComponent } from './share/share.dialog.component'; + +export const DIALOGS = [ + ReportDialogComponent, + ShareBottomSheetComponent, +]; diff --git a/src/modules/user-support/dialog/report/report.dialog.component.html b/src/modules/user-support/dialog/report/report.dialog.component.html new file mode 100755 index 0000000..f082a09 --- /dev/null +++ b/src/modules/user-support/dialog/report/report.dialog.component.html @@ -0,0 +1,16 @@ + + + {{ 'common.report' | translate }} + + +

{{ 'common.msgReportingProfile' | translate }}

+
+ + + + +
diff --git a/src/modules/user-support/dialog/report/report.dialog.component.scss b/src/modules/user-support/dialog/report/report.dialog.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/modules/user-support/dialog/report/report.dialog.component.ts b/src/modules/user-support/dialog/report/report.dialog.component.ts new file mode 100755 index 0000000..d1c5434 --- /dev/null +++ b/src/modules/user-support/dialog/report/report.dialog.component.ts @@ -0,0 +1,30 @@ +import { Component, Inject } from '@angular/core'; +import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material'; +import { User } from '../../../../shared/user/model/user.model'; +import { UserReport } from '../../../../shared/user-support/type/user-report.type'; + +export interface ReportDialogData { + user: User; +} + +export interface ReportDialogResult { + type: UserReport; +} + +@Component({ + selector: 'app-user-support-report-dialog', + templateUrl: './report.dialog.component.html', + styleUrls: ['./report.dialog.component.scss'] +}) +export class ReportDialogComponent { + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: ReportDialogData, + ) { } + + onClickClose(type: UserReport): void { + this.dialogRef.close({ + type: type, + }); + } +} diff --git a/src/modules/user-support/dialog/share/share.dialog.component.html b/src/modules/user-support/dialog/share/share.dialog.component.html new file mode 100755 index 0000000..d45e82f --- /dev/null +++ b/src/modules/user-support/dialog/share/share.dialog.component.html @@ -0,0 +1,33 @@ + diff --git a/src/modules/user-support/dialog/share/share.dialog.component.scss b/src/modules/user-support/dialog/share/share.dialog.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/modules/user-support/dialog/share/share.dialog.component.ts b/src/modules/user-support/dialog/share/share.dialog.component.ts new file mode 100755 index 0000000..74c5c22 --- /dev/null +++ b/src/modules/user-support/dialog/share/share.dialog.component.ts @@ -0,0 +1,77 @@ +import { Component, OnInit, Inject, } from '@angular/core'; +import { + MatBottomSheetRef, + MAT_BOTTOM_SHEET_DATA +} from '@angular/material'; +import { TranslateService } from '@ngx-translate/core'; + +import { User } from '../../../../shared/user/model/user.model'; +import { UserShare } from '../../../../shared/user-support/type/user-share.type'; + +export interface ShareBottomSheetData { + user: User; + link: string; +} + +export interface ShareBottomSheetResult { + type: UserShare; +} + +@Component({ + selector: 'app-user-share-dialog', + templateUrl: './share.dialog.component.html', + styleUrls: ['./share.dialog.component.scss'], +}) +export class ShareBottomSheetComponent implements OnInit { + userShare = UserShare; + + facebook: string; + twitter: string; + googlePlus: string; + + constructor( + private translateService: TranslateService, + private bottomSheetRef: MatBottomSheetRef, + @Inject(MAT_BOTTOM_SHEET_DATA) private data: ShareBottomSheetData, + ) { } + + ngOnInit(): void { + this.translateService.get([ + 'login.facebook', + 'login.twitter', + 'login.googlePlus']) + .subscribe(translations => { + this.facebook = translations['login.facebook']; + this.twitter = translations['login.twitter']; + this.googlePlus = translations['login.googlePlus']; + }); + } + + onClickClose(type: UserShare): void { + console.log(`[ShareBottomSheetComponent] onClickClose :: UserShare.${type}`); + + this.bottomSheetRef.dismiss({ + type: type, + }); + + const ourURL = 'http://54.180.12.41:4200/'; + switch (type) { + case UserShare.Facebook: + const snsFacebook = 'https://www.facebook.com/sharer/sharer.php?u=' + ourURL + 'article/' + this.data.user.id; + window.open(snsFacebook, '', 'scrollbars=no, width=600,height = 600'); + break; + case UserShare.Twitter: + const snsTwitter = 'https://twitter.com/share?url=' + ourURL + 'article/' + this.data.user.id; + window.open(snsTwitter, '', 'scrollbars=no, width=600,height = 600'); + break; + case UserShare.GooglePlus: + const snsGooglePlus = 'https://plus.google.com/share?url=' + ourURL + 'article/' + this.data.user.id; + window.open(snsGooglePlus, '', 'scrollbars=no, width=600,height = 600'); + break; + case UserShare.Email: + break; + case UserShare.CopyLink: + break; + } + } +} diff --git a/src/modules/user-support/service/index.ts b/src/modules/user-support/service/index.ts new file mode 100755 index 0000000..1531a80 --- /dev/null +++ b/src/modules/user-support/service/index.ts @@ -0,0 +1,7 @@ +import { UserSupportEventService } from './user-support-event.service'; +import { UserSupportTutorialService } from './user-support-tutorial.service'; + +export const SERVICES = [ + UserSupportEventService, + UserSupportTutorialService, +]; diff --git a/src/modules/user-support/service/user-support-event.service.ts b/src/modules/user-support/service/user-support-event.service.ts new file mode 100755 index 0000000..ba521d2 --- /dev/null +++ b/src/modules/user-support/service/user-support-event.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; + +import { AbstractRestService } from '../../common/util/service/abstract-rest.service'; +import { environment } from '../../../environments/environment'; +import { UserSupportEvent } from '../../../shared/user-support/model/user-support-event.model'; + +@Injectable() +export class UserSupportEventService extends AbstractRestService { + + public constructor( + httpClient: HttpClient, + ) { + super(httpClient, environment.apiEntryPoint + '/user/support/event'); + } + +} diff --git a/src/modules/user-support/service/user-support-tutorial.service.ts b/src/modules/user-support/service/user-support-tutorial.service.ts new file mode 100755 index 0000000..a4ab664 --- /dev/null +++ b/src/modules/user-support/service/user-support-tutorial.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; + +import { AbstractRestService } from '../../common/util/service/abstract-rest.service'; +import { UserSupportTutorial } from '../../../shared/user-support/model/user-support-tutorial.model'; +import { environment } from '../../../environments/environment'; + +@Injectable() +export class UserSupportTutorialService extends AbstractRestService { + + public constructor( + httpClient: HttpClient, + ) { + super(httpClient, environment.apiEntryPoint + '/user/support/tutorial'); + } + +} diff --git a/src/modules/user-support/user-support.module.ts b/src/modules/user-support/user-support.module.ts new file mode 100755 index 0000000..f8be0ca --- /dev/null +++ b/src/modules/user-support/user-support.module.ts @@ -0,0 +1,61 @@ +/** + * 파 일 명: user-support.module.ts + * 작성일자: 2018-12-21 + * 작 성 자: 박병준 + * 설 명: User support module을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { NgModule, ModuleWithProviders } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { TranslateModule } from '@ngx-translate/core'; + +import { SharedModule } from '../common/shared/shared.module'; + +import { COMPONENTS } from './component'; +import { DIALOGS } from './dialog'; +import { SERVICES } from './service'; +import { ClipboardModule } from 'ngx-clipboard'; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + ReactiveFormsModule, + TranslateModule, + ClipboardModule, + SharedModule, + ], + exports: [ + ...COMPONENTS + ], + declarations: [ + ...COMPONENTS, + ...DIALOGS + ], + entryComponents: [ + ...DIALOGS + ], +}) +export class UserSupportModule { + public static forRoot(): ModuleWithProviders { + return { + ngModule: UserSupportRootModule, + providers: [ + ...SERVICES, + ], + }; + } +} + +@NgModule({ + imports: [ + ], + exports: [ + ], +}) +export class UserSupportRootModule { +} diff --git a/src/modules/user/component/followers/display/index.ts b/src/modules/user/component/followers/display/index.ts new file mode 100755 index 0000000..a1fa7e4 --- /dev/null +++ b/src/modules/user/component/followers/display/index.ts @@ -0,0 +1,5 @@ +import { ListComponent } from './list/list.component'; + +export const DISPLAY_COMPONENTS = [ + ListComponent, +]; diff --git a/src/modules/user/component/followers/display/list/list.component.html b/src/modules/user/component/followers/display/list/list.component.html new file mode 100755 index 0000000..8b70e52 --- /dev/null +++ b/src/modules/user/component/followers/display/list/list.component.html @@ -0,0 +1,39 @@ +
+
+
+
    +
  • +
    + +
    + + + + + +
    +
    +

    + {{currUser.nickname}} +

    +

    + {{currUser.introduction}}

    +
    +
    + + + + + + +
    +
    +
    +
  • +
+
+
+
diff --git a/src/modules/user/component/followers/display/list/list.component.scss b/src/modules/user/component/followers/display/list/list.component.scss new file mode 100755 index 0000000..edf86d4 --- /dev/null +++ b/src/modules/user/component/followers/display/list/list.component.scss @@ -0,0 +1,27 @@ +@mixin calc($property, $expression) { + #{$property}: -moz-calc(#{$expression}); + #{$property}: -webkit-calc(#{$expression}); + #{$property}: calc(#{$expression}); +} + +.recommend { + padding: 0; + + .dataDL-data { + $rest: 180px; + @include calc(width, '100% -'$rest); + + .dataDL-image { + border-radius: 50%; + border: 3px solid #fff; + } + } + + .dataDL-button { + width: 100px; + + button { + @extend .dataDL-button; + } + } +} diff --git a/src/modules/user/component/followers/display/list/list.component.ts b/src/modules/user/component/followers/display/list/list.component.ts new file mode 100755 index 0000000..aa0316a --- /dev/null +++ b/src/modules/user/component/followers/display/list/list.component.ts @@ -0,0 +1,155 @@ +import { + Component, + Input, + OnInit, + ChangeDetectionStrategy, + AfterViewChecked +} from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { UserFollowers } from '../../../../../../shared/user/model/user-followers.model'; +import { User } from '../../../../../../shared/user/model/user.model'; +import { AccountsUtil } from 'src/modules/accounts/util/accounts.util'; +import { Store, select } from '@ngrx/store'; +import { Observable, of } from 'rxjs'; +import { + tap, + exhaustMap, + take, + map, + catchError, + finalize +} from 'rxjs/operators'; +import { AuthSelector } from '../../../../../accounts/store'; +import { UserFollowersService } from '../../../../../../modules/user/service/user-followers.service'; +import { MatDialog, MatSnackBar, MatBottomSheet } from '@angular/material'; +import { + UnfollowDialogComponent, + UnfollowDialogData, + UnfollowDialogResult +} from '../../../../../user/dialog/unfollow/unfollow.dialog.component'; + + +@Component({ + selector: 'app-user-followers-display-list', + templateUrl: './list.component.html', + styleUrls: ['./list.component.scss'], +}) +export class ListComponent implements OnInit { + @Input() + followers: UserFollowers; + + @Input() + currUser: User; + + @Input() + uid: string; + + user: User; + me: boolean; + + constructor( + private activatedRoute: ActivatedRoute, + private router: Router, + private store: Store, + private userFollowersService: UserFollowersService, + public matDialog: MatDialog) { } + + ngOnInit(): void { + AccountsUtil.getUser(this.store) + .then((user) => { + this.user = user; + if (this.currUser.id === this.user.id) { + this.me = true; + } + }) + .catch((reason) => { + console.log(reason); + }); + + } + + isFollowing(): boolean { + + if (this.user && !this.me) { + return this.userFollowersService.checkLocalFollowersId(this.user, this.currUser.id); + } + + return false; + } + + onClickFollow() { + + const userFollowers: UserFollowers = { + user: this.user, + target: this.currUser + }; + this.userFollowersService.add(userFollowers).pipe( + take(1), + map((res) => { + console.log(res); + this.userFollowersService.addLocalFollowers(this.user, res, this.store); + }), + catchError((error) => { + return of(error); + }), + finalize(() => { + }) + ).subscribe(); + + } + + onClickUnFollow() { + if (!this.user) { + return; + } + + const unFollowdialogRef = this.matDialog.open< + UnfollowDialogComponent, + UnfollowDialogData, + UnfollowDialogResult + >(UnfollowDialogComponent, { + width: '250px', + data: { + user: this.currUser + } + }); + + unFollowdialogRef.afterClosed().subscribe(result => { + if (result) { + if (result.selected) { + this.processUnFollow(); + } + } + }); + + + } + + processUnFollow() { + const userFollowersId = this.userFollowersService.getLocalFollowersId(this.user, this.currUser.id); + + if (!userFollowersId) { + return; + } + + this.userFollowersService.delete(userFollowersId).pipe( + take(1), + tap(() => { + }), + map((res: any) => { + if (res) { + this.userFollowersService.removeLocalFollowers(this.user, userFollowersId, this.store); + } + }), + catchError((error) => { + return of(error); + }), + finalize(() => { + }) + ).subscribe(); + } + + goAuthorPage(id: string) { + this.router.navigate(['/article', id]); + } +} diff --git a/src/modules/user/component/followers/index.ts b/src/modules/user/component/followers/index.ts new file mode 100755 index 0000000..94e232e --- /dev/null +++ b/src/modules/user/component/followers/index.ts @@ -0,0 +1,5 @@ +import { DISPLAY_COMPONENTS } from './display'; + +export const FOLLOWERS_COMPONENTS = [ + ...DISPLAY_COMPONENTS, +]; diff --git a/src/modules/user/component/index.ts b/src/modules/user/component/index.ts new file mode 100755 index 0000000..70cc96f --- /dev/null +++ b/src/modules/user/component/index.ts @@ -0,0 +1,14 @@ +import { PROFILE_COMPONENTS } from './profile'; +import { FOLLOWERS_COMPONENTS } from './followers'; + +import { SettingsComponent } from './settings/settings.component'; +import { RECOMMENDATIONS_COMPONENTS } from './recommendations'; +import { SearchAutocompleteComponent } from './search-autocomplete/search-autocomplete.component'; + +export const COMPONENTS = [ + ...PROFILE_COMPONENTS, + ...FOLLOWERS_COMPONENTS, + ...RECOMMENDATIONS_COMPONENTS, + SettingsComponent, + SearchAutocompleteComponent +]; diff --git a/src/modules/user/component/profile/box/box.component.html b/src/modules/user/component/profile/box/box.component.html new file mode 100755 index 0000000..70307ca --- /dev/null +++ b/src/modules/user/component/profile/box/box.component.html @@ -0,0 +1,92 @@ + + +
+
+ {{ author.nickname }} +
+
+ {{ author.introduction }} +
+
+
+
    +
  • + {{ 'myPage.article' | translate }} + + {{articleCount ? articleCount : 0}} + +
  • +
  • + {{ 'myPage.follower' | translate }} + + {{followersCount ? followersCount : 0}} + +
  • +
  • + {{ 'myPage.following' | translate }} + + {{followingCount ? followingCount : 0}} + +
  • +
+
+
diff --git a/src/modules/user/component/profile/box/box.component.scss b/src/modules/user/component/profile/box/box.component.scss new file mode 100755 index 0000000..7eaa53b --- /dev/null +++ b/src/modules/user/component/profile/box/box.component.scss @@ -0,0 +1,137 @@ +@mixin calc($property, $expression) { + #{$property}: -moz-calc(#{$expression}); + #{$property}: -webkit-calc(#{$expression}); + #{$property}: calc(#{$expression}); +} + +.user-info { + display: flex; + flex-direction: row; + align-items: stretch; + flex-shrink: 0; + position: relative; + margin: 15px 15px 10px 15px; + + .banout { + top: 50px; + right: 0 !important; + + &.mat-button { + padding: 0; + min-width: 32px; + color: rgba(0, 0, 0, 0.3); + } + } + + .avatar { + margin-right: 30px; + height: 75px; + width: 75px; + + .user-info-header-image { + height: 100%; + width: 100%; + border-radius: 50%; + border: solid 1px #fff; + object-fit: cover; + background: url('../../../../../assets/img/examples/avata.jpg') no-repeat center center; + background-size: contain; + display: block; + overflow: hidden; + + img { + height: 100%; + width: 100%; + display: block; + overflow: hidden; + } + } + } + + .user-info-data { + flex-basis: 0; + flex-grow: 1; + flex-shrink: 1; + margin-top: 7px; + + .user-tag { + max-height: 36px; + overflow: hidden; + } + + .user-follow { + text-align: center; + margin-bottom: 10px; + + button { + width: 100%; + max-width: 350px; + } + } + } +} + +.text-overflow { + display: block; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; +} + +.user-intro { + margin: 0 15px 15px 15px; + + .user-nickname { + display: block; + overflow: hidden; + font-size: 1.1em; + color: #000; + font-weight: 700; + margin-bottom: 10px; + } + + .user-introduction { + display: block; + overflow: hidden; + height: auto; + } +} + +.user-follow-info { + display: block; + position: relative; + border-top: 1px solid #d9d9d9; + border-bottom: 1px solid #d9d9d9; + + ul { + margin: 15px; + padding: 0; + list-style: none; + display: flex; + flex-direction: row; + $justify: space-around; + -webkit-justify-content: $justify; + -ms-flex-pack: $justify; + justify-content: $justify; + + li { + flex-basis: 0; + box-flex: 1; + flex-grow: 1; + flex-shrink: 1; + text-align: center; + @include calc(width, '100% / 3'); + color: #808080; + + .user-follow-info-title { + margin-bottom: 10px; + } + + .user-follow-info-data { + display: block; + font-weight: 500; + color: #000; + } + } + } +} diff --git a/src/modules/user/component/profile/box/box.component.ts b/src/modules/user/component/profile/box/box.component.ts new file mode 100755 index 0000000..21cef65 --- /dev/null +++ b/src/modules/user/component/profile/box/box.component.ts @@ -0,0 +1,255 @@ +/** + * 파 일 명: profile-box.component.ts + * 작성일자: 2019-01-03 + * 작 성 자: 박병준 + * 설 명: profile box component를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + * 수정일시: 2019-01-11 + * 수 정 자: 최지련 + * 수정내용: 설정 진입 메뉴 추가 + */ +import { Component, OnInit, Output, EventEmitter, Input } from '@angular/core'; +import { MatDialog, MatSnackBar, MatBottomSheet } from '@angular/material'; +import { Store, select } from '@ngrx/store'; + +import { of } from 'rxjs'; +import { take, tap, map, catchError, finalize } from 'rxjs/operators'; + +import * as AccountsStore from '../../../../accounts/store'; +import { User } from '../../../../../shared/user/model/user.model'; +import { UserService } from '../../../service/user.service'; + +import { + ReportDialogComponent, + ReportDialogData, + ReportDialogResult +} from '../../../../user-support/dialog/report/report.dialog.component'; +import { + UnfollowDialogComponent, + UnfollowDialogData, + UnfollowDialogResult +} from '../../../../user/dialog/unfollow/unfollow.dialog.component'; +import { + ShareBottomSheetComponent, + ShareBottomSheetData, + ShareBottomSheetResult +} from '../../../../user-support/dialog/share/share.dialog.component'; + +import * as UserProfileStore from '../../../store/profile'; +import { UserFollowers } from '../../../../../shared/user/model/user-followers.model'; +import { UserFollowersService } from '../../../../user/service/user-followers.service'; +import { AccountsUtil } from 'src/modules/accounts/util/accounts.util'; + +import { UserFavorTag } from '../../../../../shared/user/model/user-favor-tag.model'; + +@Component({ + selector: 'app-user-profile-box', + templateUrl: './box.component.html', + styleUrls: ['./box.component.scss'] +}) +export class BoxComponent implements OnInit { + @Input() + uid: string; + @Input() + link: string; + @Input() + followingCount: number; + @Input() + followersCount: number; + @Input() + articleCount: number; + @Input() + favorTagList: UserFavorTag[]; + + @Output() + isFollowingYn = new EventEmitter(); + + author: User; + user: User; + myPageYn = false; + // userFollow: UserFollowers; + // myFollowingList: UserFollowers[]; + // followingID: string; + + followingYN: boolean; + + constructor( + private userService: UserService, + private userFollowersService: UserFollowersService, + private store: Store, + public snackBar: MatSnackBar, + public matDialog: MatDialog, + public matBottomSheet: MatBottomSheet + ) { } + + private status = true; + ngOnInit() { + + AccountsUtil.getUser(this.store) + .then((user: User) => { + if (user) { + this.user = user; + this.fetchAuthor(); + + if (this.user.id === this.uid) { + this.myPageYn = true; + } else { + this.checkFollow(); + } + } + }) + .catch(error => { + return of(error); + }); + + // for text-overflow + this.status = true; + this.followingYN = false; + } + + onClickProfileEdit(): void { + this.store.dispatch(new UserProfileStore.GotoProfileEdit()); + } + + fetchAuthor() { + this.userService.get(this.uid).pipe( + take(1), + map((user: User) => { + if (user) { + this.author = user; + } + }), + catchError(error => { + return of(error); + }), + ).subscribe(); + } + + checkFollow() { + this.followingYN = this.userFollowersService.checkLocalFollowersId(this.user, this.uid); + } + + onClickReport(): void { + const dialogRef = this.matDialog.open< + ReportDialogComponent, + ReportDialogData, + ReportDialogResult + >(ReportDialogComponent, { + width: '250px', + data: { + user: this.author + } + }); + + dialogRef.afterClosed().subscribe(result => { + if (result) { + console.log('report', result.type); + } + }); + } + + onClickUnfollow(): void { + + if (!this.user) { + return; + } + + const unFollowdialogRef = this.matDialog.open< + UnfollowDialogComponent, + UnfollowDialogData, + UnfollowDialogResult + >(UnfollowDialogComponent, { + width: '250px', + data: { + user: this.author + } + }); + + unFollowdialogRef.afterClosed().subscribe(result => { + if (result) { + if (result.selected) { + this.processUnFollow(); + } + } + }); + } + + processUnFollow() { + const userFollowersId = this.userFollowersService.getLocalFollowersId(this.user, this.author.id); + + if (!userFollowersId) { + return; + } + + this.userFollowersService.delete(userFollowersId).pipe( + take(1), + tap(() => { + }), + map((res: any) => { + if (res) { + this.userFollowersService.removeLocalFollowers(this.user, userFollowersId, this.store); + this.followingYN = false; + this.isFollowingYn.emit(false); + } + }), + catchError((error) => { + return of(error); + }), + finalize(() => { + }) + ).subscribe(); + } + + onClickShareProfile(): void { + const shareBottomSheetRef = this.matBottomSheet.open< + ShareBottomSheetComponent, + ShareBottomSheetData, + ShareBottomSheetResult + >(ShareBottomSheetComponent, { + data: { + user: this.author, + link: this.link + } + }); + + shareBottomSheetRef.afterDismissed().subscribe(result => { + if (result) { + if (result.type) { + } + } + }); + } + + onClickSettings(): void { + this.store.dispatch(new UserProfileStore.GotoSettings()); + } + + onClickFollow() { + if (!this.user) { + return; + } + + const userFollowers: UserFollowers = { user: this.user, target: this.author }; + this.userFollowersService.add(userFollowers).pipe( + take(1), + map((res) => { + console.log(res); + this.userFollowersService.addLocalFollowers(this.user, res, this.store); + this.followingYN = true; + this.isFollowingYn.emit(true); + }), + catchError((error) => { + return of(error); + }), + finalize(() => { + }) + ).subscribe(); + } + clickEvent() { + this.status = !this.status; + } + +} diff --git a/src/modules/user/component/profile/index.ts b/src/modules/user/component/profile/index.ts new file mode 100755 index 0000000..1a9c336 --- /dev/null +++ b/src/modules/user/component/profile/index.ts @@ -0,0 +1,7 @@ +import { BoxComponent } from './box/box.component'; +import { WriteComponent } from './write/write.component'; + +export const PROFILE_COMPONENTS = [ + BoxComponent, + WriteComponent, +]; diff --git a/src/modules/user/component/profile/write/write.component.html b/src/modules/user/component/profile/write/write.component.html new file mode 100755 index 0000000..c0c071d --- /dev/null +++ b/src/modules/user/component/profile/write/write.component.html @@ -0,0 +1,77 @@ +
+
+
+ + my profile image + + + my profile image + + +
+
+ + my profile image + + + my profile image + +
+
+
+
+
+

{{ user.nickname }}

+
+
+
+
+ + + {{ 'myPage.userName' | translate }} + + + User nickname is required. + + + {{ 'myPage.msgUserNameAlreadyRegistered' | translate }} + + + You must enter a minimum of {{min_user_nickname_leng}} characters for the nickname. + + + + + + {{ 'myPage.introduction' | translate }} + + + You must enter a minimum of {{min_user_introduction_leng}} characters for the + introduction. + + + + + + {{ 'myPage.emailAddress' | translate }} + + + Please enter a valid email address + + +
+
+
+
diff --git a/src/modules/user/component/profile/write/write.component.scss b/src/modules/user/component/profile/write/write.component.scss new file mode 100755 index 0000000..0a775a8 --- /dev/null +++ b/src/modules/user/component/profile/write/write.component.scss @@ -0,0 +1,86 @@ +.profile-edit { + .section-wrap { + display: block; + overflow: hidden; + + .left { + float: left; + } + + .right { + float: right; + } + } + + .wrap { + position: relative; + text-align: center; + margin: 0 auto; + padding-bottom: 40px; + + .button-init { + padding: 0; + min-width: 36px; + } + + .image-bg { + position: absolute; + top: 70px; + right: 15px; + @extend .button-init; + } + + .image1 { + width: 100%; + height: 180px; + object-fit: cover; + } + + .over-profile { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + + .profile-image-wrap { + height: 80px; + width: 80px; + margin: 0 auto; + position: relative; + border-radius: 50%; + border: solid 3px #fff; + object-fit: cover; + background: url('../../../../../assets/img/examples/avata.jpg') no-repeat center center; + background-size: contain; + + .image-profile { + position: absolute; + bottom: 5px; + right: 0; + @extend .button-init; + } + + .mat-card-avatar { + height: 100%; + width: 100%; + border-radius: 50%; + flex-shrink: 0; + } + } + } + } + + .idarea { + text-align: center; + font-size: 1.4em; + font-weight: 500; + + p { + margin: 10px 0; + } + } +} + +app-attachments-image-dataurl { + display: none +} diff --git a/src/modules/user/component/profile/write/write.component.ts b/src/modules/user/component/profile/write/write.component.ts new file mode 100755 index 0000000..490c750 --- /dev/null +++ b/src/modules/user/component/profile/write/write.component.ts @@ -0,0 +1,382 @@ +/** + * 파 일 명: profile.component.ts + * 작성일자: 2018-12-19 + * 작 성 자: 박병준 + * 설 명: profile component를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + * 수정일시: 2019-01-11 + * 수 정 자: 최지련 + * 수정내용: nickname, introduction, email 유효성 적용 + */ +import { + Component, + OnInit, + ViewChild, + ElementRef, + Output, + EventEmitter, + OnDestroy, + ChangeDetectorRef, +} from '@angular/core'; +import { + FormControl, + Validators, + FormBuilder, + FormGroup, + AbstractControl, + FormGroupDirective, + NgForm +} from '@angular/forms'; + +import { + MatDialog, + MatBottomSheet, + ErrorStateMatcher +} from '@angular/material'; +import { Store, select } from '@ngrx/store'; + +import { Observable, of, Observer, Subscription } from 'rxjs'; +import { take, tap, map, catchError, finalize } from 'rxjs/operators'; + +import * as AuthStore from '../../../../accounts/store/auth'; +import { UserService } from '../../../service/user.service'; + +import { User } from '../../../../../shared/user/model/user.model'; +import { + AttachmentsService, +} from '../../../../attachments/service/attachments.service'; + +import { + ImageCropperDialogComponent, + ImageCropperDialogData, + ImageCropperDialogResult +} from '../../../../common/shared/dialog/image-cropper/image-cropper.dialog.component'; +import { FileItem } from '../../../../attachments/model/file-item.model'; +import { UIUtil } from '../../../../common/util/ui/dialog.util'; +import { AccountsUtil } from '../../../../accounts/util/accounts.util'; +import { + ImageModifyBottomSheetComponent, + ImageModifyBottomSheetData, + ImageModifyBottomSheetResult, + ImageModify, +} from '../../../../user/dialog/image-modify/image-modify.bottomsheet.component'; + +export class ValidateNicknameUnique { + static createValidator(userService: UserService, user: User) { + return (control: AbstractControl) => { + return userService.getByNickname(control.value).pipe( + map((_user) => { + let uniqueNickname = true; + if (_user && _user.id !== user.id) { + uniqueNickname = false; + } + return uniqueNickname ? null : { nicknameUnique: true }; + }) + ); + }; + } +} + +export class CustomErrorStateMatcher implements ErrorStateMatcher { + isErrorState( + control: FormControl | null, + form: FormGroupDirective | NgForm | null + ): boolean { + const isSubmitted = form && form.submitted; + return control.invalid && (control.dirty || control.touched || isSubmitted); + } +} + +@Component({ + selector: 'app-user-profile-write', + templateUrl: './write.component.html', + styleUrls: ['./write.component.scss'] +}) +export class WriteComponent implements OnInit, OnDestroy { + + @Output() + changedSaveObservable = new EventEmitter>(); + + @Output() + changedSaveDirty = new EventEmitter(); + + @ViewChild('backgroundImageFileInput') + backgroundImageFileInput: ElementRef; + + @ViewChild('profileImageFileInput') + profileImageFileInput: ElementRef; + + errorStateMatcher = new CustomErrorStateMatcher(); + user: User; + + profileImageFileItem: FileItem; + backgroundImageFileItem: FileItem; + + + profileForm: FormGroup; + bottomResult: string; + + private _saveDirty: boolean | undefined = undefined; + + profileFormValidSubscription: Subscription; + + min_user_nickname_leng = 2; + max_user_nickname_leng = 32; + min_user_introduction_leng = 2; + max_user_introduction_leng = 40; + min_user_email_leng = 2; + max_user_email_leng = 320; + + get nicknameFormControl() { + return this.profileForm.get('nickname'); + } + get introductionFormControl() { + return this.profileForm.get('introduction'); + } + get emailFormControl() { + return this.profileForm.get('email'); + } + + private readonly _saveObservable = Observable.create(async (observer: Observer) => { + const fileItemList: FileItem[] = []; + + if (this.profileImageFileItem) { + fileItemList.push(this.profileImageFileItem); + } + if (this.backgroundImageFileItem) { + fileItemList.push(this.backgroundImageFileItem); + } + + if (0 < fileItemList.length) { + await this.fileUpload(fileItemList); + + if (this.profileImageFileItem) { + this.user.profileImageAttachments = this.profileImageFileItem.attachments; + } + if (this.backgroundImageFileItem) { + this.user.backgroundImageAttachments = this.backgroundImageFileItem.attachments; + } + } + + this.userService.update(this.user) + .pipe( + take(1), + map((user) => { + this.changedSaveDirty.emit(false); + this.store.dispatch(new AuthStore.ChangeUser({ user })); + observer.next(user); + }), + catchError((error) => { + observer.error(error); + return of(error); + }), + ).subscribe(); + }); + + constructor( + private changeDetector: ChangeDetectorRef, + private matDialog: MatDialog, + private formBuilder: FormBuilder, + private userService: UserService, + private store: Store, + private attachmentsService: AttachmentsService, + public matBottomSheet: MatBottomSheet, + ) { } + + ngOnInit() { + AccountsUtil.getUser(this.store) + .then((user) => { + this.userService.get(user.id) + .pipe( + take(1), + map((usr: User) => { + this.user = usr; + this.initProfileForm(); + }), + catchError(err => { + return of(err); + }), + ).subscribe(); + }) + .catch((reason) => { + console.log(reason); + }); + } + + + ngOnDestroy(): void { + if (this.profileFormValidSubscription) { + this.profileFormValidSubscription.unsubscribe(); + } + } + + initProfileForm() { + this.profileForm = this.formBuilder.group({ + nickname: new FormControl( + this.user.nickname, + [ + Validators.required, + Validators.minLength(this.min_user_nickname_leng), + Validators.maxLength(this.max_user_nickname_leng) + ] + ), + introduction: new FormControl( + this.user.introduction, + [ + Validators.minLength(this.min_user_introduction_leng), + Validators.maxLength(this.max_user_introduction_leng) + ] + ), + email: new FormControl( + this.user.email, + [ + Validators.email, + Validators.minLength(this.min_user_email_leng), + Validators.maxLength(this.max_user_email_leng) + ]) + }); + this.nicknameFormControl.setAsyncValidators( + ValidateNicknameUnique.createValidator(this.userService, this.user) + ); + + this.profileFormValidSubscription = this.profileForm.statusChanges.pipe( + map(() => { + this.changedSaveObservable.emit(this.profileForm.valid ? this._saveObservable : undefined); + this.changedSaveDirty.emit(this.profileForm.dirty); + }) + ).subscribe(); + this.profileForm.updateValueAndValidity(); + } + + + onKey(event: any) { + this.nicknameFormControl.setAsyncValidators( + ValidateNicknameUnique.createValidator(this.userService, this.user) + ); + } + + async onClickBackgroundButton(event: MouseEvent) { + if (!this.user.backgroundImageAttachments) { + this.backgroundImageFileInput.nativeElement.click(); + } else { + const result = await this.showImageOptionBottomSheet(this.backgroundImageFileInput); + if (result === undefined) { + return; + } + switch (result.choice) { + case ImageModify.Delete: + { + this.backgroundImageFileItem = null; + this.user.backgroundImageAttachments = null; + } + break; + } + } + } + + showImageOptionBottomSheet(fileInput: ElementRef) { + const result = + UIUtil.bottomSheetOpen( + this.matBottomSheet, ImageModifyBottomSheetComponent, + { + data: { + fileInput: fileInput + }, + } + ); + return result; + } + + async onChangeBackgroundFile(event) { + if (!event.target.files || 0 === event.target.files.length) { + return; + } + + if (event.target.files && event.target.files.length > 0) { + const file: File = event.target.files[0]; + this.backgroundImageFileItem = await FileItem.fromFile(file); + if (!this._saveDirty) { + this._saveDirty = true; + this.emitSaveDirty(); + } + this.changeDetector.markForCheck(); + } + } + + async onClickProfileButton(event) { + if (!this.user.profileImageAttachments) { + this.profileImageFileInput.nativeElement.click(); + } else { + const result = await this.showImageOptionBottomSheet(this.profileImageFileInput); + if (result === undefined) { + return; + } + switch (result.choice) { + case ImageModify.Delete: + { + this.profileImageFileItem = null; + this.user.profileImageAttachments = null; + } + break; + } + } + } + + async onChangeProfileFile(event) { + if (!event.target.files || 0 === event.target.files.length) { + return; + } + const file: File = event.target.files[0]; + const fileItem = await FileItem.fromBlob(file.name, file); + console.log(fileItem); + + UIUtil.dialogOpen( + this.matDialog, ImageCropperDialogComponent, + { + panelClass: 'app-full-screen-dialog', + data: { + fileItem: fileItem + } + }) + .then((result) => { + if (result.fileItem) { + console.log(result); + this.profileImageFileItem = result.fileItem; + if (!this._saveDirty) { + this._saveDirty = true; + this.emitSaveDirty(); + } + this.changeDetector.detectChanges(); + } + }) + .catch((reason) => { + }); + } + + fileUpload(fileItemList: FileItem[]): Promise { + return new Promise((resolve, reject) => { + this.attachmentsService.upload(fileItemList) + .pipe( + take(1), + tap(() => { + }), + map(() => { + return resolve(); + }), + catchError(err => { + reject(err); + return of(err); + }), + finalize(() => { }) + ) + .subscribe(); + }); + } + + emitSaveDirty() { + this.changedSaveDirty.emit(this._saveDirty); + } +} diff --git a/src/modules/user/component/recommendations/display/card/card.component.html b/src/modules/user/component/recommendations/display/card/card.component.html new file mode 100755 index 0000000..b2db4c0 --- /dev/null +++ b/src/modules/user/component/recommendations/display/card/card.component.html @@ -0,0 +1,31 @@ +
+
+ +
+
+ + + +
+
+
+ profile image +
+
+
+
+

{{author.nickname}}

+

{{author.introduction}}

+
+ +
+
+
+
+
+
+
diff --git a/src/modules/user/component/recommendations/display/card/card.component.scss b/src/modules/user/component/recommendations/display/card/card.component.scss new file mode 100755 index 0000000..a47d8f9 --- /dev/null +++ b/src/modules/user/component/recommendations/display/card/card.component.scss @@ -0,0 +1,144 @@ +/* .example-card { + max-width: 400px; +} + +.example-header-image { + background-image: url('https://material.angular.io/assets/img/examples/shiba1.jpg'); + background-size: cover; +} */ +.transition { + transition: all 0.4s ease-in-out; +} + +.carousel { + .content { + display: flex; + + .item { + width: 100%; + display: block; + + .img { + width: 100%; + display: block; + background-size: cover; + background-position: center; + height: 0; + padding-bottom: 50%; + } + } + } + + .item { + width: 100%; + display: block; + + .img { + width: 100%; + display: block; + background-size: cover; + background-position: center; + height: 0; + padding-bottom: 50%; + } + } + + .ball { + width: 10px; + height: 10px; + border-radius: 50%; + background: black; + border: 2px solid; + opacity: 0.5; + + &.visible { + opacity: 1; + } + } + + .progress { + position: absolute; + bottom: 0; + left: 0; + width: 0; + height: 5px; + background: #ff5252; + } + + .click-area { + width: 50px; + text-align: center; + + i { + font-size: 3em; + } + } +} + +/* +.profile-recommend-author-background { + width: 100%; + height: 180px; + position: relative; +} +.image-profile { + position: absolute; + bottom: 5px; + right: 0; +} +*/ +.recommend-card { + margin-right: 10px; + + .profile-text-wrap { + margin-top: 50px; + text-align: center; + + h2 { + font-size: 1.05em; + } + + .description { + display: block; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + height: 18px; + } + } + + .author-follow-button { + width: 100%; + } + + .over-profile { + position: absolute; + left: 0; + width: 100%; + height: 23%; + top: calc((50% - 0.75px) - 50px); + + .profile-image-wrap { + height: 100%; + width: 25%; + margin: 0 auto; + position: relative; + text-align: center; + border-radius: 50%; + border: solid 1px #fff; + // background-color: #eee; + object-fit: cover; + background: url('../../../../../../assets/img/examples/avata.jpg') no-repeat center center; + background-size: contain; + + + .mat-card-avatar { + height: 100%; + width: 100%; + border-radius: 50%; + flex-shrink: 0; + object-fit: cover; + } + } + } +} diff --git a/src/modules/user/component/recommendations/display/card/card.component.spec.ts b/src/modules/user/component/recommendations/display/card/card.component.spec.ts new file mode 100755 index 0000000..bae80bf --- /dev/null +++ b/src/modules/user/component/recommendations/display/card/card.component.spec.ts @@ -0,0 +1,24 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CardComponent } from './card.component'; + +describe('CardComponent', () => { + let component: CardComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [CardComponent] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CardComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/modules/user/component/recommendations/display/card/card.component.ts b/src/modules/user/component/recommendations/display/card/card.component.ts new file mode 100755 index 0000000..e2ad323 --- /dev/null +++ b/src/modules/user/component/recommendations/display/card/card.component.ts @@ -0,0 +1,18 @@ +import { Component, OnInit, Input } from '@angular/core'; +import { User } from '../../../../../../shared/user/model/user.model'; +@Component({ + selector: 'app-user-recommendations-display-card', + templateUrl: './card.component.html', + styleUrls: ['./card.component.scss'] +}) +export class CardComponent implements OnInit { + @Input() + authors: User[]; + + constructor() { } + ngOnInit() { } + + indexChanged(index) { + console.log(index); + } +} diff --git a/src/modules/user/component/recommendations/display/index.ts b/src/modules/user/component/recommendations/display/index.ts new file mode 100755 index 0000000..b7596fd --- /dev/null +++ b/src/modules/user/component/recommendations/display/index.ts @@ -0,0 +1,5 @@ +import { CardComponent } from './card/card.component'; + +export const DISPLAY_COMPONENTS = [ + CardComponent, +]; diff --git a/src/modules/user/component/recommendations/index.ts b/src/modules/user/component/recommendations/index.ts new file mode 100755 index 0000000..bdfaa19 --- /dev/null +++ b/src/modules/user/component/recommendations/index.ts @@ -0,0 +1,7 @@ +import { DISPLAY_COMPONENTS } from './display'; +import { ListComponent } from './list/list.component'; + +export const RECOMMENDATIONS_COMPONENTS = [ + ...DISPLAY_COMPONENTS, + ListComponent, +]; diff --git a/src/modules/user/component/recommendations/list/list.component.html b/src/modules/user/component/recommendations/list/list.component.html new file mode 100755 index 0000000..e540b13 --- /dev/null +++ b/src/modules/user/component/recommendations/list/list.component.html @@ -0,0 +1,41 @@ +
+ +
+ +
+
    +
  • +
    +
    +
    + + + + + +
    +
    +
    +

    + {{author.nickname}} +

    +

    + {{author.introduction}}

    +
    +
    + + + + +
    +
    +
  • +
+
+
+
+
+
diff --git a/src/modules/user/component/recommendations/list/list.component.scss b/src/modules/user/component/recommendations/list/list.component.scss new file mode 100755 index 0000000..6da603f --- /dev/null +++ b/src/modules/user/component/recommendations/list/list.component.scss @@ -0,0 +1,19 @@ +@mixin calc($property, $expression) { + #{$property}: -moz-calc(#{$expression}); + #{$property}: -webkit-calc(#{$expression}); + #{$property}: calc(#{$expression}); +} + +.recommend { + padding: 0; + + .dataDL-data { + $rest: 160px; + @include calc(width, '100% -'$rest); + + .dataDL-image { + border-radius: 50%; + border: 3px solid #fff; + } + } +} diff --git a/src/modules/user/component/recommendations/list/list.component.ts b/src/modules/user/component/recommendations/list/list.component.ts new file mode 100755 index 0000000..fb2dc27 --- /dev/null +++ b/src/modules/user/component/recommendations/list/list.component.ts @@ -0,0 +1,136 @@ +import { Component, OnInit, Output, EventEmitter } from '@angular/core'; +import { Store, } from '@ngrx/store'; +import { ChangeEvent } from 'ngx-virtual-scroller'; +import { AccountsUtil } from 'src/modules/accounts/util/accounts.util'; +import { UserService } from '../../../service/user.service'; +import { take, map, catchError, finalize, tap } from 'rxjs/operators'; +import { of, Observable } from 'rxjs'; +import { User } from 'src/shared/user/model/user.model'; +import { UserFollowersService } from 'src/modules/user/service/user-followers.service'; +import { UserFollowers } from 'src/shared/user/model/user-followers.model'; + + +@Component({ + selector: 'app-user-recommendations-list', + templateUrl: './list.component.html', + styleUrls: ['./list.component.scss'] +}) +export class ListComponent implements OnInit { + user: User; + recommendAuthorList: User[] = []; + page: number; + @Output() selected = new EventEmitter(); + + constructor(private store: Store, + private userService: UserService, + private userFollowersService: UserFollowersService + ) { + this.page = 0; + } + ngOnInit() { + AccountsUtil.getUser(this.store) + .then((user) => { + this.user = user; + this.fetchRecommendAuthorList(); + }) + .catch((reason) => { + console.log(reason); + }); + } + + fetchRecommendAuthorList() { + // 내 취향을 base로 추천 작가 목록을 가져온다. (무한스크롤) + // 구체적 기획안 필요함. + + this.userService.getAllRecommended(this.user.id, this.page).pipe( + take(1), + // tap(() => { + // // this.store.dispatch(new AppEventsStore.ShowProgressBar()); + // }), + map((users: User[]) => { + // console.log(users); + this.recommendAuthorList.push(...users); + this.page++; + }), + catchError((error) => { + return of(error); + }), + // finalize(() =>{ + // // this.store.dispatch(new AppEventsStore.HideProgressBar()); + // }), + ).subscribe(); + + } + + isFollowing(uid: string) { + + if (this.user) { + return this.userFollowersService.checkLocalFollowersId(this.user, uid); + } else { + console.log('로그인 필요'); + } + + return false; + } + + follow(author: User) { + + if (!this.user || !author) { + return; + } + + const userFollow = { user: this.user, target: author }; + this.userFollowersService.add(userFollow).pipe( + take(1), + tap(() => { + }), + map((res: any) => { + this.userFollowersService.addLocalFollowers(this.user, res, this.store); + }), + catchError((error) => { + return of(error); + }), + finalize(() => { + }) + ).subscribe(); + + } + + unfollow(author: User) { + + if (!this.user) { + return; + } + + const userFollowersId = this.userFollowersService.getLocalFollowersId(this.user, author.id); + + if (!userFollowersId) { + return; + } + + this.userFollowersService.delete(userFollowersId).pipe( + take(1), + tap(() => { + }), + map((res: any) => { + if (res) { + this.userFollowersService.removeLocalFollowers(this.user, userFollowersId, this.store); + } + }), + catchError((error) => { + return of(error); + }), + finalize(() => { + }) + ).subscribe(); + + } + + protected onVSEnd(event: ChangeEvent) { + if (event.start === -1 || event.start === 0) { + // || event.end !== this.articleTagMongodbList.length - 1 + return; + } + this.fetchRecommendAuthorList(); + } +} diff --git a/src/modules/user/component/search-autocomplete/search-autocomplete.component.html b/src/modules/user/component/search-autocomplete/search-autocomplete.component.html new file mode 100755 index 0000000..ea3b60a --- /dev/null +++ b/src/modules/user/component/search-autocomplete/search-autocomplete.component.html @@ -0,0 +1,27 @@ +
+
+ + + +
+ + + + + + + + + + + {{ user.nickname }} + + +
+
+ search +
+
+
diff --git a/src/modules/user/component/search-autocomplete/search-autocomplete.component.scss b/src/modules/user/component/search-autocomplete/search-autocomplete.component.scss new file mode 100755 index 0000000..caceed1 --- /dev/null +++ b/src/modules/user/component/search-autocomplete/search-autocomplete.component.scss @@ -0,0 +1,43 @@ +// for search writer +.search-writer { + $thisHeight: 48px; + display: block; + + .mat-option { + max-height: $thisHeight; + display: flex; + } + + .mat-option-text { + max-height: $thisHeight; + line-height: $thisHeight; + } + + .writer { + max-height: $thisHeight; + line-height: $thisHeight; + height: $thisHeight; + display: flex; + flex-shrink: 0; + overflow: hidden; + + .mat-card-avatar { + margin-top: 8px; + margin-right: 10px; + height: 25px; + width: 25px; + border-radius: 50%; + border: solid 3px #fff; + background-color: #fff; + overflow: hidden; + } + } + + .writer-name { + max-height: $thisHeight; + line-height: $thisHeight; + overflow: hidden; + display: inline-flex; + flex-shrink: 0; + } +} diff --git a/src/modules/user/component/search-autocomplete/search-autocomplete.component.ts b/src/modules/user/component/search-autocomplete/search-autocomplete.component.ts new file mode 100755 index 0000000..b13dc69 --- /dev/null +++ b/src/modules/user/component/search-autocomplete/search-autocomplete.component.ts @@ -0,0 +1,61 @@ +import { Component, OnInit, Output, EventEmitter } from '@angular/core'; +import { FormGroup, FormBuilder } from '@angular/forms'; +import { User } from 'src/shared/user/model/user.model'; +import { Router } from '@angular/router'; +import { of } from 'rxjs'; +import { map, catchError, take, debounceTime, tap, finalize } from 'rxjs/operators'; +import { UserService } from '../../service/user.service'; + +@Component({ + selector: 'app-user-search-autocomplete', + templateUrl: './search-autocomplete.component.html', + styleUrls: ['./search-autocomplete.component.scss'] +}) +export class SearchAutocompleteComponent implements OnInit { + + @Output() selected = new EventEmitter(); + filteredUsers: User[]; + usersForm: FormGroup; + isLoading = false; + + constructor( + private router: Router, + private userService: UserService, + private fb: FormBuilder + ) { + } + + ngOnInit(): void { + this.usersForm = this.fb.group({ + userInput: null + }); + + + this.usersForm.get('userInput') + .valueChanges.subscribe((value) => { + if (value !== null || value.length > 0) { + this.userService.getAllByKeyword(value).pipe( + take(1), + debounceTime(300), + tap(() => { + this.isLoading = true; + }), + map((users: User[]) => { + this.filteredUsers = users; + }), + catchError((error) => { + return of(error); + }), + finalize(() => { + this.isLoading = false; + }), + ).subscribe(); + } + }); + } + + displayFn(user: User) { + if (user) { return user.nickname; } + } + +} diff --git a/src/modules/user/component/settings/settings.component.html b/src/modules/user/component/settings/settings.component.html new file mode 100755 index 0000000..706fea6 --- /dev/null +++ b/src/modules/user/component/settings/settings.component.html @@ -0,0 +1,90 @@ + +
+
+

{{'config.accountSettings' | translate}}

+ + + + + ico_set_01 +
+
+ +
+
+ + + + ico_set_02 +
+
+ +
+
+ + + + ico_set_03 +
{{'config.changeLanguage' | translate}}
+
+ +
+
+ + + + ico_set_04 +
{{'config.setPageVisibility' | translate}}
+
+ +
+
+ +
+
+
+ + +
+
+

{{'config.helpNGuid' | translate}}

+ + + + ico_set_05 +
{{'config.laboratory' | translate}}
+
+ + + ico_set_06 +
{{'config.help' | translate}}
+
+ + + ico_set_07 +
{{'config.reportDamage' | translate}}
+
+ + + ico_set_08 +
{{'config.logout' | translate}}
+
+ +
+
+
+ + +
+
+

{{'config.legalNotices' | translate}}

+ + {{'config.terms' | translate}} + + {{'config.privacyPolicy' | translate}} + +
+
diff --git a/src/modules/user/component/settings/settings.component.scss b/src/modules/user/component/settings/settings.component.scss new file mode 100755 index 0000000..97cb33a --- /dev/null +++ b/src/modules/user/component/settings/settings.component.scss @@ -0,0 +1,3 @@ +.mat-list-text { + padding: 0; +} diff --git a/src/modules/user/component/settings/settings.component.ts b/src/modules/user/component/settings/settings.component.ts new file mode 100755 index 0000000..f14e95b --- /dev/null +++ b/src/modules/user/component/settings/settings.component.ts @@ -0,0 +1,245 @@ +/** + * 파 일 명: settings.component.ts + * 작성일자: 2018-12-19 + * 작 성 자: 최지련 + * 설 명: Settings component를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Component, OnInit, EventEmitter, Output } from '@angular/core'; +import { Router } from '@angular/router'; +import { MatDialog } from '@angular/material'; +import { Observable, of, Observer } from 'rxjs'; +import { take, map, catchError } from 'rxjs/operators'; +import { Store } from '@ngrx/store'; +import { AccountsService } from '../../../../modules/accounts/service/accounts.service'; +import { CookieService } from 'ngx-cookie-service'; +import { UserService } from '../../service/user.service'; +import * as AuthStore from '../../../accounts/store/auth'; + +import { User } from '../../../../shared/user/model/user.model'; +import { LangType } from '../../../../shared/common/type/lang.type'; +import { AgeLimitType } from '../../../../shared/common/type/ageLimit.type'; + +import { AgeLimitDialogComponent } from '../../../common/shared/dialog/age-limit/age-limit.dialog.component'; +import { LanguageDialogComponent } from '../../dialog/language/language.dialog.component'; +import { AccountsUtil } from 'src/modules/accounts/util/accounts.util'; + +import { TranslateService } from '@ngx-translate/core'; + +@Component({ + selector: 'app-user-settings', + templateUrl: './settings.component.html', + styleUrls: ['./settings.component.scss'] +}) +export class SettingsComponent implements OnInit { + /** + * 연령제한 + * + * ageLimitTitle : 팝업 타이틀 + * ageLimits : 선택가능 연령제한 옵션들 및 선택여부 + */ + ageLimitTitle = 'myPage.ageRestriction'; + ageLimits = [ + { type: AgeLimitType.ALL, label: 'myPage.allAges', checked: false }, + { type: AgeLimitType.SOFT, label: 'myPage.expressMildSexual', checked: false }, + { type: AgeLimitType.HARD, label: 'myPage.expressViolenceAndCrotes', checked: false } + ]; + + /** + * 표시 언어 변경 + * + * languageTitle : 팝업 타이틀 + * chosenItem : 선택했던 언어값 + * languages : 선택가능 언어 옵션들 + */ + languageTitle = 'config.changeLanguage'; + chosenItem = { type: LangType.KO, label: 'config.korean' }; + languages = [ + { type: LangType.KO, label: 'config.korean' }, + { type: LangType.JP, label: 'config.japanese' }, + { type: LangType.EN, label: 'config.english' } + ]; + + @Output() + changedSaveObservable = new EventEmitter>(); + + user: User; + + private readonly _saveObservable = Observable.create(async (observer: Observer) => { + this.userService.update(this.user) + .pipe( + take(1), + map((user) => { + console.log(user); + + // 해당 화면 refresh + this.init(user); + this.store.dispatch(new AuthStore.ChangeUser({ user })); + + observer.next(user); + }), + catchError((error) => { + return of(error); + }), + ).subscribe(); + }); + + constructor( + private dialog: MatDialog, + private router: Router, + private userService: UserService, + private store: Store, + private accountsService: AccountsService, + private cookieService: CookieService, + private translateService: TranslateService + ) { } + + ngOnInit(): void { + AccountsUtil.getUser(this.store) + .then((user) => { + this.init(user); + }) + .catch((reason) => { + console.log(reason); + }); + } + + init(user: User) { + console.log(`[SettingsComponent] init - user :: ${JSON.stringify(user)}`); + + this.user = user; + + // 표시언어 세팅 + this.translateService.use(this.user.displayLanguage.toLocaleLowerCase()); + + // 값 초기화 수행 (ageLimit) + !this.user.ageLimit ? (this.ageLimits[0].checked = true) : (this.ageLimits[0].checked = false); + this.user.ageLimit % 2 ? (this.ageLimits[1].checked = true) : (this.ageLimits[1].checked = false); + Math.floor(this.user.ageLimit / 2) ? (this.ageLimits[2].checked = true) : (this.ageLimits[2].checked = false); + + // 값 초기화 수행 (displayLanguage) + for (const key in this.languages) { + if (this.languages[key].type === this.user.displayLanguage) { + this.chosenItem = this.languages[key]; + } + } + } + + onClick(event, type) { + console.log(`[SettingComponent] onClick :: ${type}`); + + switch (type) { + case 'myFavor': + this.router.navigate(['user/favor']); + break; + case 'changeAgeLimit': + // do nothing + break; + case 'changeDisplayLanguage': + this.openDialog(type); + break; + case 'pagePublicYN': + // do nothing + break; + case 'laboratory': + case 'help': + case 'reportDamage': + break; + case 'logout': + this.logout(); + break; + case 'terms': + case 'privacyPolicy': + break; + } + } + + openDialog(type) { + switch (type) { + case 'changeAgeLimit': + const dialogAgeLimitRef = this.dialog.open(AgeLimitDialogComponent, { + data: { + title: this.ageLimitTitle, + ageLimits: this.ageLimits + } + }); + + dialogAgeLimitRef.afterClosed().subscribe(result => { + console.log( + `[AgeLimitDialogComponent] was closed :: ${JSON.stringify(result)}` + ); + + let tempAgeLimits = 0; + + if (result) { + for (const key in result) { + if (result[key].checked) { + tempAgeLimits += result[key].type; + } + } + // console.log(`[openDialog] tempAgeLimits :: ${tempAgeLimits}`); + + if (tempAgeLimits !== this.user.ageLimit) { + this.user.ageLimit = tempAgeLimits; + this.changedSaveObservable.emit(this._saveObservable); + } + } + }); + break; + case 'changeDisplayLanguage': + // 현재 선택값 제외 + for (const key in this.languages) { + if (this.languages[key].type === this.chosenItem.type) { + this.languages.splice(this.languages.indexOf(this.chosenItem), 1); + } + } + + const dialogLanguageRef = this.dialog.open(LanguageDialogComponent, { + data: { + title: this.languageTitle, + chosenItem: this.chosenItem, + languages: this.languages.sort() + } + }); + + dialogLanguageRef.afterClosed().subscribe(result => { + console.log( + `[LanguageDialogComponent] was closed :: ${JSON.stringify(result)}` + ); + + if (result) { + if (this.chosenItem.type !== result.type) { + this.languages.push(this.chosenItem); + + this.user.displayLanguage = result.type; + this.changedSaveObservable.emit(this._saveObservable); + } + } + }); + break; + } + } + + onClickToggleBtn(event, type) { + console.log(`[SettingsComponent] onClickToggleBtn :: ${type}`); + + event.preventDefault(); + + switch (type) { + case 'changeAgeLimit': + this.openDialog(type); + break; + case 'pagePublicYN': + this.user.articlePublicYN = !this.user.articlePublicYN; + this.changedSaveObservable.emit(this._saveObservable); + break; + } + } + + logout(): any { + // this.cookieService.delete('jwt', '/'); + } +} diff --git a/src/modules/user/dialog/image-modify/image-modify.bottomsheet.component.html b/src/modules/user/dialog/image-modify/image-modify.bottomsheet.component.html new file mode 100755 index 0000000..7bc205c --- /dev/null +++ b/src/modules/user/dialog/image-modify/image-modify.bottomsheet.component.html @@ -0,0 +1,9 @@ + + + 이미지 변경 + + + + 이미지 삭제 + + diff --git a/src/modules/user/dialog/image-modify/image-modify.bottomsheet.component.scss b/src/modules/user/dialog/image-modify/image-modify.bottomsheet.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/modules/user/dialog/image-modify/image-modify.bottomsheet.component.ts b/src/modules/user/dialog/image-modify/image-modify.bottomsheet.component.ts new file mode 100755 index 0000000..a9c3bde --- /dev/null +++ b/src/modules/user/dialog/image-modify/image-modify.bottomsheet.component.ts @@ -0,0 +1,41 @@ +import { Component, Inject, ElementRef } from '@angular/core'; +import { MatBottomSheetRef, MAT_BOTTOM_SHEET_DATA } from '@angular/material'; + +// tslint:disable-next-line:no-empty-interface +export interface ImageModifyBottomSheetData { +} + +export enum ImageModify { + Change = 'Change', + Delete = 'Delete', +} + +export interface ImageModifyBottomSheetResult { + choice: ImageModify; +} + +@Component({ + selector: 'app-user-image-modify-bottomsheet', + templateUrl: './image-modify.bottomsheet.component.html', + styleUrls: ['./image-modify.bottomsheet.component.scss'] +}) +export class ImageModifyBottomSheetComponent { + + fileInput: ElementRef; + + constructor( + private bottomSheetRef: MatBottomSheetRef, + @Inject(MAT_BOTTOM_SHEET_DATA) public data: any, + ) { + this.fileInput = data.fileInput; + } + + onClick(choice: ImageModify) { + if (choice === ImageModify.Change) { + this.fileInput.nativeElement.click(); + } + this.bottomSheetRef.dismiss({ + choice: choice, + }); + } +} diff --git a/src/modules/user/dialog/index.ts b/src/modules/user/dialog/index.ts new file mode 100755 index 0000000..fb1f44b --- /dev/null +++ b/src/modules/user/dialog/index.ts @@ -0,0 +1,9 @@ +import { ImageModifyBottomSheetComponent } from './image-modify/image-modify.bottomsheet.component'; +import { LanguageDialogComponent } from './language/language.dialog.component'; +import { UnfollowDialogComponent } from './unfollow/unfollow.dialog.component'; + +export const DIALOGS = [ + ImageModifyBottomSheetComponent, + LanguageDialogComponent, + UnfollowDialogComponent +]; diff --git a/src/modules/user/dialog/language/language.dialog.component.html b/src/modules/user/dialog/language/language.dialog.component.html new file mode 100755 index 0000000..b026be5 --- /dev/null +++ b/src/modules/user/dialog/language/language.dialog.component.html @@ -0,0 +1,17 @@ +

+ {{ data.title | translate }} +

+ +
+ + + {{ data.chosenItem.label | translate }} + + + + {{ language.label | translate }} + + +
diff --git a/src/modules/user/dialog/language/language.dialog.component.scss b/src/modules/user/dialog/language/language.dialog.component.scss new file mode 100755 index 0000000..a27dcf9 --- /dev/null +++ b/src/modules/user/dialog/language/language.dialog.component.scss @@ -0,0 +1,10 @@ +.example-radio-group { + display: inline-flex; + flex-direction: column; + overflow-y: hidden; + padding: 5px; +} + +.example-radio-button { + margin: 10px; +} diff --git a/src/modules/user/dialog/language/language.dialog.component.spec.ts b/src/modules/user/dialog/language/language.dialog.component.spec.ts new file mode 100755 index 0000000..cf681b0 --- /dev/null +++ b/src/modules/user/dialog/language/language.dialog.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { LanguageDialogComponent } from './language.dialog.component'; + +describe('LanguageDialogComponent', () => { + let component: LanguageDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [LanguageDialogComponent] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(LanguageDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/modules/user/dialog/language/language.dialog.component.ts b/src/modules/user/dialog/language/language.dialog.component.ts new file mode 100755 index 0000000..8d1abb4 --- /dev/null +++ b/src/modules/user/dialog/language/language.dialog.component.ts @@ -0,0 +1,40 @@ +/** + * 파 일 명: language.dialog.component.ts + * 작성일자: 2018-12-17 + * 작 성 자: 최지련 + * 설 명: Language Dialog component를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Component, OnInit, Inject, Input, Output, EventEmitter } from '@angular/core'; +import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material'; + +export interface DialogLanguageData { + title: string; + chosenItem: string; + languages: Array; +} + +@Component({ + selector: 'app-language-dialog', + templateUrl: './language.dialog.component.html', + styleUrls: [ + './language.dialog.component.scss' + ] +}) +export class LanguageDialogComponent implements OnInit { + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: DialogLanguageData + ) { } + + ngOnInit(): void { + } + + onClick(item) { + // console.log(`[LanguageDialogComponent] onClick :: ${JSON.stringify(item)}`); + this.dialogRef.close(item); + } +} diff --git a/src/modules/user/dialog/unfollow/unfollow.dialog.component.html b/src/modules/user/dialog/unfollow/unfollow.dialog.component.html new file mode 100755 index 0000000..65eb88a --- /dev/null +++ b/src/modules/user/dialog/unfollow/unfollow.dialog.component.html @@ -0,0 +1,15 @@ +
+
+ + +
+

+ {{ user.nickname }} + + +

+
+ + +
+
diff --git a/src/modules/user/dialog/unfollow/unfollow.dialog.component.scss b/src/modules/user/dialog/unfollow/unfollow.dialog.component.scss new file mode 100755 index 0000000..e69de29 diff --git a/src/modules/user/dialog/unfollow/unfollow.dialog.component.ts b/src/modules/user/dialog/unfollow/unfollow.dialog.component.ts new file mode 100755 index 0000000..957e2e6 --- /dev/null +++ b/src/modules/user/dialog/unfollow/unfollow.dialog.component.ts @@ -0,0 +1,29 @@ +import { Component, Inject } from '@angular/core'; +import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material'; +import { User } from '../../../../shared/user/model/user.model'; + +export interface UnfollowDialogData { + user: User; +} + +export interface UnfollowDialogResult { + selected: boolean; +} + +@Component({ + selector: 'app-user-unfollow-dialog', + templateUrl: './unfollow.dialog.component.html', + styleUrls: ['./unfollow.dialog.component.scss'] +}) +export class UnfollowDialogComponent { + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: UnfollowDialogData + ) { } + + onClickClose(selected: boolean): void { + this.dialogRef.close({ + selected: selected, + }); + } +} diff --git a/src/modules/user/service/index.ts b/src/modules/user/service/index.ts new file mode 100755 index 0000000..e5aeffa --- /dev/null +++ b/src/modules/user/service/index.ts @@ -0,0 +1,9 @@ +import { UserService } from './user.service'; +import { UserFavorTagService } from './user-favor-tag.service'; +import { UserFollowersService } from './user-followers.service'; + +export const SERVICES = [ + UserService, + UserFavorTagService, + UserFollowersService, +]; diff --git a/src/modules/user/service/user-favor-tag.service.ts b/src/modules/user/service/user-favor-tag.service.ts new file mode 100755 index 0000000..7074ac9 --- /dev/null +++ b/src/modules/user/service/user-favor-tag.service.ts @@ -0,0 +1,30 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { environment } from '../../../environments/environment'; + +import { AbstractRestService } from '../../common/util/service/abstract-rest.service'; +import { UserFavorTag } from '../../../shared/user/model/user-favor-tag.model'; +import { httpOptions } from '../../common/util/service/abstract.service'; + +@Injectable() +export class UserFavorTagService extends AbstractRestService { + public constructor(httpClient: HttpClient) { + super(httpClient, environment.apiEntryPoint + '/user/favor_tag'); + } + + public updateAll(data: { added: UserFavorTag[], deleted: UserFavorTag[] }): Observable { + return this.httpClient.post( + `${this.apiEntryPoint}`, + data, + httpOptions + ); + } + + public getAllByUid(uid: string): Observable { + return this.httpClient.get( + `${this.apiEntryPoint}/${uid}`, + ); + } + +} diff --git a/src/modules/user/service/user-followers.service.ts b/src/modules/user/service/user-followers.service.ts new file mode 100755 index 0000000..8f11973 --- /dev/null +++ b/src/modules/user/service/user-followers.service.ts @@ -0,0 +1,125 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { environment } from '../../../environments/environment'; + +import { AbstractRestService } from '../../common/util/service/abstract-rest.service'; +import { UserFollowers } from '../../../shared/user/model/user-followers.model'; +import { User } from 'src/shared/user/model/user.model'; +import { Store } from '@ngrx/store'; +import * as AuthStore from '../../accounts/store/auth'; + +@Injectable() +export class UserFollowersService extends AbstractRestService { + + private localFollowingMap: Map; + + public constructor(httpClient: HttpClient) { + super(httpClient, environment.apiEntryPoint + '/user/followers'); + + this.localFollowingMap = new Map(); + } + + public getAllFollowers(uid: string): Observable { + return this.httpClient.get(`${this.apiEntryPoint}/followers/${uid}`); + } + + public getAllFollowing(uid: string): Observable { + return this.httpClient.get(`${this.apiEntryPoint}/following/${uid}`); + } + + public deleteFollowing(id: string): Observable { + return this.httpClient.delete(`${this.apiEntryPoint}/${id}`); + } + + private findUserFollowers(user: User, uid: string): UserFollowers { + + if (!user.followingList) { + return null; + } + + this.generateUserFollowersMap(user); + + if (this.localFollowingMap.has(uid)) { + return this.localFollowingMap.get(uid); + } + + return null; + } + + private generateUserFollowersMap(user: User) { + if (this.localFollowingMap.size === user.followingList.length) { + return; + } + + this.localFollowingMap.clear(); + + user.followingList.map((userFollowers: UserFollowers) => { + this.localFollowingMap.set(userFollowers.target.id, userFollowers); + }); + } + + public checkLocalFollowersId(user: User, uid: string): boolean { + + const resultId = this.getLocalFollowersId(user, uid); + + if (resultId) { + return true; + } + + return false; + } + + public getLocalFollowersId(user: User, uid: string): string { + + let userFollowersId = null; + + if (!user) { + return userFollowersId; + } + + if (user.followingList === null || user.followingList.length <= 0) { + return userFollowersId; + } + + const model = this.findUserFollowers(user, uid); + if (model) { + userFollowersId = model.id; + } + + return userFollowersId; + } + + public addLocalFollowers(user: User, resFollowers: UserFollowers, store: Store): void { + if (!user.followingList) { + user.followingList = []; + } + + user.followingList.push(resFollowers); + this.localFollowingMap.clear(); + // change user + store.dispatch(new AuthStore.ChangeUser({ user: user })); + } + + public removeLocalFollowers(user: User, userFollowersId: string, store: Store): void { + if (!user.followingList) { + return; + } + + let removeIdx = -1; + user.followingList.find((followers: UserFollowers, idx: number): boolean => { + if (followers.id === userFollowersId) { + removeIdx = idx; + return true; + } + return false; + }); + + if (0 <= removeIdx) { + user.followingList.splice(removeIdx, 1); + } + // change user + store.dispatch(new AuthStore.ChangeUser({ user: user })); + } + +} diff --git a/src/modules/user/service/user.service.ts b/src/modules/user/service/user.service.ts new file mode 100755 index 0000000..8beb041 --- /dev/null +++ b/src/modules/user/service/user.service.ts @@ -0,0 +1,30 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { environment } from '../../../environments/environment'; +import { User } from '../../../shared/user/model/user.model'; +import { AbstractRestService } from '../../common/util/service/abstract-rest.service'; + +@Injectable() +export class UserService extends AbstractRestService { + public constructor(httpClient: HttpClient) { + super(httpClient, environment.apiEntryPoint + '/user'); + } + + public getByNickname(nickname: string): Observable { + return this.httpClient.get(`${this.apiEntryPoint}/nickname/${nickname}`); + } + + public getAllByWriter(nickname: string): Observable { + return this.httpClient.get(`${this.apiEntryPoint}/search/w/${nickname}`); + } + + public getAllRecommended(uid: string, page: number): Observable { + return this.httpClient.get(`${this.apiEntryPoint}/recommendations/${uid}/${page}`); + } + + public getAllByKeyword(keyword: string): Observable { + return this.httpClient.get(`${this.apiEntryPoint}/search/${keyword}`); + } + +} diff --git a/src/modules/user/store/index.ts b/src/modules/user/store/index.ts new file mode 100755 index 0000000..61a68f5 --- /dev/null +++ b/src/modules/user/store/index.ts @@ -0,0 +1,16 @@ +import { + createSelector, + createFeatureSelector, +} from '@ngrx/store'; + +// tslint:disable-next-line:no-empty-interface +export interface State { +} + +export const REDUCERS = { +}; + +export const EFFECTS = [ +]; + +export const selectState = createFeatureSelector('user'); diff --git a/src/modules/user/store/profile/index.ts b/src/modules/user/store/profile/index.ts new file mode 100755 index 0000000..eb3aaef --- /dev/null +++ b/src/modules/user/store/profile/index.ts @@ -0,0 +1 @@ +export * from './profile.action'; diff --git a/src/modules/user/store/profile/profile.action.ts b/src/modules/user/store/profile/profile.action.ts new file mode 100755 index 0000000..deb2589 --- /dev/null +++ b/src/modules/user/store/profile/profile.action.ts @@ -0,0 +1,31 @@ +import { Action } from '@ngrx/store'; + +export enum ActionType { + GotoProfile = '[user.profile] GotoProfile', + GotoProfileEdit = '[user.profile] GotoProfileEdit', + GotoSettings = '[user.profile] GotoSettings', +} + +export class GotoProfile implements Action { + readonly type = ActionType.GotoProfile; + + constructor(public payload: { uid: string }) { } +} + +export class GotoProfileEdit implements Action { + readonly type = ActionType.GotoProfileEdit; + + constructor() { } +} + +export class GotoSettings implements Action { + readonly type = ActionType.GotoSettings; + + constructor() { } +} + +export type Actions = + | GotoProfile + | GotoProfileEdit + | GotoSettings + ; diff --git a/src/modules/user/user-store.module.ts b/src/modules/user/user-store.module.ts new file mode 100755 index 0000000..34c5510 --- /dev/null +++ b/src/modules/user/user-store.module.ts @@ -0,0 +1,17 @@ +import { NgModule, ModuleWithProviders } from '@angular/core'; +import { StoreModule } from '@ngrx/store'; +import { EffectsModule } from '@ngrx/effects'; + +import { + REDUCERS, + EFFECTS, +} from './store'; + +@NgModule({ + imports: [ + StoreModule.forFeature('user', REDUCERS), + EffectsModule.forFeature(EFFECTS), + ], +}) +export class UserStoreModule { +} diff --git a/src/modules/user/user.module.ts b/src/modules/user/user.module.ts new file mode 100755 index 0000000..4ff4268 --- /dev/null +++ b/src/modules/user/user.module.ts @@ -0,0 +1,50 @@ +import { NgModule, ModuleWithProviders } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { TranslateModule } from '@ngx-translate/core'; +import { ClipboardModule } from 'ngx-clipboard'; + +import { SharedModule } from '../common/shared/shared.module'; +import { AttachmentsModule } from '../attachments/attachments.module'; +import { ArticleModule } from '../article/article.module'; + +import { COMPONENTS } from './component'; +import { DIALOGS } from './dialog'; +import { SERVICES } from './service'; +import { NgxHmCarouselModule } from 'ngx-hm-carousel'; +import { UserSupportModule } from '../../modules/user-support/user-support.module'; +import { UserStoreModule } from './user-store.module'; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + ReactiveFormsModule, + TranslateModule, + ClipboardModule, + SharedModule, + AttachmentsModule, + ArticleModule, + NgxHmCarouselModule, + UserSupportModule, + ], + exports: [...COMPONENTS], + declarations: [...COMPONENTS, ...DIALOGS], + entryComponents: [...DIALOGS] +}) +export class UserModule { + public static forRoot(): ModuleWithProviders { + return { + ngModule: UserRootModule, + providers: [...SERVICES] + }; + } +} + +@NgModule({ + imports: [ + UserStoreModule, + ], + exports: [] +}) +export class UserRootModule { } diff --git a/src/polyfills.ts b/src/polyfills.ts new file mode 100755 index 0000000..ba644ea --- /dev/null +++ b/src/polyfills.ts @@ -0,0 +1,81 @@ +/** + * This file includes polyfills needed by Angular and is loaded before the app. + * You can add your own extra polyfills to this file. + * + * This file is divided into 2 sections: + * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. + * 2. Application imports. Files imported after ZoneJS that should be loaded before your main + * file. + * + * The current setup is for so-called "evergreen" browsers; the last versions of browsers that + * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), + * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. + * + * Learn more in https://angular.io/guide/browser-support + */ + +/*************************************************************************************************** + * BROWSER POLYFILLS + */ + +/** IE9, IE10 and IE11 requires all of the following polyfills. **/ +import 'core-js/es6/symbol'; +import 'core-js/es6/object'; +import 'core-js/es6/function'; +import 'core-js/es6/parse-int'; +import 'core-js/es6/parse-float'; +import 'core-js/es6/number'; +import 'core-js/es6/math'; +import 'core-js/es6/string'; +import 'core-js/es6/date'; +import 'core-js/es6/array'; +import 'core-js/es6/regexp'; +import 'core-js/es6/map'; +import 'core-js/es7/map'; +import 'core-js/es6/weak-map'; +import 'core-js/es6/set'; + +/** + * If the application will be indexed by Google Search, the following is required. + * Googlebot uses a renderer based on Chrome 41. + * https://developers.google.com/search/docs/guides/rendering + **/ +// import 'core-js/es6/array'; + +/** IE10 and IE11 requires the following for NgClass support on SVG elements */ +// import 'classlist.js'; // Run `npm install --save classlist.js`. + +/** IE10 and IE11 requires the following for the Reflect API. */ +// import 'core-js/es6/reflect'; + +/** + * Web Animations `@angular/platform-browser/animations` + * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. + * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). + **/ +// import 'web-animations-js'; // Run `npm install --save web-animations-js`. + +/** + * By default, zone.js will patch all possible macroTask and DomEvents + * user can disable parts of macroTask/DomEvents patch by setting following flags + */ + +// (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame +// (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick +// (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames + +/* +* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js +* with the following flag, it will bypass `zone.js` patch for IE/Edge +*/ +// (window as any).__Zone_enable_cross_context_check = true; + +/*************************************************************************************************** + * Zone JS is required by default for Angular itself. + */ +import 'zone.js/dist/zone'; // Included with Angular CLI. + + +/*************************************************************************************************** + * APPLICATION IMPORTS + */ diff --git a/src/server/Server.ts b/src/server/Server.ts new file mode 100755 index 0000000..c2e3e8c --- /dev/null +++ b/src/server/Server.ts @@ -0,0 +1,237 @@ +import { + GlobalAcceptMimesMiddleware, + ServerLoader, + ServerSettings +} from '@tsed/common'; +import '@tsed/typeorm'; +import { TypeORMService } from '@tsed/typeorm'; +import { $log } from 'ts-log-debug'; +import * as path from 'path'; +import * as fse from 'fs-extra'; +import * as express from 'express'; +import '@tsed/mongoose'; +import * as unzipper from 'unzipper'; +import { MongoMemoryServer } from 'mongodb-memory-server'; +import { userImageConfig } from '../config/images/user.config'; +import { articleImageConfig } from '../config/images/article.config'; + +const rootPath = path.resolve(__dirname); +const devPath = path.resolve(rootPath, '..', '..', 'dev'); +const devDatabasePath = path.resolve(devPath, 'database.sqlite'); +const testDatabasePath = path.resolve(__dirname, 'test', 'database.sqlite'); +const devStoragePath = path.resolve(devPath, 'storage'); +const testStoragePath = path.resolve(__dirname, 'test', 'storage.zip'); + +@ServerSettings({ + rootDir: rootPath, + port: 8080, + debug: false, + uploadDir: path.resolve(devPath, 'upload'), + acceptMimes: ['application/json'], + mount: { + '/api': [ + '${rootDir}/modules/accounts/controller/*.ts', + '${rootDir}/modules/advertisements/controller/*.ts', + '${rootDir}/modules/article/controller/*.ts', + '${rootDir}/modules/attachments/controller/*.ts', + '${rootDir}/modules/cartoons/controller/*.ts', + '${rootDir}/modules/comments/controller/*.ts', + '${rootDir}/modules/contents/controller/*.ts', + '${rootDir}/modules/emotions/controller/*.ts', + '${rootDir}/modules/illustrations/controller/*.ts', + '${rootDir}/modules/meta/controller/*.ts', + '${rootDir}/modules/novel/controller/*.ts', + '${rootDir}/modules/tag/controller/*.ts', + '${rootDir}/modules/user/controller/*.ts', + '${rootDir}/modules/user-analysis/controller/*.ts', + '${rootDir}/modules/user-support/controller/*.ts', + ], + '/oauth': ['${rootDir}/modules/oauth/controller/*.ts'] + }, + componentsScan: [ + '${rootDir}/modules/**/converter/*.ts', + '${rootDir}/modules/**/middleware/*.ts', + '${rootDir}/modules/**/service/*.ts', + '${rootDir}/mongodb/**/service/*.ts' + ], + typeorm: [ + { + name: 'sqlite', + type: 'sqlite', + database: devDatabasePath, + synchronize: true, + autoReconnect: true, + reconnectTries: Number.MAX_VALUE, + reconnectInterval: 1000, + useNewUrlParser: true, + useUTC: true, + entities: ['${rootDir}/modules/**/entity/*.ts'], + migrations: ['${rootDir}/modules/**/migrations/*.ts'], + subscribers: ['${rootDir}/modules/**/subscriber/*.ts'] + } + ], + mongoose: { + urls: { + mongodb: { + url: 'mongodb://127.0.0.1:59211/venture', + connectionOptions: { + autoReconnect: true, + reconnectTries: Number.MAX_VALUE, + reconnectInterval: 1000, + useNewUrlParser: true, + } + }, + } + }, + passport: { + local: { + userProperty: 'user' + }, + jwt: { + privateKey: fse.readFileSync( + path.resolve(rootPath, '..', 'assets', 'key', 'private.key') + ), + publicKey: fse.readFileSync( + path.resolve(rootPath, '..', 'assets', 'key', 'public.pem') + ), + signOptions: { + algorithm: 'RS256', + expiresIn: '7d', + issuer: 'third.venture.com' + }, + cookieOption: { + httpOnly: true, + sameSite: true + } + }, + oauth: { + common: { + callbackURL: 'http://localhost:4200/accounts/authentication/callback' + }, + oneall: { + subdomain: 'chailocalhost', + publicKey: 'fae43a82-a9eb-44cc-9617-acf72afcce85', + privateKey: '5cdf9765-c21d-4189-b3b2-6c50c904d08d' + }, + kakao: { + clientID: '8c99da56e5ea1ca39e29dc1d8bc38e59', + clientSecret: '', + callbackURL: '/oauth/authentication/kakao' + }, + naver: { + clientID: 'gZEXfq7YYDVF4T9XDuRb', + clientSecret: 'X_imaoBHz6', + callbackURL: 'http://localhost:8080/oauth/authentication/naver' + } + } + }, + attachments: { + storageDir: path.resolve(devPath, 'storage') + }, + images: { + user: userImageConfig, + article: articleImageConfig, + } +}) +export class Server extends ServerLoader { + private mongoMemoryServer: MongoMemoryServer; + + constructor() { + super(); + } + + $onInit(): void | Promise { + return new Promise(async (resolve, reject) => { + if (!fse.existsSync(devPath)) { + fse.mkdirpSync(devPath); + } + + if (fse.existsSync(devDatabasePath)) { + try { + fse.removeSync(devDatabasePath); + $log.info(`Database file[${devDatabasePath}] have been removed`); + fse.copyFileSync(testDatabasePath, devDatabasePath); + $log.info(`Database file[${devDatabasePath}] have been initialized`); + + fse.removeSync(devStoragePath); + $log.info(`Storage dir[${devDatabasePath}] have been removed`); + + fse.createReadStream(testStoragePath) + .pipe(unzipper.Extract({ path: devPath })) + .on('finish', (err) => { + $log.info(`Storage file[${devStoragePath}] have been initialized`); + }); + } catch (error) { + $log.info(`Dev Initializing skiped`); + } + } + + this.mongoMemoryServer = new MongoMemoryServer({ + instance: { + port: 59211, + dbName: 'venture' + } + }); + + this.mongoMemoryServer + .getConnectionString() + .then(mongoUri => { + return resolve(); + }) + .catch(reason => { + return reject(reason); + }); + }); + } + + /** + * This method let you configure the middleware required by your application to works. + */ + $onMountingMiddlewares(): void | Promise { + return new Promise((resolve, reject) => { + const cookieParser = require('cookie-parser'), + bodyParser = require('body-parser'), + compress = require('compression'), + cors = require('cors'), + methodOverride = require('method-override'); + + this.use(GlobalAcceptMimesMiddleware) + .use(cookieParser()) + .use(compress({})) + .use(cors()) + .use(methodOverride()) + .use(bodyParser.json()) + .use( + bodyParser.urlencoded({ + extended: true + }) + ); + + const typeORMService = this.injectorService.get( + TypeORMService + ); + let retryTime = 0; + const h = setInterval(() => { + if (typeORMService.has('sqlite')) { + $log.info(`acquiring a database connection success`); + clearInterval(h); + return resolve(); + } + ++retryTime; + $log.info(`attempting database connection: ${retryTime}`); + if (3 < retryTime) { + clearInterval(h); + return reject('unable to acquire database connection'); + } + }, 1000); + }); + } + + $onReady() { + $log.debug('Server initialized'); + } + + $onServerInitError(error): any { + $log.error('Server encounter an error =>', error); + } +} diff --git a/src/server/main.ts b/src/server/main.ts new file mode 100755 index 0000000..b2f295f --- /dev/null +++ b/src/server/main.ts @@ -0,0 +1,12 @@ +import { $log } from 'ts-log-debug'; +import 'multer'; + +import { Server } from './Server'; + +$log.debug('Start server.....'); +const s = new Server(); + +s + .start().catch((er) => { + $log.error(er); + }); diff --git a/src/server/modules/accounts/controller/accounts.controller.ts b/src/server/modules/accounts/controller/accounts.controller.ts new file mode 100755 index 0000000..a6083bb --- /dev/null +++ b/src/server/modules/accounts/controller/accounts.controller.ts @@ -0,0 +1,59 @@ +/** + * 파 일 명: account.controller.ts + * 작성일자: 2018-12-20 + * 작 성 자: 박병준 + * 설 명: 계정 인증 entry point를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { + Controller, + Req, + Res, + Get, + ServerSettingsService, + BeforeRoutesInit, + Authenticated, +} from '@tsed/common'; +import * as express from 'express'; +import { UserService } from '../../user/service/user.service'; +import { User } from '../../user/entity/user.entity'; +import { UserFollowersService } from '../../user/service/user-followers.service'; + + +@Controller('/accounts') +export class AccountsController implements BeforeRoutesInit { + protected passportOAuthCommonOptions: any; + + constructor( + protected serverSettingsService: ServerSettingsService, + protected userService: UserService, + protected userFollowersService: UserFollowersService, + ) { + } + + $beforeRoutesInit() { + this.passportOAuthCommonOptions = this.serverSettingsService.get(`passport.oauth.common`) || {} as any; + } + + @Get('/login') + @Authenticated({ + roles: ['R_USER'] + }) + async get( + @Req() req: express.Request, + @Res() res: express.Response, + ) { + const user: User = req['user']; + + if (user) { + // FIXME:: + user.followingList = await this.userFollowersService.findAllFollowing(user.id); + return user; + } + + res.status(203).send('No Content'); + } +} diff --git a/src/server/modules/accounts/middleware/auth.middleware.ts b/src/server/modules/accounts/middleware/auth.middleware.ts new file mode 100755 index 0000000..3ab4493 --- /dev/null +++ b/src/server/modules/accounts/middleware/auth.middleware.ts @@ -0,0 +1,63 @@ +import { + AuthenticatedMiddleware, EndpointInfo, EndpointMetadata, + OverrideMiddleware, Next, Req, Res, ServerSettingsService, +} from '@tsed/common'; +import { Forbidden } from 'ts-httpexceptions'; +import { $log } from 'ts-log-debug'; +import * as express from 'express'; +import * as Passport from 'passport'; +import { UserService } from '../../user/service/user.service'; + +@OverrideMiddleware(AuthenticatedMiddleware) +export class AuthMiddleware { + protected passportOAuthCommonOptions: any; + + constructor( + protected serverSettingsService: ServerSettingsService, + protected userService: UserService, + ) { + } + + + $beforeRoutesInit() { + this.passportOAuthCommonOptions = this.serverSettingsService.get(`passport.oauth.common`) || {} as any; + } + + use( + @EndpointInfo() endpoint: EndpointMetadata, + @Req() req: express.Request, + @Res() res: express.Response, + @Next() next: express.NextFunction) { + + const options = endpoint.store.get(AuthenticatedMiddleware) || {}; + $log.debug('AuthMiddleware =>', options); + $log.debug('AuthMiddleware isAuthenticated ? =>', req.isAuthenticated()); + + Passport.authenticate('jwt', { session: false }, async (err, payload) => { + if (err) { + return next(err); + } + + const roles: string[] = options.roles; + + if (undefined === roles || 0 === roles.length) { + return next(); + } + + const userRoles: string[] = payload.roles; + if (undefined === userRoles || 0 === userRoles.length) { + return next(new Forbidden('Forbidden')); + } + + for (const role of roles) { + if (userRoles.includes(role)) { + req.user = await this.userService.find(payload.sub); + return next(); + } + } + + return next(new Forbidden('Forbidden')); + })(req, res); + } + +} diff --git a/src/server/modules/advertisements/controller/advertisements.controller.ts b/src/server/modules/advertisements/controller/advertisements.controller.ts new file mode 100755 index 0000000..81c199c --- /dev/null +++ b/src/server/modules/advertisements/controller/advertisements.controller.ts @@ -0,0 +1,39 @@ +/** + * 파 일 명: advertisements.controller.ts + * 작성일자: 2019-01-20 + * 작 성 자: 박병준 + * 설 명: Advertisements entry point를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { + Controller, + Req, + Res, + Get, + Required, + PathParams, + ServerSettingsService, + BeforeRoutesInit, + Authenticated, +} from '@tsed/common'; +import * as express from 'express'; +import { AbstractController } from '../../common/util/controller/abstract.controller'; +import { AdvertisementsService } from '../service/advertisements.service'; +import { Advertisements } from '../entity/advertisements.entity'; + +@Controller('/advertisements') +export class AdvertisementsController extends AbstractController implements BeforeRoutesInit { + + constructor( + private advertisementsService: AdvertisementsService, + ) { + super(advertisementsService); + } + + $beforeRoutesInit() { + } + +} diff --git a/src/server/modules/advertisements/entity/advertisements.entity.ts b/src/server/modules/advertisements/entity/advertisements.entity.ts new file mode 100755 index 0000000..87381b1 --- /dev/null +++ b/src/server/modules/advertisements/entity/advertisements.entity.ts @@ -0,0 +1,45 @@ +/** + * 파 일 명: advertisements.entity.ts + * 작성일자: 2019-01-20 + * 작 성 자: 박병준 + * 설 명: Advertisements 관리 Entity 스키마를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Property, Required } from '@tsed/common'; +import { Entity, Column, Index } from 'typeorm'; + +import { Base } from '../../common/util/entity/base.entity'; + +@Entity() +export class Advertisements extends Base { + @Column({ + type: 'varchar', + length: 50 + }) + @Index('IDX_Advertisements_title') + @Required() + @Property() + title: string; + + @Column() + @Required() + @Property() + contents: string; + + @Column() + @Property() + startDate: Date; + + @Column() + @Property() + endDate: Date; + + @Column() + @Required() + @Property() + url: string; + +} diff --git a/src/server/modules/advertisements/service/advertisements.service.ts b/src/server/modules/advertisements/service/advertisements.service.ts new file mode 100755 index 0000000..77099d1 --- /dev/null +++ b/src/server/modules/advertisements/service/advertisements.service.ts @@ -0,0 +1,26 @@ +import { Service } from '@tsed/di'; +import { TypeORMService } from '@tsed/typeorm'; +import { Like, EntityManager, Repository } from 'typeorm'; + +import { AbstractEntityService } from '../../common/util/service/abstract-entity.service'; +import { Advertisements } from '../entity/advertisements.entity'; + + +@Service() +export class AdvertisementsService extends AbstractEntityService { + constructor(typeORMService: TypeORMService) { + super(typeORMService); + } + + getRepository(entityManager: EntityManager): Repository { + return entityManager.getRepository(Advertisements); + } + + async findAllByPage(size: number, page: number): Promise { + return this.repository.find({ + skip: (page - 1) * size, + take: size, + }); + } + +} diff --git a/src/server/modules/article/controller/article-best-comments-tag.controller.ts b/src/server/modules/article/controller/article-best-comments-tag.controller.ts new file mode 100755 index 0000000..3b0b657 --- /dev/null +++ b/src/server/modules/article/controller/article-best-comments-tag.controller.ts @@ -0,0 +1,53 @@ +/** + * 파 일 명: article-comments-tag.controller.ts + * 작성일자: 2019-01-23 + * 작 성 자: 박병준 + * 설 명: ArticleCommentsTag entry point를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { + Controller, + Req, + Res, + Get, + ServerSettingsService, + BeforeRoutesInit, + Authenticated, + PathParams, + Required, + Put, + BodyParams, + Post, + Delete, +} from '@tsed/common'; +import * as express from 'express'; +import { ArticleService as MongdbArticleService } from '../../../mongodb/article/service/article.service'; +import { AbstractController } from '../../common/util/controller/abstract.controller'; +import { ArticleCommentsTag } from '../entity/article-comments-tag.entity'; +import { ArticleCommentsTagService } from '../service/article-comments-tag.service'; +import { ArticleBestCommentsTag } from '../entity/article-best-comments-tag.entity'; +import { ArticleBestCommentsTagService } from '../service/article-best-comments-tag.service'; + + +@Controller('/article/best_comments_tag') +export class ArticleBestCommentsTagController extends AbstractController { + protected passportOAuthCommonOptions: any; + + constructor( + private articleBestCommentsTagService: ArticleBestCommentsTagService, + ) { + super(articleBestCommentsTagService); + } + + $beforeRoutesInit() { + } + + @Put('/') + @Authenticated({ roles: ['R_USER'] }) + async save(@BodyParams() model: ArticleBestCommentsTag): Promise { + return this.articleBestCommentsTagService.save(model); + } +} diff --git a/src/server/modules/article/controller/article-bookmarks.controller.ts b/src/server/modules/article/controller/article-bookmarks.controller.ts new file mode 100755 index 0000000..84dbff4 --- /dev/null +++ b/src/server/modules/article/controller/article-bookmarks.controller.ts @@ -0,0 +1,97 @@ +/** + * 파 일 명: article-bookmarks.controller.ts + * 작성일자: 2019-01-18 + * 작 성 자: 윤대훈 + * 설 명: Bookmarks entry point를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { + Controller, + Req, + Res, + Get, + ServerSettingsService, + BeforeRoutesInit, + Authenticated, + PathParams, + Required, + Put, + BodyParams, + Post, + Delete, +} from '@tsed/common'; +import * as express from 'express'; +import { ArticleBookmarksService } from '../service/article-bookmarks.service'; +import { ArticleService as MongdbArticleService } from '../../../mongodb/article/service/article.service'; +import { ArticleBookmarks } from '../entity/article-bookmarks.entity'; +import { AbstractController } from '../../common/util/controller/abstract.controller'; +import { ArticleMongodb } from 'src/shared/article/model/article.mongodb.model'; + + +@Controller('/article/bookmarks') +export class ArticleBookmarksController extends AbstractController { + protected passportOAuthCommonOptions: any; + + constructor( + protected articleBookmarksService: ArticleBookmarksService, + private mongdbArticleService: MongdbArticleService) { + super(articleBookmarksService); + } + + $beforeRoutesInit() { + } + + @Get('/user/:uid') + @Authenticated({ roles: ['R_USER'] }) + async getAllByBookmarksUid( + @Req() req: express.Request, + @Res() res: express.Response, + @Required() @PathParams('uid') uid: string, + ): Promise { + + const bookmarksList = await this.articleBookmarksService.findAllArticleIdByUid(uid); + + const articleIdList = []; + + bookmarksList.forEach(bookmarks => { + articleIdList.push(bookmarks.articleId); + }); + + const modelList = await this.mongdbArticleService.findAll({ id: { $in: articleIdList } }); + + if (modelList) { + return modelList; + } + + res.status(203).send('No Content'); + } + + @Delete('/:id') + @Authenticated({ roles: ['R_USER'] }) + remove(@PathParams('id') @Required() id: string): Promise { + return this.service.remove(id); + } + + @Put('/') + @Authenticated({ roles: ['R_USER'] }) + async save(@BodyParams() model: ArticleBookmarks): Promise { + + const modelList = await this.articleBookmarksService.findAll({ + articleId: model.articleId, + user: { + id: model.user.id + } + }); + + if (modelList.length <= 0) { + return this.service.save(model); + } + + return null; + } + + +} diff --git a/src/server/modules/article/controller/article-comments-tag.controller.ts b/src/server/modules/article/controller/article-comments-tag.controller.ts new file mode 100755 index 0000000..b78b5d7 --- /dev/null +++ b/src/server/modules/article/controller/article-comments-tag.controller.ts @@ -0,0 +1,64 @@ +/** + * 파 일 명: article-comments-tag.controller.ts + * 작성일자: 2019-01-23 + * 작 성 자: 박병준 + * 설 명: ArticleCommentsTag entry point를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { + Controller, + Req, + Res, + Get, + ServerSettingsService, + BeforeRoutesInit, + Authenticated, + PathParams, + Required, + Put, + BodyParams, + Post, + Delete, +} from '@tsed/common'; +import * as express from 'express'; +import { ArticleService as MongdbArticleService } from '../../../mongodb/article/service/article.service'; +import { AbstractController } from '../../common/util/controller/abstract.controller'; +import { ArticleCommentsTag } from '../entity/article-comments-tag.entity'; +import { ArticleCommentsTagService } from '../service/article-comments-tag.service'; + + +@Controller('/article/comments_tag') +export class ArticleCommentsTagController extends AbstractController { + protected passportOAuthCommonOptions: any; + + constructor( + private articleCommentsTagService: ArticleCommentsTagService, + ) { + super(articleCommentsTagService); + } + + $beforeRoutesInit() { + } + + @Put('/') + @Authenticated({ roles: ['R_USER'] }) + async save(@BodyParams() model: ArticleCommentsTag): Promise { + return this.articleCommentsTagService.save(model); + } + + @Get('/list/:aid') + async getAllByArticleId( + @Required() @PathParams('aid') aid: string + ): Promise { + const list = await this.articleCommentsTagService.findAllByArticleId(aid); + if (list) { + return list; + } + + return null; + } + +} diff --git a/src/server/modules/article/controller/article-like.controller.ts b/src/server/modules/article/controller/article-like.controller.ts new file mode 100755 index 0000000..d195614 --- /dev/null +++ b/src/server/modules/article/controller/article-like.controller.ts @@ -0,0 +1,49 @@ +/** + * 파 일 명: article-like.controller.ts + * 작성일자: 2019-01-23 + * 작 성 자: 박병준 + * 설 명: ArticleLike entry point를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { + Controller, + Authenticated, + Put, + BodyParams, + Post, +} from '@tsed/common'; +import * as express from 'express'; +import { AbstractController } from '../../common/util/controller/abstract.controller'; +import { ArticleLike } from '../entity/article-like.entity'; +import { ArticleLikeService } from '../service/article-like.service'; + + +@Controller('/article/like') +export class ArticleLikeController extends AbstractController { + protected passportOAuthCommonOptions: any; + + constructor( + private articleLikeService: ArticleLikeService, + ) { + super(articleLikeService); + } + + $beforeRoutesInit() { + } + + @Put('/') + @Authenticated({ roles: ['R_USER'] }) + save(@BodyParams() model: ArticleLike) { + return this.articleLikeService.save(model); + } + + @Post('/') + @Authenticated({ roles: ['R_USER'] }) + async update(@BodyParams() model: ArticleLike): Promise { + return this.articleLikeService.save(model); + } + +} diff --git a/src/server/modules/article/controller/article-tag.controller.ts b/src/server/modules/article/controller/article-tag.controller.ts new file mode 100755 index 0000000..c684dfe --- /dev/null +++ b/src/server/modules/article/controller/article-tag.controller.ts @@ -0,0 +1,39 @@ +import * as express from 'express'; +import { + Controller, + BodyParams, + Authenticated, + PathParams, + Get, + Put, + Required, +} from '@tsed/common'; +import { AbstractController } from '../../common/util/controller/abstract.controller'; +import { ArticleTag } from '../entity/article-tag.entity'; +import { ArticleTagService } from '../service/article-tag.service'; + +@Controller('/article/tag') +export class ArticleTagController extends AbstractController { + constructor( + private articleTagService: ArticleTagService, + ) { + super(articleTagService); + } + + @Get('/search/:keyword') + @Authenticated({ roles: ['R_USER'] }) + search(@PathParams('keyword') keyword: string) { + return this.articleTagService.findAllByKeyword(keyword); + } + + @Put('/') + save(@BodyParams() articleTag: ArticleTag) { + return this.articleTagService.save(articleTag); + } + + + @Get('/popular') + async getAllPopular(): Promise { + return await this.articleTagService.findAllPopularTags(); + } +} diff --git a/src/server/modules/article/controller/article.controller.ts b/src/server/modules/article/controller/article.controller.ts new file mode 100755 index 0000000..41fbbdd --- /dev/null +++ b/src/server/modules/article/controller/article.controller.ts @@ -0,0 +1,84 @@ +import * as express from 'express'; +import { + Controller, + Post, + BodyParams, + Required, + Authenticated, + PathParams, + Get, + Req, + Res, +} from '@tsed/common'; +import { ArticleService } from '../service/article.service'; +import { Article } from '../entity/article.entity'; +import { AbstractController } from '../../common/util/controller/abstract.controller'; +import { ArticleService as MongdbArticleService } from '../../../mongodb/article/service/article.service'; +import { Article as ArticleMongodb } from '../../../mongodb/article/model/article.model'; +import { User } from '../../user/entity/user.entity'; +import { UserService } from '../../user/service/user.service'; + +@Controller('/article') +export class ArticleController extends AbstractController
{ + constructor( + private articleService: ArticleService, + private userService: UserService, + private mongdbArticleService: MongdbArticleService, + ) { + super(articleService); + } + + @Get('/:aid') + async get( + @Req() req: express.Request, + @Res() res: express.Response, + @Required() @PathParams('aid') aid: string + ): Promise { + const resModel = await this.mongdbArticleService.findByAid(aid); + + if (resModel) { + return resModel; + } + + res.status(203).send('No Content'); + } + + @Get('/user/:uid/:timestamp/:size') + async getAllByUid( + @Req() req: express.Request, + @Res() res: express.Response, + @Required() @PathParams('uid') uid: string, + @Required() @PathParams('timestamp') timestamp: number, + @Required() @PathParams('size') size: number, + ): Promise { + const modelList = await this.mongdbArticleService.findAllByUid(uid, timestamp, size); + + // const modelList = await this.mongdbArticleService.findAll({ userId: uid }); + + if (modelList) { + await this.articleService.increaseViewCount(modelList); + return modelList; + } + + res.status(203).send('No Content'); + } + + @Get('/tag/:tagName/:timestamp/:size') + async getAllByTagName( + @Req() req: express.Request, + @Res() res: express.Response, + @Required() @PathParams('tagName') tagName: string, + @Required() @PathParams('timestamp') timestamp: number, + @Required() @PathParams('size') size: number, + ): Promise { + const modelList = await this.mongdbArticleService.findAllByTagName(null, [tagName], timestamp, size); + + if (modelList) { + await this.articleService.increaseViewCount(modelList); + return modelList; + } + + res.status(203).send('No Content'); + } + +} diff --git a/src/server/modules/article/entity/article-attachments.entity.ts b/src/server/modules/article/entity/article-attachments.entity.ts new file mode 100755 index 0000000..d6fa31b --- /dev/null +++ b/src/server/modules/article/entity/article-attachments.entity.ts @@ -0,0 +1,24 @@ +/** + * 파 일 명: article-attachments.entity.ts + * 작성일자: 2018-12-13 + * 작 성 자: 박병준 + * 설 명: Article Attachments 관리 Entity 스키마를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Property, Required } from '@tsed/common'; +import { Entity, Column, TableInheritance } from 'typeorm'; + +import { AttachmentsRelation } from '../../attachments/entity/attachments-relation.entity'; +import { ArticleImage } from '../../../../shared/article/type/article-image.type'; + +@Entity() +@TableInheritance({ column: { type: 'varchar', name: 'type' } }) +export class ArticleAttachments extends AttachmentsRelation { + @Column() + @Required() + @Property() + imageType: ArticleImage; +} diff --git a/src/server/modules/article/entity/article-best-comments-tag.entity.ts b/src/server/modules/article/entity/article-best-comments-tag.entity.ts new file mode 100755 index 0000000..13a3272 --- /dev/null +++ b/src/server/modules/article/entity/article-best-comments-tag.entity.ts @@ -0,0 +1,35 @@ + +/** + * 파 일 명: article-comments-tag.entity.ts + * 작성일자: 2019-01-23 + * 작 성 자: 윤대훈 + * 설 명: article comments tag 관리 Entity 스키마를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Property, Required } from '@tsed/common'; +import { Entity, Column, Index, OneToOne, JoinColumn, ManyToOne, TableInheritance, OneToMany } from 'typeorm'; + +import { Base } from '../../common/util/entity/base.entity'; +import { User } from '../../user/entity/user.entity'; +import { Article } from './article.entity'; +import { MetaCommentsTag } from '../../meta/entity/meta-comments-tag.entity'; + +@Entity() +export class ArticleBestCommentsTag extends Base { + @ManyToOne(type => Article, article => article.commentsTagList, {}) + @Property() + article: Article; + + @ManyToOne(type => MetaCommentsTag, metaCommentsTag => metaCommentsTag.articleCommentsTagList, { + eager: true + }) + @JoinColumn() + @Property() + metaCommentsTag?: MetaCommentsTag; + + @Column() + count: number; +} diff --git a/src/server/modules/article/entity/article-bookmarks.entity.ts b/src/server/modules/article/entity/article-bookmarks.entity.ts new file mode 100755 index 0000000..996c45c --- /dev/null +++ b/src/server/modules/article/entity/article-bookmarks.entity.ts @@ -0,0 +1,29 @@ +/** + * 파 일 명: bookmarks.entity.ts + * 작성일자: 2019-01-18 + * 작 성 자: 윤대훈 + * 설 명: Bookmarks 관리 Entity 스키마를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Property, Required } from '@tsed/common'; +import { Entity, Column, Index, OneToOne, JoinColumn, ManyToOne, TableInheritance, OneToMany, Unique } from 'typeorm'; + +import { Base } from '../../common/util/entity/base.entity'; +import { User } from '../../user/entity/user.entity'; + + +@Entity() +@Unique(['articleId', 'user']) +export class ArticleBookmarks extends Base { + @Column() + @Required() + @Property() + articleId: string; + + @ManyToOne(type => User, user => user.articleBookmarksList) + @Property() + user: User; +} diff --git a/src/server/modules/article/entity/article-comments-tag.entity.ts b/src/server/modules/article/entity/article-comments-tag.entity.ts new file mode 100755 index 0000000..5a3dcdc --- /dev/null +++ b/src/server/modules/article/entity/article-comments-tag.entity.ts @@ -0,0 +1,38 @@ + +/** + * 파 일 명: article-comments-tag.entity.ts + * 작성일자: 2019-01-23 + * 작 성 자: 윤대훈 + * 설 명: article comments tag 관리 Entity 스키마를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Property, Required } from '@tsed/common'; +import { Entity, Column, Index, OneToOne, JoinColumn, ManyToOne, TableInheritance, OneToMany } from 'typeorm'; + +import { Base } from '../../common/util/entity/base.entity'; +import { User } from '../../user/entity/user.entity'; +import { Article } from './article.entity'; +import { MetaCommentsTag } from '../../meta/entity/meta-comments-tag.entity'; + +@Entity() +export class ArticleCommentsTag extends Base { + @ManyToOne(type => Article, article => article.commentsTagList) + @Property() + article: Article; + + @ManyToOne(type => User, user => user.articleCommentsTagList, { + eager: true + }) + @Property() + user: User; + + @ManyToOne(type => MetaCommentsTag, metaCommentsTag => metaCommentsTag.articleCommentsTagList, { + eager: true + }) + @JoinColumn() + @Property() + metaCommentsTag?: MetaCommentsTag; +} diff --git a/src/server/modules/article/entity/article-like.entity.ts b/src/server/modules/article/entity/article-like.entity.ts new file mode 100755 index 0000000..f1823cf --- /dev/null +++ b/src/server/modules/article/entity/article-like.entity.ts @@ -0,0 +1,32 @@ +/** + * 파 일 명: article-like.entity.ts + * 작성일자: 2019-01-25 + * 작 성 자: 박병준 + * 설 명: Article like 관리 Entity 스키마를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Property } from '@tsed/common'; +import { Entity, ManyToOne, ManyToMany, Column } from 'typeorm'; + +import { Base } from '../../common/util/entity/base.entity'; +import { User } from '../../user/entity/user.entity'; +import { Article } from './article.entity'; + +@Entity() +export class ArticleLike extends Base { + @ManyToOne(type => Article, article => article.likeList) + @Property() + article: Article; + + @ManyToOne(type => User, user => user.articleLikeList) + @Property() + user: User; + + @Column() + @Property() + count: number; + +} diff --git a/src/server/modules/article/entity/article-series.entity.ts b/src/server/modules/article/entity/article-series.entity.ts new file mode 100755 index 0000000..a6b27ae --- /dev/null +++ b/src/server/modules/article/entity/article-series.entity.ts @@ -0,0 +1,29 @@ +import { Property, Required } from '@tsed/common'; +import { Entity, Column, OneToMany, ManyToOne, TableInheritance, } from 'typeorm'; + +import { Base } from '../../common/util/entity/base.entity'; +import { User } from '../../user/entity/user.entity'; +import { Article } from './article.entity'; + +@Entity() +@TableInheritance({ column: { type: 'varchar', name: 'type' } }) +export class ArticleSeries extends Base { + @Column() + @Property() + @Required() + title: string; + + @Column({ + nullable: true, + }) + @Property() + description: string; + + @ManyToOne(type => User, user => user.articleSeriesList) + @Property() + user: User; + + @OneToMany(type => Article, article => article.articleSeries) + articleList: Article[]; + +} diff --git a/src/server/modules/article/entity/article-tag.entity.ts b/src/server/modules/article/entity/article-tag.entity.ts new file mode 100755 index 0000000..4aab4b9 --- /dev/null +++ b/src/server/modules/article/entity/article-tag.entity.ts @@ -0,0 +1,29 @@ +/** + * 파 일 명: article-tag.entity.ts + * 작성일자: 2018-12-13 + * 작 성 자: 박병준 + * 설 명: Article Tag 관리 Entity 스키마를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Property } from '@tsed/common'; +import { Entity, ManyToOne, ManyToMany } from 'typeorm'; + +import { Tag } from '../../tag/entity/tag.entity'; +import { User } from '../../user/entity/user.entity'; +import { Article } from './article.entity'; + +@Entity() +export class ArticleTag extends Tag { + @ManyToMany(type => Article, article => article.tagList) + public articleList: Article[]; + + @ManyToOne(type => User, user => user.articleTagList, { + nullable: true, + eager: true, + }) + @Property() + user: User; +} diff --git a/src/server/modules/article/entity/article.entity.ts b/src/server/modules/article/entity/article.entity.ts new file mode 100755 index 0000000..51382ad --- /dev/null +++ b/src/server/modules/article/entity/article.entity.ts @@ -0,0 +1,117 @@ +import { Property, Required } from '@tsed/common'; +import { + Entity, + Column, + BeforeInsert, + TableInheritance, + OneToMany, + ManyToOne, + ManyToMany, + JoinTable, +} from 'typeorm'; + +import { Base } from '../../common/util/entity/base.entity'; +import { User } from '../../user/entity/user.entity'; +import { ArticleTag } from './article-tag.entity'; +import { ArticleSeries } from './article-series.entity'; +import { ArticleType } from '../../../../shared/article/type/article-type.type'; +import { AgeLimitType } from '../../../../shared/common/type/ageLimit.type'; +import { ArticleCommentsTag } from './article-comments-tag.entity'; +import { ArticleLike } from './article-like.entity'; +import { ArticleBestCommentsTag } from './article-best-comments-tag.entity'; + +@Entity() +@TableInheritance({ column: { type: 'varchar', name: 'type' } }) +export class Article extends Base { + @Column() + @Property() + type: ArticleType; + + @Column() + @Property() + @Required() + title: string; + + @Column({ + nullable: true, + }) + @Property() + description: string; + + @Column({ + default: false + }) + @Property() + original: boolean; + + @Column({ + default: AgeLimitType.ALL + }) + @Property() + ageLimit: AgeLimitType; + + @Column({ + default: 0 + }) + @Property() + commentsCount: number; + + @Column({ + default: 0 + }) + @Property() + viewCount: number; + + @Column({ + default: true + }) + @Property() + publicYN: boolean; + + @ManyToOne(type => User, user => user.articleList, { + eager: true + }) + @Property() + user: User; + + @ManyToOne( + type => ArticleSeries, + articleSeries => articleSeries.articleList, + { + eager: true + } + ) + @Property() + articleSeries: ArticleSeries; + + @Property() + originalTag: ArticleTag; + + @ManyToMany(type => ArticleTag, { + eager: true + }) + @JoinTable() + @Property() + tagList: ArticleTag[]; + + @OneToMany(type => ArticleCommentsTag, articleCommentsTag => articleCommentsTag.article, { + // eager: true + }) + commentsTagList: ArticleCommentsTag[]; + + + @OneToMany(type => ArticleBestCommentsTag, bestCommentsTagList => bestCommentsTagList.article, { + eager: true + }) + bestCommentsTagList: ArticleBestCommentsTag[]; + + @OneToMany(type => ArticleLike, articleLike => articleLike.article, { + eager: true + }) + @Property() + likeList: ArticleLike[]; + + @BeforeInsert() + beforeInsert() { + } +} diff --git a/src/server/modules/article/service/article-attachments.service.ts b/src/server/modules/article/service/article-attachments.service.ts new file mode 100755 index 0000000..bf056ab --- /dev/null +++ b/src/server/modules/article/service/article-attachments.service.ts @@ -0,0 +1,18 @@ +import { Service } from '@tsed/di'; +import { TypeORMService } from '@tsed/typeorm'; +import { EntityManager, Repository } from 'typeorm'; + +import { AbstractEntityService } from '../../common/util/service/abstract-entity.service'; +import { ArticleAttachments } from '../entity/article-attachments.entity'; + +@Service() +export class ArticleAttachmentsService extends AbstractEntityService { + constructor(typeORMService: TypeORMService) { + super(typeORMService); + } + + getRepository(entityManager: EntityManager): Repository { + return entityManager.getRepository(ArticleAttachments); + } + +} diff --git a/src/server/modules/article/service/article-best-comments-tag.service.ts b/src/server/modules/article/service/article-best-comments-tag.service.ts new file mode 100755 index 0000000..d89e011 --- /dev/null +++ b/src/server/modules/article/service/article-best-comments-tag.service.ts @@ -0,0 +1,39 @@ +/** + * 파 일 명: article-comments-tag.service.ts + * 작성일자: 2019-01-23 + * 작 성 자: 박병준 + * 설 명: ArticleCommentsTag Service를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ + +import { Service } from '@tsed/di'; +import { TypeORMService } from '@tsed/typeorm'; +import { EntityManager, Repository } from 'typeorm'; + +import { AbstractEntityService } from '../../common/util/service/abstract-entity.service'; +import { ArticleCommentsTag } from '../entity/article-comments-tag.entity'; +import { ArticleService } from './article.service'; +import { ArticleBestCommentsTag } from '../entity/article-best-comments-tag.entity'; + + + +@Service() +export class ArticleBestCommentsTagService extends AbstractEntityService { + constructor( + typeORMService: TypeORMService, + ) { + super(typeORMService); + } + + getRepository(entityManager: EntityManager): Repository { + return entityManager.getRepository(ArticleBestCommentsTag); + } + + async save(model: ArticleBestCommentsTag): Promise { + return super.save(model); + } + +} diff --git a/src/server/modules/article/service/article-bookmarks.service.ts b/src/server/modules/article/service/article-bookmarks.service.ts new file mode 100755 index 0000000..206d769 --- /dev/null +++ b/src/server/modules/article/service/article-bookmarks.service.ts @@ -0,0 +1,40 @@ +/** + * 파 일 명: article-bookmarks.service.ts + * 작성일자: 2019-01-18 + * 작 성 자: 윤대훈 + * 설 명: ArticleBookmarks Service를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ + +import { Service } from '@tsed/di'; +import { TypeORMService } from '@tsed/typeorm'; +import { EntityManager, Repository } from 'typeorm'; + +import { AbstractEntityService } from '../../common/util/service/abstract-entity.service'; +import { ArticleBookmarks } from '../entity/article-bookmarks.entity'; + + + +@Service() +export class ArticleBookmarksService extends AbstractEntityService { + constructor(typeORMService: TypeORMService) { + super(typeORMService); + } + + getRepository(entityManager: EntityManager): Repository { + return entityManager.getRepository(ArticleBookmarks); + } + + findAllArticleIdByUid(uid: string): Promise { + + return this.repository.find({ + select: ['articleId'], + where: { user: { id: uid } } + }); + + } + +} diff --git a/src/server/modules/article/service/article-comments-tag.service.ts b/src/server/modules/article/service/article-comments-tag.service.ts new file mode 100755 index 0000000..8673cc4 --- /dev/null +++ b/src/server/modules/article/service/article-comments-tag.service.ts @@ -0,0 +1,81 @@ +/** + * 파 일 명: article-comments-tag.service.ts + * 작성일자: 2019-01-23 + * 작 성 자: 박병준 + * 설 명: ArticleCommentsTag Service를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ + +import { Service } from '@tsed/di'; +import { TypeORMService } from '@tsed/typeorm'; +import { EntityManager, Repository } from 'typeorm'; + +import { AbstractEntityService } from '../../common/util/service/abstract-entity.service'; +import { ArticleCommentsTag } from '../entity/article-comments-tag.entity'; +import { ArticleService } from './article.service'; + + + +@Service() +export class ArticleCommentsTagService extends AbstractEntityService { + constructor( + typeORMService: TypeORMService, + private articleService: ArticleService, + ) { + super(typeORMService); + } + + getRepository(entityManager: EntityManager): Repository { + return entityManager.getRepository(ArticleCommentsTag); + } + + async save(model: ArticleCommentsTag): Promise { + const queryRunner = this.createQueryRunner(); + await queryRunner.connect(); + await queryRunner.startTransaction(); + + try { + const _model = await super.saveWithTransaction(queryRunner.manager, model); + await this.articleService.increaseCommentsCountWithTransactions(queryRunner.manager, _model.article.id); + await queryRunner.commitTransaction(); + return _model; + } catch (error) { + console.log(error); + await queryRunner.rollbackTransaction(); + } finally { + await queryRunner.release(); + } + } + + + async findAllByArticleId(aid: string): Promise { + const article = { + id: aid + }; + // return new Promise(async (resolve, reject) => { + // this.repository.find({ + // where: { article: article }, + // }) + // .then((result) => { + // return resolve(result); + // }) + // .catch((reason) => { + // return null; + // }); + // }); + + return this.repository.createQueryBuilder('comment') + .select('comment.metaCommentsTag.id as id') + .addSelect('COUNT(id) as coun') + .groupBy('id') + .orderBy('coun', 'DESC') + .where('comment.article = :article', { article: article }) + + // .take(6) + .getRawMany(); + } + +} diff --git a/src/server/modules/article/service/article-like.service.ts b/src/server/modules/article/service/article-like.service.ts new file mode 100755 index 0000000..c44682c --- /dev/null +++ b/src/server/modules/article/service/article-like.service.ts @@ -0,0 +1,40 @@ +/** + * 파 일 명: article-like.service.ts + * 작성일자: 2019-01-25 + * 작 성 자: 박병준 + * 설 명: ArticleLike Service를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ + +import { Service } from '@tsed/di'; +import { TypeORMService } from '@tsed/typeorm'; +import { EntityManager, Repository } from 'typeorm'; + +import { AbstractEntityService } from '../../common/util/service/abstract-entity.service'; +import { ArticleLike } from '../entity/article-like.entity'; +import { ArticleService as MongdbArticleService } from '../../../mongodb/article/service/article.service'; + +@Service() +export class ArticleLikeService extends AbstractEntityService { + constructor( + typeORMService: TypeORMService, + private mongdbArticleService: MongdbArticleService, + ) { + super(typeORMService); + } + + getRepository(entityManager: EntityManager): Repository { + return entityManager.getRepository(ArticleLike); + } + + async save(articleLike: ArticleLike): Promise { + const _articleLike = await super.save(articleLike); + + this.mongdbArticleService.saveLike(articleLike); + + return _articleLike; + } +} diff --git a/src/server/modules/article/service/article-series.service.ts b/src/server/modules/article/service/article-series.service.ts new file mode 100755 index 0000000..fb4dfa6 --- /dev/null +++ b/src/server/modules/article/service/article-series.service.ts @@ -0,0 +1,23 @@ +import { Service } from '@tsed/di'; +import { TypeORMService } from '@tsed/typeorm'; +import { EntityManager, Repository } from 'typeorm'; + +import { AbstractEntityService } from '../../common/util/service/abstract-entity.service'; +import { ArticleSeries } from '../entity/article-series.entity'; +import { User } from '../../user/entity/user.entity'; + +@Service() +export class ArticleSeriesService extends AbstractEntityService { + constructor(typeORMService: TypeORMService) { + super(typeORMService); + } + + getRepository(entityManager: EntityManager): Repository { + return entityManager.getRepository(ArticleSeries); + } + + async findAllByUser(user: User): Promise { + return this.repository.find({ where: { user: user } }); + } + +} diff --git a/src/server/modules/article/service/article-tag.service.ts b/src/server/modules/article/service/article-tag.service.ts new file mode 100755 index 0000000..cfd3cf5 --- /dev/null +++ b/src/server/modules/article/service/article-tag.service.ts @@ -0,0 +1,54 @@ +import { Service } from '@tsed/di'; +import { TypeORMService } from '@tsed/typeorm'; +import { EntityManager, Repository, Like } from 'typeorm'; + +import { AbstractEntityService } from '../../common/util/service/abstract-entity.service'; +import { ArticleTag } from '../entity/article-tag.entity'; +import { Article } from '../entity/article.entity'; + +@Service() +export class ArticleTagService extends AbstractEntityService { + constructor(typeORMService: TypeORMService) { + super(typeORMService); + } + + getRepository(entityManager: EntityManager): Repository { + return entityManager.getRepository(ArticleTag); + } + + async findByTagName(tagName: string): Promise { + return this.repository.findOne({ tagName: tagName }); + } + + async findAllByKeyword(keyword: string): Promise { + return this.repository.find({ where: { tagName: Like(`${keyword}%`) } }); + } + + async findAllByArticle(article: Article): Promise { + return this.repository.find({ where: { articleList: [article] } }); + } + + async removeByArticleWithTransaction(entityManager: EntityManager, article: Article): Promise { + const articleTagList = await this.findAllByArticle(article); + await entityManager.createQueryBuilder().relation(Article, 'tagList').of(article).remove(articleTagList); + } + + async findAllPopularTags(): Promise { + return new Promise(async (resolve, reject) => { + this.repository.find({ + order: { + tagCount: 'DESC', + }, + take: 20, + cache: true, + }) + .then((result) => { + return resolve(result); + }) + .catch((reason) => { + return reject(reason); + }); + }); + } + +} diff --git a/src/server/modules/article/service/article.service.ts b/src/server/modules/article/service/article.service.ts new file mode 100755 index 0000000..0f57d87 --- /dev/null +++ b/src/server/modules/article/service/article.service.ts @@ -0,0 +1,116 @@ +import { Service } from '@tsed/di'; +import { TypeORMService } from '@tsed/typeorm'; +import { Repository, EntityManager, In } from 'typeorm'; + +import { AbstractEntityService } from '../../common/util/service/abstract-entity.service'; +import { Article } from '../entity/article.entity'; +import { ArticleTagService } from './article-tag.service'; +import { ArticleTag } from '../entity/article-tag.entity'; +import { Article as MongdbArticle } from '../../../mongodb/article/model/article.model'; +import { UserFavorTagService } from '../../user/service/user-favor-tag.service'; + +@Service() +export class ArticleService extends AbstractEntityService
{ + constructor( + typeORMService: TypeORMService, + private articleTagService: ArticleTagService, + private userFavorTagService: UserFavorTagService, + ) { + super(typeORMService); + } + + getRepository(entityManager: EntityManager): Repository
{ + return entityManager.getRepository(Article); + } + + async findByAid(aid: string): Promise
{ + return this.repository.findOne(aid); + } + + async saveTagsWithTransactions( + entityManager: EntityManager, + article: Article + ): Promise { + const articleTagList: ArticleTag[] = []; + + if (article.id) { + await this.articleTagService.removeByArticleWithTransaction( + entityManager, + article + ); + } + + if (article.originalTag) { + let originalTag = await this.articleTagService.findByTagName( + article.originalTag.tagName + ); + if (originalTag) { + throw new Error(`Original tag[${article.originalTag.tagName}] exist`); + } + article.originalTag.user = article.user; + originalTag = await this.articleTagService.saveWithTransaction( + entityManager, + article.originalTag + ); + articleTagList.push(originalTag); + } + + if (!article.tagList || 0 === article.tagList.length) { + return null; + } + + for (let index = 0; index < article.tagList.length; index++) { + const tag = article.tagList[index]; + + let articleTag = await this.articleTagService.findByTagName(tag.tagName); + if (!articleTag) { + articleTag = await this.articleTagService.saveWithTransaction( + entityManager, + tag + ); + } + articleTagList.push(articleTag); + } + + return articleTagList; + } + + async increaseViewCount( + articleList: MongdbArticle[], + ): Promise { + const idList = await this.getIdListFromMongdbArticleList(articleList); + if (!idList) { + return; + } + this.repository.createQueryBuilder('increaseViewCount') + .update() + .set({ viewCount: () => 'viewCount + 1' }) + .where({ id: In(idList) }) + .execute(); + } + + async increaseCommentsCountWithTransactions( + entityManager: EntityManager, + aid: string, + ): Promise { + const repository = this.getRepository(entityManager); + await repository.createQueryBuilder('increaseCommentsCount') + .update() + .set({ commentsCount: () => 'commentsCount + 1' }) + .where('id = :id', { id: aid }) + .execute(); + + return true; + } + + async getIdListFromMongdbArticleList(articleList: MongdbArticle[]): Promise { + if (!articleList || 0 === articleList.length) { + return undefined; + } + const idList: string[] = []; + for (const article of articleList) { + idList.push(article.id); + } + return idList; + } +} diff --git a/src/server/modules/attachments/controller/attachments.controller.ts b/src/server/modules/attachments/controller/attachments.controller.ts new file mode 100755 index 0000000..d10fbfe --- /dev/null +++ b/src/server/modules/attachments/controller/attachments.controller.ts @@ -0,0 +1,90 @@ +import * as express from 'express'; +import { Express } from 'express'; +import { + Controller, + Post, + Authenticated, + Req, + Res, + Get, + Required, + PathParams, + Delete, + BodyParams, + QueryParams +} from '@tsed/common'; +import { MultipartFile } from '@tsed/multipartfiles'; + +import { AttachmentsService } from '../service/attachments.service'; +import { Attachments } from '../entity/attachments.entity'; +import { AbstractController } from '../../common/util/controller/abstract.controller'; + +type MulterFile = Express.Multer.File; + +@Controller('/attachments') +export class AttachmentsController extends AbstractController { + constructor(private attachmentsService: AttachmentsService) { + super(attachmentsService); + } + + @Get('/i/:id') + @Get('/i/:id/:option1') + @Get('/i/:id/:option1/:option2') + @Get('/i/:id/:option1/:option2/:option3') + async get( + @Req() req: express.Request, + @Res() res: express.Response, + @Required() @PathParams('id') id: string, + @PathParams('option1') option1: string, + @PathParams('option2') option2: string, + @PathParams('option3') option3: string + ) { + const attachments = await this.service.find(id); + + if (!attachments) { + res.status(203).send('No Content'); + return; + } + + const options: string[] = [option1, option2, option3]; + + try { + const buffer = await this.attachmentsService.getFile( + attachments, + options + ); + res.writeHead(200, { 'Content-type': attachments.mimeType }); + res.end(buffer); + } catch (error) { + res.writeHead(400, { 'Content-type': 'text/html' }); + res.end('No such image'); + } + } + + @Post('/') + @Authenticated({ roles: ['R_USER'] }) + async uploadFile( + @Req() req: express.Request, + @MultipartFile('file') file: MulterFile + ) { + const attachments = new Attachments(); + attachments.mimeType = file.mimetype; + attachments.name = file.originalname; + attachments.size = file.size; + // attachments.user = req['user']; => 2018.01.07 user 삭제 + const _attachments = await this.service.save(attachments); + + await this.attachmentsService.move(file, _attachments); + + return _attachments; + } + + @Delete('/') + @Authenticated({ roles: ['R_USER'] }) + async remove( + @QueryParams('ids') @Required() ids: string[], + ): Promise { + console.log('>>>>>여기 아이디: ', ids); + return this.attachmentsService.removeAll(ids); + } +} diff --git a/src/server/modules/attachments/entity/attachments-relation.entity.ts b/src/server/modules/attachments/entity/attachments-relation.entity.ts new file mode 100755 index 0000000..32c2d2c --- /dev/null +++ b/src/server/modules/attachments/entity/attachments-relation.entity.ts @@ -0,0 +1,26 @@ +/** + * 파 일 명: attachments-relation.entity.ts + * 작성일자: 2018-12-10 + * 작 성 자: 박병준 + * 설 명: Attachments 관계 관리 Entity 스키마를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Property } from '@tsed/common'; +import { OneToOne, JoinColumn } from 'typeorm'; +import { Attachments } from './attachments.entity'; + +import { Base } from '../../common/util/entity/base.entity'; + +export abstract class AttachmentsRelation extends Base { + @OneToOne(type => Attachments, { + eager: true, + onDelete: 'CASCADE', + }) + @JoinColumn() + @Property() + attachments: Attachments; + +} diff --git a/src/server/modules/attachments/entity/attachments.entity.ts b/src/server/modules/attachments/entity/attachments.entity.ts new file mode 100755 index 0000000..e1688c0 --- /dev/null +++ b/src/server/modules/attachments/entity/attachments.entity.ts @@ -0,0 +1,38 @@ +/** + * 파 일 명: attachments.entity.ts + * 작성일자: 2018-12-13 + * 작 성 자: 박병준 + * 설 명: Attachments 관리 Entity 스키마를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Property, Required } from '@tsed/common'; +import { Entity, Column, ManyToOne } from 'typeorm'; + +import { Base } from '../../common/util/entity/base.entity'; +import { User } from '../../user/entity/user.entity'; + +@Entity() +export class Attachments extends Base { + @Column() + @Required() + @Property() + name: string; + + @Column() + @Required() + @Property() + mimeType: string; + + @Column() + @Required() + @Property() + size: number; + + // @ManyToOne(type => User, user => user.attachmentsList) + // @Property() + // @Required() + // user: User; +} diff --git a/src/server/modules/attachments/service/attachments.service.ts b/src/server/modules/attachments/service/attachments.service.ts new file mode 100755 index 0000000..d9387dc --- /dev/null +++ b/src/server/modules/attachments/service/attachments.service.ts @@ -0,0 +1,101 @@ +import { ServerSettingsService } from '@tsed/common'; +import { Service } from '@tsed/di'; +import { TypeORMService } from '@tsed/typeorm'; +import { EntityManager, Repository } from 'typeorm'; +import * as path from 'path'; +import * as fse from 'fs-extra'; + +import { AbstractEntityService } from '../../common/util/service/abstract-entity.service'; +import { Attachments } from '../entity/attachments.entity'; + +@Service() +export class AttachmentsService extends AbstractEntityService { + private attachmentsOptions: any; + + constructor( + protected serverSettingsService: ServerSettingsService, + typeORMService: TypeORMService + ) { + super(typeORMService); + } + + $beforeRoutesInit() { + this.attachmentsOptions = + this.serverSettingsService.get(`attachments`) || ({} as any); + } + + getRepository(entityManager: EntityManager): Repository { + return entityManager.getRepository(Attachments); + } + + async move(file: any, attachments: Attachments): Promise { + return new Promise((resolve, reject) => { + if (!attachments.id) { + return reject(`id of Attachments is not valid`); + } + let targetPath = path.join(this.attachmentsOptions.storageDir); + if (!fse.existsSync(targetPath)) { + fse.mkdirSync(targetPath); + } + for (let index = 0; index < 6; index++) { + const char = attachments.id.charAt(index); + targetPath = path.join(targetPath, `${char}`); + if (!fse.existsSync(targetPath)) { + fse.mkdirSync(targetPath); + } + } + fse.moveSync(path.join(file.path), path.join(targetPath, attachments.id)); + return resolve(); + }); + } + + async getPath( + attachments: Attachments, + options: string[] = [] + ): Promise { + return new Promise((resolve, reject) => { + if (!attachments.id) { + return reject(`id of Attachments is not valid`); + } + let targetPath = path.join(this.attachmentsOptions.storageDir); + if (!fse.existsSync(targetPath)) { + return reject(`Attachments is not exist`); + } + for (let index = 0; index < 6; index++) { + const char = attachments.id.charAt(index); + targetPath = path.join(targetPath, `${char}`); + } + + if (!fse.existsSync(targetPath)) { + return reject(`Attachments is not exist`); + } + + let fileName = attachments.id; + for (let index = 0; index < options.length; index++) { + const option = options[index]; + if (!option) { + break; + } + fileName = `${fileName}_${option}`; + } + + return resolve(path.join(targetPath, fileName)); + }); + } + + async getFile( + attachments: Attachments, + options: string[] = [] + ): Promise { + return new Promise(async (resolve, reject) => { + const filePath = await this.getPath(attachments, options); + + fse.readFile(filePath, (err, data) => { + if (err) { + return reject(err); + } + return resolve(data); + }); + }); + } +} diff --git a/src/server/modules/cartoons/controller/cartoons-series.controller.ts b/src/server/modules/cartoons/controller/cartoons-series.controller.ts new file mode 100755 index 0000000..2930662 --- /dev/null +++ b/src/server/modules/cartoons/controller/cartoons-series.controller.ts @@ -0,0 +1,60 @@ +/** + * 파 일 명: cartoons-series.controller.ts + * 작성일자: 2018-12-31 + * 작 성 자: 박병준 + * 설 명: cartoons series entry point를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import * as express from 'express'; +import { + Controller, Required, PathParams, Get, Put, Req, Res, Authenticated, BodyParams +} from '@tsed/common'; +import { UserService } from '../../user/service/user.service'; +import { User } from '../../user/entity/user.entity'; + +import { CartoonsSeries } from '../entity/cartoons-series.entity'; +import { CartoonsSeriesService } from '../service/cartoons-series.service'; +import { AbstractController } from '../../common/util/controller/abstract.controller'; + +@Controller('/cartoons_series') +export class CartoonsSeriesController extends AbstractController { + + constructor( + private cartoonsSeriesService: CartoonsSeriesService, + private userService: UserService, + ) { + super(cartoonsSeriesService); + } + + @Get('/:uid') + async getAllByUid( + @Req() req: express.Request, + @Res() res: express.Response, + @Required() @PathParams('uid') uid: string, + ): Promise { + const user = await this.userService.find(uid); + const articleSeriesList = await this.cartoonsSeriesService.findAllByUser(user); + + if (articleSeriesList) { + return articleSeriesList; + } + + res.status(203).send('No Content'); + } + + @Put('/') + @Authenticated({ roles: ['R_USER'] }) + save( + @Req() req: express.Request, + @Res() res: express.Response, + @BodyParams() cartoonsSeries: CartoonsSeries, + ): Promise { + cartoonsSeries.user = req['user']; + + return this.cartoonsSeriesService.save(cartoonsSeries); + } + +} diff --git a/src/server/modules/cartoons/controller/cartoons.controller.ts b/src/server/modules/cartoons/controller/cartoons.controller.ts new file mode 100755 index 0000000..d0392ba --- /dev/null +++ b/src/server/modules/cartoons/controller/cartoons.controller.ts @@ -0,0 +1,75 @@ +import * as express from 'express'; +import { + Controller, Put, Post, Delete, BodyParams, Required, Authenticated, PathParams, Get, Req, Res, QueryParams, +} from '@tsed/common'; + +import { CartoonsService } from '../service/cartoons.service'; +import { Cartoons } from '../entity/cartoons.entity'; +import { AbstractController } from '../../common/util/controller/abstract.controller'; + +@Controller('/cartoons') +export class CartoonsController extends AbstractController { + + constructor( + private cartoonsService: CartoonsService, + ) { + super(cartoonsService); + } + + @Get('/:id') + async get( + @Req() req: express.Request, + @Res() res: express.Response, + @Required() @PathParams('id') id: string + ): Promise { + const model = await this.cartoonsService.find(id); + + if (model) { + return model; + } + + res.status(203).send('No Content'); + } + + @Put('/') + @Authenticated({ roles: ['R_USER'] }) + save( + @Req() req: express.Request, + @Res() res: express.Response, + @BodyParams() cartoons: Cartoons, + ): Promise { + cartoons.user = req['user']; + + return this.cartoonsService.save(cartoons); + } + + @Post('/') + @Authenticated({ roles: ['R_USER'] }) + async update( + @BodyParams() cartoons: Cartoons + ): Promise { + return this.cartoonsService.save(cartoons); + } + + @Delete('/') + @Authenticated({ roles: ['R_USER'] }) + async remove(@BodyParams('id') @Required() id: string): Promise { + return this.cartoonsService.remove(id); + } + + @Get('/') + async getCartoonsWithPagination( + @Req() req: express.Request, + @Res() res: express.Response, + @Required() @QueryParams('pageSize') pageSize: number, + @Required() @QueryParams('page') page: number, + ): Promise { + const cartoonsList = await this.cartoonsService.findAllWithPagination(pageSize, page); + + if (cartoonsList && 0 < cartoonsList.length) { + return cartoonsList; + } + + res.status(203).send('No Content'); + } +} diff --git a/src/server/modules/cartoons/entity/cartoons-attachments.entity.ts b/src/server/modules/cartoons/entity/cartoons-attachments.entity.ts new file mode 100755 index 0000000..358e4e1 --- /dev/null +++ b/src/server/modules/cartoons/entity/cartoons-attachments.entity.ts @@ -0,0 +1,25 @@ +/** + * 파 일 명: article-attachments.entity.ts + * 작성일자: 2018-12-13 + * 작 성 자: 박병준 + * 설 명: Article Attachments 관리 Entity 스키마를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Property } from '@tsed/common'; +import { Column, ChildEntity, ManyToOne } from 'typeorm'; + +import { Cartoons } from './cartoons.entity'; +import { ArticleAttachments } from '../../article/entity/article-attachments.entity'; + +@ChildEntity('Cartoons') +export class CartoonsAttachments extends ArticleAttachments { + @ManyToOne(type => Cartoons, cartoons => cartoons.attachmentsList) + article: Cartoons; + + @Column() + @Property() + sequence: number; +} diff --git a/src/server/modules/cartoons/entity/cartoons-series.entity.ts b/src/server/modules/cartoons/entity/cartoons-series.entity.ts new file mode 100755 index 0000000..4301f4a --- /dev/null +++ b/src/server/modules/cartoons/entity/cartoons-series.entity.ts @@ -0,0 +1,17 @@ +/** + * 파 일 명: cartoons-series.entity.ts + * 작성일자: 2018-12-13 + * 작 성 자: 박병준 + * 설 명: Cartoons series 관리 Entity 스키마를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { ChildEntity } from 'typeorm'; + +import { ArticleSeries } from '../../article/entity/article-series.entity'; + +@ChildEntity('Cartoons') +export class CartoonsSeries extends ArticleSeries { +} diff --git a/src/server/modules/cartoons/entity/cartoons.entity.ts b/src/server/modules/cartoons/entity/cartoons.entity.ts new file mode 100755 index 0000000..91db372 --- /dev/null +++ b/src/server/modules/cartoons/entity/cartoons.entity.ts @@ -0,0 +1,24 @@ +/** + * 파 일 명: cartoons.entity.ts + * 작성일자: 2018-12-13 + * 작 성 자: 박병준 + * 설 명: Cartoons 관리 Entity 스키마를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Property } from '@tsed/common'; +import { ChildEntity, OneToMany } from 'typeorm'; + +import { Article } from '../../article/entity/article.entity'; +import { CartoonsAttachments } from './cartoons-attachments.entity'; + +@ChildEntity() +export class Cartoons extends Article { + @OneToMany(type => CartoonsAttachments, cartoonsAttachments => cartoonsAttachments.article, { + eager: true, + }) + @Property() + attachmentsList: CartoonsAttachments[]; +} diff --git a/src/server/modules/cartoons/service/cartoons-attachments.service.ts b/src/server/modules/cartoons/service/cartoons-attachments.service.ts new file mode 100755 index 0000000..c4be668 --- /dev/null +++ b/src/server/modules/cartoons/service/cartoons-attachments.service.ts @@ -0,0 +1,17 @@ +import { Service } from '@tsed/di'; +import { TypeORMService } from '@tsed/typeorm'; +import { EntityManager, Repository } from 'typeorm'; + +import { AbstractEntityService } from '../../common/util/service/abstract-entity.service'; +import { CartoonsAttachments } from '../entity/cartoons-attachments.entity'; + +@Service() +export class CartoonsAttachmentsService extends AbstractEntityService { + constructor(typeORMService: TypeORMService) { + super(typeORMService); + } + + getRepository(entityManager: EntityManager): Repository { + return entityManager.getRepository(CartoonsAttachments); + } +} diff --git a/src/server/modules/cartoons/service/cartoons-series.service.ts b/src/server/modules/cartoons/service/cartoons-series.service.ts new file mode 100755 index 0000000..5471828 --- /dev/null +++ b/src/server/modules/cartoons/service/cartoons-series.service.ts @@ -0,0 +1,39 @@ +import { Service } from '@tsed/di'; +import { TypeORMService } from '@tsed/typeorm'; +import { EntityManager, Repository } from 'typeorm'; + +import { AbstractEntityService } from '../../common/util/service/abstract-entity.service'; +import { User } from '../../user/entity/user.entity'; +import { CartoonsSeries } from '../entity/cartoons-series.entity'; + +@Service() +export class CartoonsSeriesService extends AbstractEntityService { + constructor(typeORMService: TypeORMService) { + super(typeORMService); + } + + getRepository(entityManager: EntityManager): Repository { + return entityManager.getRepository(CartoonsSeries); + } + + async findAllByUser(user: User): Promise { + return this.repository.find({ where: { user: user } }); + } + + async save(cartoonsSeries: CartoonsSeries): Promise { + const queryRunner = this.typeORMService.get('sqlite').createQueryRunner(); + await queryRunner.connect(); + + await queryRunner.startTransaction(); + try { + const _cartoonsSeries = await super.saveWithTransaction(queryRunner.manager, cartoonsSeries); + await queryRunner.commitTransaction(); + return _cartoonsSeries; + } catch (error) { + console.log(error); + await queryRunner.rollbackTransaction(); + } finally { + await queryRunner.release(); + } + } +} diff --git a/src/server/modules/cartoons/service/cartoons.service.ts b/src/server/modules/cartoons/service/cartoons.service.ts new file mode 100755 index 0000000..471eaf6 --- /dev/null +++ b/src/server/modules/cartoons/service/cartoons.service.ts @@ -0,0 +1,139 @@ +import { ServerSettingsService, BeforeRoutesInit } from '@tsed/common'; +import { Service } from '@tsed/di'; +import { TypeORMService } from '@tsed/typeorm'; +import { EntityManager, Repository } from 'typeorm'; + +import { ArticleService } from '../../article/service/article.service'; +import { Cartoons } from '../entity/cartoons.entity'; +import { CartoonsAttachmentsService } from './cartoons-attachments.service'; +import { CartoonsAttachments } from '../entity/cartoons-attachments.entity'; +import { AbstractEntityService } from '../../common/util/service/abstract-entity.service'; +import { + ThumbnailsService, + ThumbnailsType, + ThumbnailsOption +} from '../../thumbnails/service/thumbnails.service'; +import { ArticleService as MongdbArticleService } from '../../../mongodb/article/service/article.service'; +import { ArticleType } from '../../../../shared/article/type/article-type.type'; +import { ArticleImage } from '../../../../shared/article/type/article-image.type'; +import { ImageThumbnails } from '../../../../shared/attachments/type/image.type'; +import { ImageConfig } from '../../../../shared/attachments/type/image-config.type'; + +@Service() +export class CartoonsService extends AbstractEntityService implements BeforeRoutesInit { + private imageOptions: Map; + + constructor( + typeORMService: TypeORMService, + private articleService: ArticleService, + private mongdbArticleService: MongdbArticleService, + private cartoonsAttachmentsService: CartoonsAttachmentsService, + private thumbnailsService: ThumbnailsService, + private serverSettingsService: ServerSettingsService, + ) { + super(typeORMService); + } + + $beforeRoutesInit() { + this.imageOptions = this.serverSettingsService.get(`images.article`) || {} as any; + } + + getRepository(entityManager: EntityManager): Repository { + return entityManager.getRepository(Cartoons); + } + + async save(cartoons: Cartoons): Promise { + const queryRunner = this.createQueryRunner(); + await queryRunner.connect(); + + await queryRunner.startTransaction(); + + try { + let cartoonsAttachmentsList: CartoonsAttachments[] = null; + if (cartoons.attachmentsList && 0 < cartoons.attachmentsList.length) { + cartoonsAttachmentsList = []; + + for (let index = 0; index < cartoons.attachmentsList.length; index++) { + const attachments = cartoons.attachmentsList[index]; + const cartoonsAttachments = await this.cartoonsAttachmentsService.saveWithTransaction( + queryRunner.manager, + attachments, + ); + cartoonsAttachmentsList.push(cartoonsAttachments); + } + cartoons.attachmentsList = cartoonsAttachmentsList; + } + + cartoons.tagList = await this.articleService.saveTagsWithTransactions(queryRunner.manager, cartoons); + + const _cartoons = await super.saveWithTransaction(queryRunner.manager, cartoons); + + if (cartoonsAttachmentsList && 0 < cartoonsAttachmentsList.length) { + for (let index = 0; index < cartoonsAttachmentsList.length; index++) { + const cartoonsAttachments = cartoonsAttachmentsList[index]; + const optionList: ThumbnailsOption[] = []; + const thumbnailsList: Map = this.imageOptions.get(ArticleImage.Contents).thumbnails; + thumbnailsList.forEach((thumbnails, key) => { + optionList.push({ + type: ThumbnailsType.SCALE, + option: { + key: key, + width: thumbnails.width, + height: thumbnails.height, + } + }); + }); + + if (ArticleImage.Cover === cartoonsAttachments.imageType) { + const coverThumbnailsList: Map = this.imageOptions.get(ArticleImage.Cover).thumbnails; + coverThumbnailsList.forEach((thumbnails, key) => { + optionList.push({ + type: ThumbnailsType.COVER, + option: { + key: key, + width: thumbnails.width, + height: thumbnails.height, + } + }); + }); + } + await this.thumbnailsService.generate( + cartoonsAttachments.attachments, + optionList, + ); + } + } + await queryRunner.commitTransaction(); + _cartoons.type = ArticleType.Cartoons; + this.mongdbArticleService.saveModel(_cartoons); + return _cartoons; + } catch (error) { + console.log(error); + await queryRunner.rollbackTransaction(); + } finally { + await queryRunner.release(); + } + } + + async findAllWithPagination( + pageSize: number, + page: number + ): Promise { + return new Promise(async (resolve, reject) => { + page = page > 0 ? page : 1; + const skip = pageSize * (page - 1); + + await this.repository + .createQueryBuilder('cartoons') + .skip(skip) + .take(pageSize) + .getMany() + .then(cartoonsList => { + return resolve(cartoonsList); + }) + .catch(reason => { + return reject(reason); + }); + }); + } +} diff --git a/src/server/modules/common/converter/date.converter.ts b/src/server/modules/common/converter/date.converter.ts new file mode 100755 index 0000000..89db865 --- /dev/null +++ b/src/server/modules/common/converter/date.converter.ts @@ -0,0 +1,12 @@ +import { Converter, IConverter } from '@tsed/common'; + +@Converter(Date) +export class DateConverter implements IConverter { + deserialize(data: string): Date { + return new Date(data); + } + + serialize(object: Date): any { + return object.toUTCString(); + } +} diff --git a/src/server/modules/common/util/controller/abstract-rest.controller.ts b/src/server/modules/common/util/controller/abstract-rest.controller.ts new file mode 100755 index 0000000..e726410 --- /dev/null +++ b/src/server/modules/common/util/controller/abstract-rest.controller.ts @@ -0,0 +1,67 @@ +/** + * 파 일 명: abstract-rest.controller.ts + * 작성일자: 2018-12-17 + * 작 성 자: 박병준 + * 설 명: express에서 사용되는 RESTful Controller의 공통 사항을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import * as express from 'express'; +import { + BodyParams, + Delete, + Get, + PathParams, + Post, + Put, + Required, + Req, + Res, +} from '@tsed/common'; +import { AbstractEntityService } from '../service/abstract-entity.service'; +import { Base } from '../entity/base.entity'; +import { AbstractController } from './abstract.controller'; + +export abstract class AbstractRestController extends AbstractController { + + constructor(service: AbstractEntityService) { + super(service); + } + + @Get('/:id') + async get( + @Req() req: express.Request, + @Res() res: express.Response, + @Required() @PathParams('id') id: string, + ): Promise { + const model = await this.service.find(id); + + if (model) { + return model; + } + + res.status(203).send('No Content'); + } + + @Get('/') + async getAll(): Promise { + return this.service.findAll(); + } + + @Put('/') + save(@BodyParams() model: T) { + return this.service.save(model); + } + + @Post('/') + async update(@BodyParams() model: T): Promise { + return this.service.save(model); + } + + @Delete('/:id') + async remove(@PathParams('id') @Required() id: string): Promise { + return this.service.remove(id); + } +} diff --git a/src/server/modules/common/util/controller/abstract.controller.ts b/src/server/modules/common/util/controller/abstract.controller.ts new file mode 100755 index 0000000..a1580e3 --- /dev/null +++ b/src/server/modules/common/util/controller/abstract.controller.ts @@ -0,0 +1,20 @@ +/** + * 파 일 명: abstract.controller.ts + * 작성일자: 2018-12-17 + * 작 성 자: 박병준 + * 설 명: express에서 사용되는 Controller의 공통 사항을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import * as express from 'express'; +import { AbstractEntityService } from '../service/abstract-entity.service'; +import { Base } from '../entity/base.entity'; + +export abstract class AbstractController { + + constructor(protected service: AbstractEntityService) { + } + +} diff --git a/src/server/modules/common/util/entity/base.entity.ts b/src/server/modules/common/util/entity/base.entity.ts new file mode 100755 index 0000000..a1f2dbc --- /dev/null +++ b/src/server/modules/common/util/entity/base.entity.ts @@ -0,0 +1,41 @@ +/** + * 파 일 명: base.entity.ts + * 작성일자: 2018-12-20 + * 작 성 자: 박병준 + * 설 명: Base Entity 스키마를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Column, Index, CreateDateColumn, UpdateDateColumn, PrimaryColumn } from 'typeorm'; +import { Property, Required, MaxLength, IgnoreProperty } from '@tsed/common'; + +export abstract class Base { + @PrimaryColumn({ + type: 'varchar', + length: 22, + unique: true, + }) + @Property() + @MaxLength(22) + id: string; + + @CreateDateColumn() + @Property() + createDate: Date; + + @UpdateDateColumn() + @Property() + updateDate: Date; + + @Column({ + nullable: true, + }) + @Property() + deleteDate: Date; + + constructor(id?: string) { + this.id = id; + } +} diff --git a/src/server/modules/common/util/passport/oneall/index.ts b/src/server/modules/common/util/passport/oneall/index.ts new file mode 100755 index 0000000..a1d74e6 --- /dev/null +++ b/src/server/modules/common/util/passport/oneall/index.ts @@ -0,0 +1 @@ +export * from './strategy'; diff --git a/src/server/modules/common/util/passport/oneall/strategy.ts b/src/server/modules/common/util/passport/oneall/strategy.ts new file mode 100755 index 0000000..54b1dc1 --- /dev/null +++ b/src/server/modules/common/util/passport/oneall/strategy.ts @@ -0,0 +1,138 @@ +import * as express from 'express'; +import * as Passport from 'passport'; +import { Strategy as PassportStrategy } from 'passport-strategy'; + +const Oneall = require('oneall'); + +const StrategyName = 'oneall'; + +export interface Profile extends Passport.Profile { + // user_token + id: string; + // oneall + provider: string; + + _raw: string; + _json: any; +} + +export interface StrategyOption { + oneall: any; +} + +export interface StrategyOptionWithRequest extends StrategyOption { + passReqToCallback: boolean; +} + +export type VerifyFunction = ( + // identity_token + accessToken: string, + profile: Profile, + done: (error: any, user?: any, info?: any) => void +) => void; + +export type VerifyFunctionWithRequest = ( + req: express.Request, + accessToken: string, + profile: Profile, + done: (error: any, user?: any, info?: any) => void +) => void; + +export interface AuthenticateOptions extends Passport.AuthenticateOptions { + oaAction?: string; + oaConnector?: string; + connectionToken: string; +} + +export class Strategy extends PassportStrategy { + name: string; + oneAll: any; + options: StrategyOption | StrategyOptionWithRequest; + verify: VerifyFunction | VerifyFunctionWithRequest; + + constructor( + options: StrategyOption | StrategyOptionWithRequest, + verify: VerifyFunction | VerifyFunctionWithRequest + ) { + super(); + + this.options = { + ...options, + }; + this.verify = verify; + + this.name = StrategyName; + } + + authenticate(req: express.Request, options?: AuthenticateOptions): void { + if (!options || !options.connectionToken) { + return this.fail({ message: 'options is not valid' }, 400); + } + + const done = (error: any, user?: any, info?: any) => { + if (error) { + return this.error(error); + } + if (!user) { + return this.fail(info); + } + this.success(user, info); + }; + + this.profile(options.connectionToken) + .then((value) => { + if (undefined === this.options['passReqToCallback']) { + (this.verify as VerifyFunction)(value.accessToken, value.profile, done); + } else { + (this.verify as VerifyFunctionWithRequest)(req, value.accessToken, value.profile, done); + } + }) + .catch((reason) => { + this.error(reason); + }); + } + + profile(connectionToken: string): Promise<{ accessToken: string, profile: Profile }> { + return new Promise<{ accessToken: string, profile: Profile }>((resolve, reject) => { + this.options.oneall.connection.getUser( + connectionToken, + (err, data, fullData) => { + if (err) { + return reject(err); + } + + const profile: Profile = { + id: data.data.user_token, + displayName: data.data.identity.displayName, + provider: data.data.identity.provider, + _raw: JSON.stringify(data.data.identity), + _json: data.data.identity, + }; + + if (data.data.identity.name) { + profile.name = { + familyName: data.data.identity.name.familyName, + givenName: data.data.identity.name.givenName, + middleName: data.data.identity.name.middleName, + }; + } + + if (data.data.identity.emails) { + profile.emails = []; + for (let index = 0; index < data.data.identity.emails.length; index++) { + const email = data.data.identity.emails[index]; + profile.emails.push({ + value: email.value, + }); + } + } + + return resolve({ + accessToken: data.data.identity.identity_token, + profile, + }); + } + ); + }); + } +} diff --git a/src/server/modules/common/util/service/abstract-entity.service.ts b/src/server/modules/common/util/service/abstract-entity.service.ts new file mode 100755 index 0000000..79cb9ae --- /dev/null +++ b/src/server/modules/common/util/service/abstract-entity.service.ts @@ -0,0 +1,101 @@ +/** + * 파 일 명: abstract-entity.service.ts + * 작성일자: 2018-12-17 + * 작 성 자: 박병준 + * 설 명: express에서 사용되는 Repository Service의 공통 사항을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { AfterRoutesInit } from '@tsed/common'; +import { TypeORMService } from '@tsed/typeorm'; +import { Repository, EntityManager, QueryRunner } from 'typeorm'; +import { generate } from 'short-uuid'; + +import { Base } from '../entity/base.entity'; + +export abstract class AbstractEntityService implements AfterRoutesInit { + protected repository: Repository; + + constructor(protected typeORMService: TypeORMService) { + } + + $afterRoutesInit() { + this.repository = this.getRepository(this.typeORMService.get('sqlite').manager); + } + + async id(): Promise { + return new Promise(async (resolve, reject) => { + for (let index = 0; index < 6; index++) { + const id = generate(); + const e = await this.repository.findOne(id); + if (!e) { + return resolve(id); + } + } + return reject(`Cannot generate id of Entity`); + }); + } + + async find(id: string): Promise { + return this.repository.findOne(id); + } + + async findAll(options = {}): Promise { + return this.repository.find(options); + } + + async save(model: T): Promise { + if (!model.id) { + model.id = await this.id(); + } + return this.repository.save(model as any); + } + + async saveAll(modelList: T[]): Promise { + if (!modelList || 0 === modelList.length) { + return null; + } + + for (const model of modelList) { + if (!model.id) { + model.id = await this.id(); + } + } + + return this.repository.save(modelList as any[]); + } + + async remove(id: string): Promise { + // undefined 오류로 인한 수정 + // const result = await this.repository.delete(id); + // return result.affected; + await this.repository.delete(id); + return 1; + } + + async removeAll(ids: string[]): Promise { + const result = await this.repository.delete(ids); + return result.affected; + } + + async saveWithTransaction(entityManager: EntityManager, model: T): Promise { + if (!model.id) { + model.id = await this.id(); + } + return this.getRepository(entityManager).save(model as any); + } + + async removeWithTransaction(entityManager: EntityManager, id: string): Promise { + const result = await this.getRepository(entityManager).delete(id); + return result.affected; + } + + createQueryRunner(): QueryRunner { + return this.typeORMService.get('sqlite').createQueryRunner(); + } + + abstract getRepository(entityManager: EntityManager): Repository; + +} diff --git a/src/server/modules/contents/controller/contents.controller.ts b/src/server/modules/contents/controller/contents.controller.ts new file mode 100755 index 0000000..ef1c88e --- /dev/null +++ b/src/server/modules/contents/controller/contents.controller.ts @@ -0,0 +1,188 @@ +/** + * 파 일 명: contents.controller.ts + * 작성일자: 2019-01-20 + * 작 성 자: 박병준 + * 설 명: Contents entry point를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { + Controller, + Req, + Res, + Get, + Required, + PathParams, + BeforeRoutesInit, + Post, + QueryParams, +} from '@tsed/common'; +import * as express from 'express'; +import { Contents } from '../model/contents.model'; +import { ContentsSize } from '../../../../shared/contents/model/contents-size.model'; +import { ArticleType } from '../../../../shared/article/type/article-type.type'; +import { ContentsRequest } from '../../../../shared/contents/type/contents-request.type'; +import { ContentsRequestUser } from '../../../../shared/contents/type/contents-request-user.type'; +import { ContentsMongodbService } from '../service/contents-mongodb.service'; +import { ContentsRandomService } from '../service/contents-random.service'; +import { ContentsGuestService } from '../service/contents-guest.service'; +import { ContentsUserService } from '../service/contents-user.service'; +import { ContentsSearchService } from '../service/contents-search.service'; +import { ContentsSearch } from '../../../../shared/contents/type/contents-search.type'; +import { BadRequest } from 'ts-httpexceptions'; + +@Controller('/contents') +export class ContentsController implements BeforeRoutesInit { + + constructor( + private contentsMongodbService: ContentsMongodbService, + private contentsRandomService: ContentsRandomService, + private contentsGuestService: ContentsGuestService, + private contentsUserService: ContentsUserService, + private contentsSearchService: ContentsSearchService, + ) { + } + + $beforeRoutesInit() { + } + + @Post('/mongodb/article') + async updateMongoArticle( + @Req() req: express.Request, + @Res() res: express.Response, + ): Promise { + return this.contentsMongodbService.saveArticle(); + } + + @Post('/random/article/:size') + async updateRandomArticle( + @Req() req: express.Request, + @Res() res: express.Response, + @Required() @PathParams('size') size: number, + ): Promise { + return this.contentsRandomService.saveArticle(size); + } + + @Post('/random/author/:size') + async updateRandomAuthor( + @Req() req: express.Request, + @Res() res: express.Response, + @Required() @PathParams('size') size: number, + ): Promise { + return this.contentsRandomService.saveAuthor(size); + } + + + @Get(`/${ContentsRequest.Recommendations}/${ContentsRequestUser.Guest}/:articleType/:page`) + async getAllByRecommendationsWithGuest( + @Req() req: express.Request, + @Res() res: express.Response, + @Required() @PathParams('articleType') articleType: ArticleType, + @Required() @PathParams('page') page: number, + @Required() @QueryParams('contentsSizeList') _contentsSizeList: string, + ): Promise { + let contentsSizeList: ContentsSize[]; + try { + contentsSizeList = JSON.parse(_contentsSizeList); + } catch (error) { + throw new BadRequest('contentsSizeList is not valid'); + } + + const modelList = await this.contentsGuestService.findAll( + articleType, + contentsSizeList, + page + ); + + if (modelList) { + return modelList; + } + + res.status(203).send('No Content'); + } + + + @Get(`/${ContentsRequest.Recommendations}/${ContentsRequestUser.User}/:uid/:articleType/:page`) + async getAllByRecommendationsWithUser( + @Req() req: express.Request, + @Res() res: express.Response, + @Required() @PathParams('uid') uid: string, + @Required() @PathParams('articleType') articleType: ArticleType, + @Required() @PathParams('page') page: number, + @Required() @QueryParams('contentsSizeList') _contentsSizeList: string, + ): Promise { + let contentsSizeList: ContentsSize[]; + try { + contentsSizeList = JSON.parse(_contentsSizeList); + } catch (error) { + throw new BadRequest('contentsSizeList is not valid'); + } + + const modelList = await this.contentsUserService.findAllByRecommendations( + uid, + articleType, + contentsSizeList, + page + ); + + if (modelList) { + return modelList; + } + + res.status(203).send('No Content'); + } + + @Get(`/${ContentsRequest.Following}/:uid/:articleType/:page`) + async getAllByFollowingWithUser( + @Req() req: express.Request, + @Res() res: express.Response, + @Required() @PathParams('uid') uid: string, + @Required() @PathParams('articleType') articleType: ArticleType, + @Required() @PathParams('page') page: number, + @Required() @QueryParams('contentsSizeList') _contentsSizeList: string, + ): Promise { + let contentsSizeList: ContentsSize[]; + try { + contentsSizeList = JSON.parse(_contentsSizeList); + } catch (error) { + throw new BadRequest('contentsSizeList is not valid'); + } + const modelList = await this.contentsUserService.findAllByFollowing( + uid, + articleType, + contentsSizeList, + page + ); + + if (modelList) { + return modelList; + } + + res.status(203).send('No Content'); + } + + @Get(`/${ContentsRequest.Search}/${ContentsSearch.Tag}/:tagName/:page`) + async getAllBySearchWithTag( + @Req() req: express.Request, + @Res() res: express.Response, + @Required() @PathParams('tagName') tagName: string, + @Required() @PathParams('page') page: number, + @Required() @QueryParams('contentsSizeList') _contentsSizeList: string, + ): Promise { + let contentsSizeList: ContentsSize[]; + try { + contentsSizeList = JSON.parse(_contentsSizeList); + } catch (error) { + throw new BadRequest('contentsSizeList is not valid'); + } + const modelList = await this.contentsSearchService.findAllByTagName(tagName, contentsSizeList, page); + + if (modelList) { + return modelList; + } + + res.status(203).send('No Content'); + } +} diff --git a/src/server/modules/contents/model/contents.model.ts b/src/server/modules/contents/model/contents.model.ts new file mode 100755 index 0000000..4c35a52 --- /dev/null +++ b/src/server/modules/contents/model/contents.model.ts @@ -0,0 +1,39 @@ +/** + * 파 일 명: contents.model.ts + * 작성일자: 2019-01-13 + * 작 성 자: 박병준 + * 설 명: contents model 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Property, } from '@tsed/common'; + +import { ContentsType } from '../../../../shared/contents/type/contents-type.type'; +import { ArticleMongodb } from '../../../../shared/article/model/article.mongodb.model'; +import { User } from '../../user/entity/user.entity'; +import { Advertisements } from '../../advertisements/entity/advertisements.entity'; +import { UserSupportEvent } from '../../user-support/entity/user-support-event.entity'; + +export class Contents { + @Property() + type: ContentsType; + + @Property() + display: any; + + @Property() + data: ContentsDataType; +} + +export type ContentsDataType = + | ArticleMongodb + | ArticleMongodb[] + | User + | User[] + | Advertisements + | Advertisements[] + | UserSupportEvent + | UserSupportEvent[] + ; diff --git a/src/server/modules/contents/service/contents-guest.service.ts b/src/server/modules/contents/service/contents-guest.service.ts new file mode 100755 index 0000000..4df41f0 --- /dev/null +++ b/src/server/modules/contents/service/contents-guest.service.ts @@ -0,0 +1,84 @@ +import { Service } from '@tsed/di'; +import { In } from 'typeorm'; + +import { UserService } from '../../user/service/user.service'; +import { UserRandomService as MongdbUserRandomService } from '../../../mongodb/user/service/user-random.service'; +import { UserSupportEventService } from '../../user-support/service/user-support-event.service'; +import { AdvertisementsService } from '../../advertisements/service/advertisements.service'; +import { Article as ArticleMongodb } from '../../../mongodb/article/model/article.model'; +import { ArticleRandomService as MongdbArticleRandomService } from '../../../mongodb/article/service/article-random.service'; +import { ArticleType } from '../../../../shared/article/type/article-type.type'; +import { ContentsSize } from '../../../../shared/contents/model/contents-size.model'; +import { Contents } from '../model/contents.model'; +import { ContentsType } from '../../../../shared/contents/type/contents-type.type'; +import { ContentsUtil } from '../../../../shared/contents/util/contents.util'; +import { User } from '../../user/entity/user.entity'; +import { Advertisements } from '../../advertisements/entity/advertisements.entity'; +import { UserSupportEvent } from '../../user-support/entity/user-support-event.entity'; +import { ContentsService } from './contents.service'; +import { ArticleService } from '../../article/service/article.service'; + +@Service() +export class ContentsGuestService extends ContentsService { + constructor( + private mongdbArticleRandomService: MongdbArticleRandomService, + private userService: UserService, + private mongdbUserRandomService: MongdbUserRandomService, + private userSupportEventService: UserSupportEventService, + private advertisementsService: AdvertisementsService, + private articleService: ArticleService, + ) { + super(); + } + + protected async findAllArticle(articleType: ArticleType, contentsSizeList: ContentsSize[], page: number): Promise { + const articleSize = ContentsUtil.getSizeByContentsSize(ContentsType.Article, contentsSizeList); + let articleList: ArticleMongodb[] = []; + if (0 < articleSize) { + articleList = await this.mongdbArticleRandomService.findAllByPage(articleType, articleSize, page); + } + return articleList; + } + + protected async findAllFeaturedAuthor(contentsSizeList: ContentsSize[], page: number): Promise { + const featuredAuthorSize = ContentsUtil.getSizeByContentsSize(ContentsType.FeaturedAuthor, contentsSizeList); + let featuredAuthorList: User[] = []; + if (0 < featuredAuthorSize) { + const uidRandomList = await this.mongdbUserRandomService.findAllByPage(featuredAuthorSize, page); + if (uidRandomList && 0 < uidRandomList.length) { + featuredAuthorList = await this.userService.findAll({ id: In(uidRandomList) }); + } + } + return featuredAuthorList; + } + + protected async findAllEvent(contentsSizeList: ContentsSize[], page: number): Promise { + const eventSize = ContentsUtil.getSizeByContentsSize(ContentsType.Event, contentsSizeList); + let eventList: UserSupportEvent[] = []; + if (0 < eventSize) { + eventList = await this.userSupportEventService.findAllByPage(eventSize, page); + } + return eventList; + } + + protected async findAllAdvertisements(contentsSizeList: ContentsSize[], page: number): Promise { + const advertisementsSize = ContentsUtil.getSizeByContentsSize(ContentsType.Advertisements, contentsSizeList); + let advertisementsList: Advertisements[] = []; + if (0 < advertisementsSize) { + advertisementsList = await this.advertisementsService.findAllByPage(advertisementsSize, page); + } + return advertisementsList; + } + + async findAll(articleType: ArticleType, contentsSizeList: ContentsSize[], page: number): Promise { + const articleList = await this.findAllArticle(articleType, contentsSizeList, page); + await this.articleService.increaseViewCount(articleList); + + const featuredAuthorList = await this.findAllFeaturedAuthor(contentsSizeList, page); + const advertisementsList = await this.findAllAdvertisements(contentsSizeList, page); + const eventList = await this.findAllEvent(contentsSizeList, page); + + return super.getContentsList(articleList, featuredAuthorList, advertisementsList, eventList, contentsSizeList); + } + +} diff --git a/src/server/modules/contents/service/contents-mongodb.service.ts b/src/server/modules/contents/service/contents-mongodb.service.ts new file mode 100755 index 0000000..abc2a71 --- /dev/null +++ b/src/server/modules/contents/service/contents-mongodb.service.ts @@ -0,0 +1,49 @@ +import { Service } from '@tsed/di'; + +import { ArticleService as MongdbArticleService } from '../../../mongodb/article/service/article.service'; +import { ArticleType } from '../../../../shared/article/type/article-type.type'; +import { ArticleService } from '../../article/service/article.service'; +import { CartoonsService } from '../../cartoons/service/cartoons.service'; + +@Service() +export class ContentsMongodbService { + constructor( + private articleService: ArticleService, + private cartoonsService: CartoonsService, + private mongdbArticleService: MongdbArticleService, + ) { + } + + async saveArticle(): Promise { + const articleList = await this.articleService.findAll({ + select: ['id', 'type'] + }); + if (!articleList || 0 === articleList.length) { + return 0; + } + + for (const article of articleList) { + switch (article.type) { + case ArticleType.Cartoons: + { + const cartoons = await this.cartoonsService.find(article.id); + await this.mongdbArticleService.saveModel(cartoons); + } + break; + case ArticleType.Illustrations: + { + + } + break; + case ArticleType.Novel: + { + + } + break; + } + } + + return articleList.length; + } + +} diff --git a/src/server/modules/contents/service/contents-random.service.ts b/src/server/modules/contents/service/contents-random.service.ts new file mode 100755 index 0000000..4c19f84 --- /dev/null +++ b/src/server/modules/contents/service/contents-random.service.ts @@ -0,0 +1,25 @@ +import { Service } from '@tsed/di'; + +import { UserService } from '../../user/service/user.service'; +import { UserRandomService as MongdbUserRandomService } from '../../../mongodb/user/service/user-random.service'; +import { ArticleRandomService as MongdbArticleRandomService } from '../../../mongodb/article/service/article-random.service'; + +@Service() +export class ContentsRandomService { + constructor( + private mongdbArticleRandomService: MongdbArticleRandomService, + private userService: UserService, + private mongdbUserRandomService: MongdbUserRandomService, + ) { + } + + async saveArticle(size: number): Promise { + return this.mongdbArticleRandomService.genRandom(size); + } + + async saveAuthor(size: number): Promise { + const userList = await this.userService.findWithRandom(size); + + return this.mongdbUserRandomService.genRandom(userList); + } +} diff --git a/src/server/modules/contents/service/contents-search.service.ts b/src/server/modules/contents/service/contents-search.service.ts new file mode 100755 index 0000000..6fc72c4 --- /dev/null +++ b/src/server/modules/contents/service/contents-search.service.ts @@ -0,0 +1,93 @@ +import { Service } from '@tsed/di'; +import { In } from 'typeorm'; + +import { UserService } from '../../user/service/user.service'; +import { UserRandomService as MongdbUserRandomService } from '../../../mongodb/user/service/user-random.service'; +import { UserSupportEventService } from '../../user-support/service/user-support-event.service'; +import { AdvertisementsService } from '../../advertisements/service/advertisements.service'; +import { Article as ArticleMongodb } from '../../../mongodb/article/model/article.model'; +import { ArticleService as MongdbArticleService } from '../../../mongodb/article/service/article.service'; +import { ContentsSize } from '../../../../shared/contents/model/contents-size.model'; +import { Contents } from '../model/contents.model'; +import { ContentsType } from '../../../../shared/contents/type/contents-type.type'; +import { ContentsUtil } from '../../../../shared/contents/util/contents.util'; +import { User } from '../../user/entity/user.entity'; +import { Advertisements } from '../../advertisements/entity/advertisements.entity'; +import { UserSupportEvent } from '../../user-support/entity/user-support-event.entity'; +import { ContentsService } from './contents.service'; +import { ArticleService } from '../../article/service/article.service'; + +@Service() +export class ContentsSearchService extends ContentsService { + constructor( + private mongdbArticleService: MongdbArticleService, + private userService: UserService, + private userSupportEventService: UserSupportEventService, + private advertisementsService: AdvertisementsService, + private articleService: ArticleService, + ) { + super(); + } + + protected async findAllArticleByTagName(tagName: string, contentsSizeList: ContentsSize[], page: number): Promise { + const articleSize = ContentsUtil.getSizeByContentsSize(ContentsType.Article, contentsSizeList); + let articleList: ArticleMongodb[] = []; + if (0 < articleSize) { + articleList = await this.mongdbArticleService.findAllByTagName(null, [tagName], articleSize, page); + } + return articleList; + } + + protected async findAllFeaturedAuthor( + tagNameList: string[], + contentsSizeList: ContentsSize[], + page: number, + ): Promise { + const featuredAuthorSize = ContentsUtil.getSizeByContentsSize(ContentsType.FeaturedAuthor, contentsSizeList); + const featuredAuthorList: User[] = []; + if (0 < featuredAuthorSize) { + const userTagList: { _id: User, count: number }[] = await this.mongdbArticleService.findAllUserByTagName( + null, + tagNameList, + featuredAuthorSize, + page + ); + if (userTagList && 0 < userTagList.length) { + for (const userTag of userTagList) { + featuredAuthorList.push(userTag._id); + } + } + } + return featuredAuthorList; + } + + protected async findAllEvent(contentsSizeList: ContentsSize[], page: number): Promise { + const eventSize = ContentsUtil.getSizeByContentsSize(ContentsType.Event, contentsSizeList); + let eventList: UserSupportEvent[] = []; + if (0 < eventSize) { + eventList = await this.userSupportEventService.findAllByPage(eventSize, page); + } + return eventList; + } + + protected async findAllAdvertisements(contentsSizeList: ContentsSize[], page: number): Promise { + const advertisementsSize = ContentsUtil.getSizeByContentsSize(ContentsType.Advertisements, contentsSizeList); + let advertisementsList: Advertisements[] = []; + if (0 < advertisementsSize) { + advertisementsList = await this.advertisementsService.findAllByPage(advertisementsSize, page); + } + return advertisementsList; + } + + async findAllByTagName(tagName: string, contentsSizeList: ContentsSize[], page: number): Promise { + const articleList = await this.findAllArticleByTagName(tagName, contentsSizeList, page); + await this.articleService.increaseViewCount(articleList); + + const featuredAuthorList = await this.findAllFeaturedAuthor([tagName], contentsSizeList, page); + const advertisementsList = await this.findAllAdvertisements(contentsSizeList, page); + const eventList = await this.findAllEvent(contentsSizeList, page); + + return super.getContentsList(articleList, featuredAuthorList, advertisementsList, eventList, contentsSizeList); + } + +} diff --git a/src/server/modules/contents/service/contents-user.service.ts b/src/server/modules/contents/service/contents-user.service.ts new file mode 100755 index 0000000..c336f53 --- /dev/null +++ b/src/server/modules/contents/service/contents-user.service.ts @@ -0,0 +1,130 @@ +import { Service } from '@tsed/di'; +import { In } from 'typeorm'; + +import { UserService } from '../../user/service/user.service'; +import { UserSupportEventService } from '../../user-support/service/user-support-event.service'; +import { AdvertisementsService } from '../../advertisements/service/advertisements.service'; +import { Article as ArticleMongodb } from '../../../mongodb/article/model/article.model'; +import { ArticleService as MongdbArticleService } from '../../../mongodb/article/service/article.service'; +import { ArticleType } from '../../../../shared/article/type/article-type.type'; +import { ContentsSize } from '../../../../shared/contents/model/contents-size.model'; +import { Contents } from '../model/contents.model'; +import { ContentsType } from '../../../../shared/contents/type/contents-type.type'; +import { ContentsUtil } from '../../../../shared/contents/util/contents.util'; +import { User } from '../../user/entity/user.entity'; +import { Advertisements } from '../../advertisements/entity/advertisements.entity'; +import { UserSupportEvent } from '../../user-support/entity/user-support-event.entity'; +import { ContentsService } from './contents.service'; +import { UserFavorTagService } from '../../user/service/user-favor-tag.service'; +import { ArticleService } from '../../article/service/article.service'; + +@Service() +export class ContentsUserService extends ContentsService { + constructor( + private mongdbArticleService: MongdbArticleService, + private userService: UserService, + private userFavorTagService: UserFavorTagService, + private userSupportEventService: UserSupportEventService, + private advertisementsService: AdvertisementsService, + private articleService: ArticleService, + ) { + super(); + } + + protected async findAllArticle( + user: User, + userFavorTagNameList: string[], + articleType: ArticleType, + contentsSizeList: ContentsSize[], + page: number + ): Promise { + const articleSize = ContentsUtil.getSizeByContentsSize(ContentsType.Article, contentsSizeList); + let articleList: ArticleMongodb[] = []; + + if (0 < articleSize) { + articleList = await this.mongdbArticleService.findAllByTagName(user.id, userFavorTagNameList, articleSize, page); + } + return articleList; + } + + protected async findAllFeaturedAuthor( + user: User, + userFavorTagNameList: string[], + contentsSizeList: ContentsSize[], + page: number, + ): Promise { + const featuredAuthorSize = ContentsUtil.getSizeByContentsSize(ContentsType.FeaturedAuthor, contentsSizeList); + const featuredAuthorList: User[] = []; + if (0 < featuredAuthorSize) { + const userTagList: { _id: User, count: number }[] = await this.mongdbArticleService.findAllUserByTagName( + user.id, + userFavorTagNameList, + featuredAuthorSize, + page + ); + if (userTagList && 0 < userTagList.length) { + for (const userTag of userTagList) { + featuredAuthorList.push(userTag._id); + } + } + } + return featuredAuthorList; + } + + protected async findAllEvent(user: User, contentsSizeList: ContentsSize[], page: number): Promise { + const eventSize = ContentsUtil.getSizeByContentsSize(ContentsType.Event, contentsSizeList); + let eventList: UserSupportEvent[] = []; + if (0 < eventSize) { + eventList = await this.userSupportEventService.findAllByPage(eventSize, page); + } + return eventList; + } + + protected async findAllAdvertisements(user: User, contentsSizeList: ContentsSize[], page: number): Promise { + const advertisementsSize = ContentsUtil.getSizeByContentsSize(ContentsType.Advertisements, contentsSizeList); + let advertisementsList: Advertisements[] = []; + if (0 < advertisementsSize) { + advertisementsList = await this.advertisementsService.findAllByPage(advertisementsSize, page); + } + return advertisementsList; + } + + async findAllByRecommendations( + uid: string, + articleType: ArticleType, + contentsSizeList: ContentsSize[], + page: number + ): Promise { + const user: User = await this.userService.find(uid); + const userFavorTagNameList = await this.userFavorTagService.findAllTagNameByUser(uid); + + const articleList = await this.findAllArticle(user, userFavorTagNameList, articleType, contentsSizeList, page); + await this.articleService.increaseViewCount(articleList); + + const featuredAuthorList = await this.findAllFeaturedAuthor(user, userFavorTagNameList, contentsSizeList, page); + const advertisementsList = await this.findAllAdvertisements(user, contentsSizeList, page); + const eventList = await this.findAllEvent(user, contentsSizeList, page); + + return super.getContentsList(articleList, featuredAuthorList, advertisementsList, eventList, contentsSizeList); + } + + async findAllByFollowing( + uid: string, + articleType: ArticleType, + contentsSizeList: ContentsSize[], + page: number + ): Promise { + const user: User = await this.userService.find(uid); + const userFavorTagNameList = await this.userFavorTagService.findAllTagNameByUser(uid); + + const articleList = await this.findAllArticle(user, userFavorTagNameList, articleType, contentsSizeList, page); + await this.articleService.increaseViewCount(articleList); + + const featuredAuthorList = await this.findAllFeaturedAuthor(user, userFavorTagNameList, contentsSizeList, page); + const advertisementsList = await this.findAllAdvertisements(user, contentsSizeList, page); + const eventList = await this.findAllEvent(user, contentsSizeList, page); + + return super.getContentsList(articleList, featuredAuthorList, advertisementsList, eventList, contentsSizeList); + } + +} diff --git a/src/server/modules/contents/service/contents.service.ts b/src/server/modules/contents/service/contents.service.ts new file mode 100755 index 0000000..7c21acf --- /dev/null +++ b/src/server/modules/contents/service/contents.service.ts @@ -0,0 +1,98 @@ +import { Article as ArticleMongodb } from '../../../mongodb/article/model/article.model'; +import { ContentsSize } from '../../../../shared/contents/model/contents-size.model'; +import { Contents } from '../model/contents.model'; +import { ContentsType } from '../../../../shared/contents/type/contents-type.type'; +import { User } from '../../user/entity/user.entity'; +import { Advertisements } from '../../advertisements/entity/advertisements.entity'; +import { UserSupportEvent } from '../../user-support/entity/user-support-event.entity'; +import { ContentsDisplay } from '../../../../shared/contents/type/contents-display.type'; + +class ContentsIndex { + constructor( + private list: ArticleMongodb[] | User[] | Advertisements[] | UserSupportEvent[], + private index: number = 0, + ) { + } + + next(): ArticleMongodb | User | Advertisements | UserSupportEvent | null { + if (!this.has()) { + return null; + } + return this.list[this.index++]; + } + + has(): boolean { + if (!this.list || 0 === this.list.length) { + return false; + } + if (this.list.length <= this.index) { + return false; + } + return true; + } +} + +export abstract class ContentsService { + constructor( + ) { + } + + protected async getContentsList( + articleList: ArticleMongodb[], + featuredAuthorList: User[], + advertisementsList: Advertisements[], + eventList: UserSupportEvent[], + contentsSizeList: ContentsSize[], + ): Promise { + + const contentsMap: Map = new Map([ + [ContentsType.Article, new ContentsIndex(articleList)], + [ContentsType.FeaturedAuthor, new ContentsIndex(featuredAuthorList)], + [ContentsType.Event, new ContentsIndex(eventList)], + [ContentsType.Advertisements, new ContentsIndex(advertisementsList)], + ]); + + const contentsList: Contents[] = []; + + for (const contentsSize of contentsSizeList) { + const isSeperate: boolean = + contentsSize.displayOption + && contentsSize.displayOption.type + && ContentsDisplay.Card === contentsSize.displayOption.type; + + if (isSeperate) { + for (let index = 0; index < contentsSize.size; index++) { + const contents = contentsMap.get(contentsSize.type).next(); + if (!contents) { + break; + } + contentsList.push({ + type: contentsSize.type, + display: contentsSize.displayOption, + data: contents, + }); + } + } else { + const _contentsList: any[] = []; + for (let index = 0; index < contentsSize.size; index++) { + const contents = contentsMap.get(contentsSize.type).next(); + if (!contents) { + break; + } + _contentsList.push(contents); + } + + if (0 === _contentsList.length) { + continue; + } + contentsList.push({ + type: contentsSize.type, + display: contentsSize.displayOption, + data: _contentsList, + }); + } + } + + return contentsList; + } +} diff --git a/src/server/modules/meta/controller/meta-comments-tag.controller.ts b/src/server/modules/meta/controller/meta-comments-tag.controller.ts new file mode 100755 index 0000000..492f412 --- /dev/null +++ b/src/server/modules/meta/controller/meta-comments-tag.controller.ts @@ -0,0 +1,43 @@ +import * as express from 'express'; +import { + Controller, Put, Post, Delete, BodyParams, Required, Authenticated, PathParams, Get, Req, Res, QueryParams, +} from '@tsed/common'; + +import { AbstractController } from '../../common/util/controller/abstract.controller'; +import { MetaCommentsTag } from '../entity/meta-comments-tag.entity'; +import { MetaCommentsTagService } from '../service/meta-comments-tag.service'; + +@Controller('/meta/comments_tag') +export class MetaCommentsTagController extends AbstractController { + + constructor( + private metaCommentsTagService: MetaCommentsTagService, + ) { + super(metaCommentsTagService); + } + + @Get('/') + async getAll( + @Req() req: express.Request, + @Res() res: express.Response, + ): Promise { + const model = await this.metaCommentsTagService.findAll(); + + if (model) { + return model; + } + + res.status(203).send('No Content'); + } + + @Put('/') + @Authenticated({ roles: ['R_USER'] }) + save( + @Req() req: express.Request, + @Res() res: express.Response, + @BodyParams() metaCommentsTag: MetaCommentsTag, + ): Promise { + return this.metaCommentsTagService.save(metaCommentsTag); + } + +} diff --git a/src/server/modules/meta/entity/meta-comments-tag.entity.ts b/src/server/modules/meta/entity/meta-comments-tag.entity.ts new file mode 100755 index 0000000..0aefa66 --- /dev/null +++ b/src/server/modules/meta/entity/meta-comments-tag.entity.ts @@ -0,0 +1,35 @@ +/** + * 파 일 명: user-analysis-favor-tag.entity.ts + * 작성일자: 2018-12-13 + * 작 성 자: 박병준 + * 설 명: User 분석용 favor tag Entity 스키마를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Property, } from '@tsed/common'; +import { Entity, Column, ManyToOne, OneToMany, } from 'typeorm'; + +import { Base } from '../../common/util/entity/base.entity'; +import { MetaCommentsTagTarget } from '../../../../shared/meta/type/meta-comments-tag-target.type'; +import { ArticleCommentsTag } from '../../article/entity/article-comments-tag.entity'; + +@Entity() +export class MetaCommentsTag extends Base { + @Column() + @Property() + code: string; + + @Column() + @Property() + type: MetaCommentsTagTarget; + + @Column() + @Property() + styleClass: string; + + @Property() + @OneToMany(type => ArticleCommentsTag, articleCommentsTag => articleCommentsTag.metaCommentsTag) + articleCommentsTagList: ArticleCommentsTag[]; +} diff --git a/src/server/modules/meta/service/meta-comments-tag.service.ts b/src/server/modules/meta/service/meta-comments-tag.service.ts new file mode 100755 index 0000000..0b2a179 --- /dev/null +++ b/src/server/modules/meta/service/meta-comments-tag.service.ts @@ -0,0 +1,20 @@ +import { Service } from '@tsed/di'; +import { TypeORMService } from '@tsed/typeorm'; +import { EntityManager, Repository } from 'typeorm'; + +import { AbstractEntityService } from '../../common/util/service/abstract-entity.service'; +import { MetaCommentsTag } from '../entity/meta-comments-tag.entity'; + +@Service() +export class MetaCommentsTagService extends AbstractEntityService { + constructor( + typeORMService: TypeORMService, + ) { + super(typeORMService); + } + + getRepository(entityManager: EntityManager): Repository { + return entityManager.getRepository(MetaCommentsTag); + } + +} diff --git a/src/server/modules/oauth/controller/oauth.controller.ts b/src/server/modules/oauth/controller/oauth.controller.ts new file mode 100755 index 0000000..54d4eb9 --- /dev/null +++ b/src/server/modules/oauth/controller/oauth.controller.ts @@ -0,0 +1,76 @@ +/** + * 파 일 명: oauth.controller.ts + * 작성일자: 2018-12-20 + * 작 성 자: 박병준 + * 설 명: OAuth 인증 entry point를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { + Controller, + Req, + Res, + Get, + Required, + PathParams, + ServerSettingsService, + BeforeRoutesInit, + QueryParams, +} from '@tsed/common'; +import * as Passport from 'passport'; +import * as express from 'express'; +import { PassportJwtService } from '../service/passport-jwt.service'; +import { UserService } from '../../user/service/user.service'; +import { OAuth } from '../entity/oauth.entity'; +import { OAuthResult } from '../../../../shared/oauth/type/oauth-result.type'; + +@Controller('/') +export class OAuthController implements BeforeRoutesInit { + protected passportOAuthCommonOptions: any; + + constructor( + protected serverSettingsService: ServerSettingsService, + protected passportJwtService: PassportJwtService, + protected userService: UserService, + ) { + } + + $beforeRoutesInit() { + this.passportOAuthCommonOptions = this.serverSettingsService.get(`passport.oauth.common`) || {} as any; + } + + @Get('/authentication/:oauthType') + async authenticate( + @Req() req: express.Request, + @Res() res: express.Response, + @Required() @PathParams('oauthType') oauthType: string, + @QueryParams('returnURL') returnURL: string, + ) { + const oauthResult = await new Promise<{ oauth: OAuth, oauthResult: OAuthResult, returnURL: string }>((resolve, reason) => { + const authenticateOptions: Passport.AuthenticateOptions = { + session: false, + state: new Buffer(JSON.stringify({ returnURL })).toString('base64'), + }; + Passport.authenticate( + oauthType, + authenticateOptions, (err, result: { oauth: OAuth, oauthResult: OAuthResult, returnURL: string }) => { + if (err) { + return reason(err); + } + + return resolve(result); + })(req, res); + }); + + const jwtToken = this.passportJwtService.jwtResult(oauthResult.oauth.user.id); + + let redirectURL = `${this.passportOAuthCommonOptions.callbackURL}`; + redirectURL += `?token=${jwtToken}`; + redirectURL += `&result=${oauthResult.oauthResult}`; + redirectURL += `&returnURL=${oauthResult.returnURL}`; + + res.redirect(redirectURL); + } +} diff --git a/src/server/modules/oauth/controller/oneall.controller.ts b/src/server/modules/oauth/controller/oneall.controller.ts new file mode 100755 index 0000000..abe28ae --- /dev/null +++ b/src/server/modules/oauth/controller/oneall.controller.ts @@ -0,0 +1,85 @@ +/** + * 파 일 명: oneall.controller.ts + * 작성일자: 2018-12-20 + * 작 성 자: 박병준 + * 설 명: OAuth 인증 entry point를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { + Controller, + Req, + Res, + Get, + Required, + PathParams, + ServerSettingsService, + BeforeRoutesInit, + Post, + BodyParams, + QueryParams, +} from '@tsed/common'; +import * as Passport from 'passport'; +import * as express from 'express'; +import { AuthenticateOptions } from '../../common/util/passport/oneall'; + +import { PassportJwtService } from '../service/passport-jwt.service'; +import { UserService } from '../../user/service/user.service'; +import { OAuth } from '../entity/oauth.entity'; +import { OAuthResult } from '../../../../shared/oauth/type/oauth-result.type'; +import { PassportOneallService } from '../service/passport-oneall.service'; + +@Controller('/oneall') +export class OneallController implements BeforeRoutesInit { + protected passportOAuthCommonOptions: any; + + constructor( + protected serverSettingsService: ServerSettingsService, + protected passportJwtService: PassportJwtService, + protected passportOneallService: PassportOneallService, + protected userService: UserService, + ) { + } + + $beforeRoutesInit() { + this.passportOAuthCommonOptions = this.serverSettingsService.get(`passport.oauth.common`) || {} as any; + } + + @Get('/authentication') + async authenticate( + @Req() req: express.Request, + @Res() res: express.Response, + @QueryParams('oa_action') oaAction: string, + @QueryParams('oa_connector') oaConnector: string, + @QueryParams('connection_token') connectionToken: string, + @QueryParams('returnURL') returnURL: string, + ) { + const oauthResult = await new Promise<{ oauth: OAuth, oauthResult: OAuthResult, returnURL: string }>((resolve, reason) => { + const authenticateOptions: AuthenticateOptions = { + session: false, + oaAction: oaAction, + oaConnector: oaConnector, + connectionToken: connectionToken, + state: new Buffer(JSON.stringify({ returnURL })).toString('base64'), + }; + Passport.authenticate('oneall', authenticateOptions, (err, result: { oauth: OAuth, oauthResult: OAuthResult, returnURL: string }) => { + if (err) { + return reason(err); + } + + return resolve(result); + })(req, res); + }); + + const jwtToken = this.passportJwtService.jwtResult(oauthResult.oauth.user.id); + + let redirectURL = `${this.passportOAuthCommonOptions.callbackURL}`; + redirectURL += `?token=${jwtToken}`; + redirectURL += `&result=${oauthResult.oauthResult}`; + redirectURL += `&returnURL=${oauthResult.returnURL}`; + + res.redirect(redirectURL); + } +} diff --git a/src/server/modules/oauth/entity/oauth.entity.ts b/src/server/modules/oauth/entity/oauth.entity.ts new file mode 100755 index 0000000..d625c03 --- /dev/null +++ b/src/server/modules/oauth/entity/oauth.entity.ts @@ -0,0 +1,79 @@ +/** + * 파 일 명: oneall-user.entity.ts + * 작성일자: 2018-12-20 + * 작 성 자: 박병준 + * 설 명: OAuth 관리 Entity 스키마를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Property, Required } from '@tsed/common'; +import { Entity, Column, Index, Unique, ManyToOne } from 'typeorm'; +import { OAuthProvider } from '../../../../shared/oauth/type/oauth-provider.type'; +import { OAuthUse } from '../../../../shared/oauth/type/oauth-use.type'; + +import { Base } from '../../common/util/entity/base.entity'; +import { User } from '../../user/entity/user.entity'; + +@Entity({ + name: 'oauth', +}) +@Unique([ + 'oauthId', + 'accessToken', +]) +export class OAuth extends Base { + @Column({ + type: 'varchar', + length: 100, + }) + @Index('IDX_OAuth_oauthId') + @Required() + @Property() + oauthId: string; + + @Column({ + type: 'varchar', + length: 100, + }) + @Index('IDX_OAuth_accessToken') + @Required() + @Property() + accessToken: string; + + @Column({ + type: 'varchar', + length: 100, + nullable: true, + }) + @Property() + refreshToken: string; + + @Column({ + type: 'varchar', + length: 20, + }) + @Property() + oauthProvider: OAuthProvider; + + @Column({ + type: 'varchar', + length: 4, + }) + @Property() + oauthUse: OAuthUse; + + @Column('simple-json') + @Property() + profile: any; + + @ManyToOne(type => User, user => user.oauthes, { + eager: true, + }) + @Property() + user: User; + + @Column() + userId?: string; +} diff --git a/src/server/modules/oauth/service/oauth.service.ts b/src/server/modules/oauth/service/oauth.service.ts new file mode 100755 index 0000000..92e36a7 --- /dev/null +++ b/src/server/modules/oauth/service/oauth.service.ts @@ -0,0 +1,33 @@ +/** + * 파 일 명: oneall.service.ts + * 작성일자: 2018-12-20 + * 작 성 자: 박병준 + * 설 명: OAuth 관리 로직을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Service } from '@tsed/di'; +import { TypeORMService } from '@tsed/typeorm'; +import { EntityManager, Repository } from 'typeorm'; + +import { AbstractEntityService } from '../../common/util/service/abstract-entity.service'; +import { OAuth } from '../entity/oauth.entity'; +import { OAuthProvider } from '../../../../shared/oauth/type/oauth-provider.type'; + +@Service() +export class OAuthService extends AbstractEntityService { + + constructor(typeORMService: TypeORMService) { + super(typeORMService); + } + + getRepository(entityManager: EntityManager): Repository { + return entityManager.getRepository(OAuth); + } + + async findByOAuthProviderAndId(oauthProvider: OAuthProvider, oauthId: string): Promise { + return this.repository.findOne({ oauthId: oauthId, oauthProvider: oauthProvider }); + } +} diff --git a/src/server/modules/oauth/service/passport-jwt.service.ts b/src/server/modules/oauth/service/passport-jwt.service.ts new file mode 100755 index 0000000..3f5671a --- /dev/null +++ b/src/server/modules/oauth/service/passport-jwt.service.ts @@ -0,0 +1,77 @@ +/** + * 파 일 명: passport-jwt.service.ts + * 작성일자: 2018-12-20 + * 작 성 자: 박병준 + * 설 명: Passport를 이용한 jwt 인증 관리 + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { BeforeRoutesInit, AfterRoutesInit, ServerSettingsService } from '@tsed/common'; +import { Service } from '@tsed/di'; +import * as passport from 'passport'; +import { Strategy, ExtractJwt, StrategyOptions } from 'passport-jwt'; +import * as jwt from 'jsonwebtoken'; + +import { PassportService } from './passport.service'; +import { UserService } from '../../user/service/user.service'; + +const StrategyName = 'jwt'; + +@Service() +export class PassportJwtService extends PassportService implements BeforeRoutesInit, AfterRoutesInit { + constructor( + serverSettingsService: ServerSettingsService, + private userService: UserService, + ) { + super(StrategyName, serverSettingsService); + } + + $beforeRoutesInit() { + super.$beforeRoutesInit(); + } + + $afterRoutesInit() { + super.$afterRoutesInit(); + } + + protected initStrategy() { + const strategyOptions: StrategyOptions = { + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + secretOrKey: this.passportStrategyOptions.publicKey, + issuer: this.passportStrategyOptions.signOptions.issuer, + algorithms: [ + this.passportStrategyOptions.signOptions.algorithm, + ], + }; + + passport.use(new Strategy(strategyOptions, (jwt_payload, done) => { + // this.accountService.findByUid(jwt_payload.sub) + // .then((user) => { + // return done(null, user); + // }) + // .catch((reason) => { + // return done(reason, null); + // }); + return done(null, jwt_payload); + })); + } + + jwtResult(uid: string): string { + const body = { + sub: uid, + roles: [ + 'R_USER' + ], + }; + + const token = jwt.sign( + body, + this.passportStrategyOptions.privateKey, + this.passportStrategyOptions.signOptions, + ); + + return token; + } +} diff --git a/src/server/modules/oauth/service/passport-kakao.service.ts b/src/server/modules/oauth/service/passport-kakao.service.ts new file mode 100755 index 0000000..c0b5399 --- /dev/null +++ b/src/server/modules/oauth/service/passport-kakao.service.ts @@ -0,0 +1,90 @@ +/** + * 파 일 명: passport-kakao.service.ts + * 작성일자: 2018-12-20 + * 작 성 자: 박병준 + * 설 명: Passport를 이용한 kakao 인증 관리 + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { + BeforeRoutesInit, + AfterRoutesInit, + ServerSettingsService +} from '@tsed/common'; +import { Service } from '@tsed/di'; +import * as express from 'express'; +import * as passport from 'passport'; +import { Strategy, Profile, StrategyOptionWithRequest } from 'passport-kakao'; +import { UserService } from '../../user/service/user.service'; +import { OAuthService } from './oauth.service'; +import { PassportOAuthService } from './passport-oauth.service'; +import { OAuthProvider } from '../../../../shared/oauth/type/oauth-provider.type'; + +const StrategyName = 'kakao'; + +@Service() +export class PassportKakaoService extends PassportOAuthService + implements BeforeRoutesInit, AfterRoutesInit { + constructor( + oauthService: OAuthService, + userService: UserService, + serverSettingsService: ServerSettingsService + ) { + super(StrategyName, serverSettingsService, oauthService, userService); + } + + $beforeRoutesInit() { + super.$beforeRoutesInit(); + } + + $afterRoutesInit() { + super.$afterRoutesInit(); + } + + protected initStrategy() { + const strategyOptions: StrategyOptionWithRequest = { + clientID: this.passportStrategyOptions.clientID, + clientSecret: this.passportStrategyOptions.clientSecret, + callbackURL: this.passportStrategyOptions.callbackURL, + passReqToCallback: true + }; + + passport.use( + new Strategy( + strategyOptions, + ( + req: express.Request, + accessToken: string, + refreshToken: string, + profile: Profile, + done: (error, user?, info?) => void + ) => { + this.findOrSave( + req, + OAuthProvider.KAKAO, + profile.id, + user => { + user.nickname = profile.displayName; + if (profile.emails) { + user.email = profile.emails[0].value; + } + }, + oauth => { + oauth.accessToken = accessToken; + oauth.refreshToken = refreshToken; + oauth.profile = profile._json; + } + ) + .then(result => { + return done(null, result); + }) + .catch(reason => { + return done(reason); + }); + } + ) + ); + } +} diff --git a/src/server/modules/oauth/service/passport-local.service.ts b/src/server/modules/oauth/service/passport-local.service.ts new file mode 100755 index 0000000..2c5ef43 --- /dev/null +++ b/src/server/modules/oauth/service/passport-local.service.ts @@ -0,0 +1,65 @@ +/** + * 파 일 명: passport-kakao.service.ts + * 작성일자: 2018-12-20 + * 작 성 자: 박병준 + * 설 명: Passport를 이용한 local 인증 관리 + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { BeforeRoutesInit, AfterRoutesInit, ServerSettingsService, ExpressApplication } from '@tsed/common'; +import { Service, Inject } from '@tsed/di'; +import * as passport from 'passport'; +import { IStrategyOptions } from 'passport-local'; +import { PassportService } from './passport.service'; + +const StrategyName = 'local'; + +@Service() +export class PassportLocalService extends PassportService implements BeforeRoutesInit, AfterRoutesInit { + constructor( + serverSettingsService: ServerSettingsService, + @Inject(ExpressApplication) private expressApplication: ExpressApplication, + ) { + super(StrategyName, serverSettingsService); + passport.serializeUser(this.serialize.bind(this)); + passport.deserializeUser(this.deserialize.bind(this)); + } + + $beforeRoutesInit() { + super.$beforeRoutesInit(); + + const { userProperty } = this.passportStrategyOptions; + + this.expressApplication.use(passport.initialize({ userProperty })); + } + + $afterRoutesInit() { + super.$afterRoutesInit(); + } + + protected initStrategy() { + const StrategyOptions: IStrategyOptions = { + usernameField: 'userId', + passwordField: 'userPw', + }; + + // Passport.use('login', new Strategy(StrategyOptions, (userId, userPw, done) => { + // this.accountService.login(userId, userPw) + // .then((result: { user: User, err: string }) => { + // done(null, result); + // }) + // .catch((err) => done(err)); + // })); + } + + serialize(user, done) { + done(null, user); + } + + deserialize(user, done) { + done(null, user); + } + +} diff --git a/src/server/modules/oauth/service/passport-naver.service.ts b/src/server/modules/oauth/service/passport-naver.service.ts new file mode 100755 index 0000000..2431d5f --- /dev/null +++ b/src/server/modules/oauth/service/passport-naver.service.ts @@ -0,0 +1,72 @@ +/** + * 파 일 명: passport-naver.service.ts + * 작성일자: 2018-12-20 + * 작 성 자: 박병준 + * 설 명: Passport를 이용한 naver 인증 관리 + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { BeforeRoutesInit, AfterRoutesInit, ServerSettingsService } from '@tsed/common'; +import { Service } from '@tsed/di'; +import * as express from 'express'; +import * as passport from 'passport'; +import { Strategy, Profile, StrategyOptionWithRequest } from 'passport-naver'; +import { UserService } from '../../user/service/user.service'; +import { OAuthService } from './oauth.service'; +import { OAuthProvider } from '../../../../shared/oauth/type/oauth-provider.type'; +import { PassportOAuthService } from './passport-oauth.service'; + +const StrategyName = 'naver'; + +@Service() +export class PassportNaverService extends PassportOAuthService implements BeforeRoutesInit, AfterRoutesInit { + constructor( + oauthService: OAuthService, + userService: UserService, + serverSettingsService: ServerSettingsService, + ) { + super(StrategyName, serverSettingsService, oauthService, userService); + } + + $beforeRoutesInit() { + super.$beforeRoutesInit(); + } + + $afterRoutesInit() { + super.$afterRoutesInit(); + } + + protected initStrategy() { + const strategyOptions: StrategyOptionWithRequest = { + clientID: this.passportStrategyOptions.clientID, + clientSecret: this.passportStrategyOptions.clientSecret, + callbackURL: this.passportStrategyOptions.callbackURL, + passReqToCallback: true, + }; + + passport.use(new Strategy(strategyOptions, + (req: express.Request, accessToken: string, refreshToken: string, profile: Profile, done: (error, user?, info?) => void) => { + this.findOrSave(req, OAuthProvider.NAVER, profile.id, + (user) => { + user.nickname = profile.displayName; + if (profile.emails) { + user.email = profile.emails[0].value; + } + }, + (oauth) => { + oauth.accessToken = accessToken; + oauth.refreshToken = refreshToken; + oauth.profile = profile._json; + }) + .then((result) => { + return done(null, result); + }) + .catch((reason) => { + return done(reason); + }); + }) + ); + } +} diff --git a/src/server/modules/oauth/service/passport-oauth.service.ts b/src/server/modules/oauth/service/passport-oauth.service.ts new file mode 100755 index 0000000..fcfe0e5 --- /dev/null +++ b/src/server/modules/oauth/service/passport-oauth.service.ts @@ -0,0 +1,115 @@ +/** + * 파 일 명: passport-oauth.service.ts + * 작성일자: 2018-12-20 + * 작 성 자: 박병준 + * 설 명: OAuth 인증을 위한 Passport 공통 로직. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import * as express from 'express'; +import { ServerSettingsService, BeforeRoutesInit } from '@tsed/common'; +import { $log } from 'ts-log-debug'; +import { generate } from 'short-uuid'; +import { UserService } from '../../user/service/user.service'; +import { OAuthService } from './oauth.service'; +import { PassportService } from './passport.service'; +import { OAuthProvider } from '../../../../shared/oauth/type/oauth-provider.type'; +import { OAuthResult } from '../../../../shared/oauth/type/oauth-result.type'; +import { OAuthUse } from '../../../../shared/oauth/type/oauth-use.type'; +import { User } from '../../user/entity/user.entity'; +import { OAuth } from '../entity/oauth.entity'; + +export type NewUserFunc = (user: User) => void; +export type NewOAuthFunc = (oauth: OAuth) => void; + +export abstract class PassportOAuthService extends PassportService implements BeforeRoutesInit { + protected passportStrategyOptions: any; + + constructor( + strategyName: string, + serverSettingsService: ServerSettingsService, + protected oauthService: OAuthService, + protected userService: UserService, + ) { + super(strategyName, serverSettingsService); + } + + $beforeRoutesInit() { + this.passportStrategyOptions = this.serverSettingsService.get(`passport.oauth.${this.strategyName}`) || {} as any; + } + + protected findOrSave( + req: express.Request, + oauthProvider: OAuthProvider, + oauthId: string, + newUserFunc?: NewUserFunc, + newOAuthFunc?: NewOAuthFunc + ): Promise<{ oauth: OAuth, oauthResult: OAuthResult, returnURL: string }> { + return new Promise<{ oauth: OAuth, oauthResult: OAuthResult, returnURL: string }>((resolve, reason) => { + let returnURL: string = null; + + try { + const { state } = req.query; + if (state) { + returnURL = JSON.parse(new Buffer(state, 'base64').toString()).returnURL; + } else { + returnURL = req.query.returnURL; + } + } catch (error) { + $log.error(error); + } + + if (!returnURL) { + returnURL = ''; + } + + this.oauthService.findByOAuthProviderAndId(oauthProvider, oauthId) + .then(async (oauth) => { + if (!oauth) { + const user = new User(); + if (newUserFunc) { + newUserFunc(user); + } + if (!user.nickname) { + const nicknameUser = await this.userService.findByNickname(user.nickname); + if (nicknameUser) { + user.nickname = null; + } + } + + this.userService + .save(user) + .then((_user) => { + oauth = new OAuth(); + if (newOAuthFunc) { + newOAuthFunc(oauth); + } + oauth.oauthId = oauthId; + oauth.oauthProvider = oauthProvider; + oauth.oauthUse = OAuthUse.REGI; + oauth.user = _user; + + this.oauthService.save(oauth) + .then((_oauth) => { + return resolve({ oauth: _oauth, oauthResult: OAuthResult.REGIST, returnURL }); + }) + .catch((_reason) => { + return reason(_reason); + }); + }) + .catch((_reason) => { + return reason(_reason); + }); + + } else { + return resolve({ oauth: oauth, oauthResult: OAuthResult.LOGIN, returnURL }); + } + }) + .catch((_reason) => { + return reason(_reason); + }); + }); + } +} diff --git a/src/server/modules/oauth/service/passport-oneall.service.ts b/src/server/modules/oauth/service/passport-oneall.service.ts new file mode 100755 index 0000000..7b9865d --- /dev/null +++ b/src/server/modules/oauth/service/passport-oneall.service.ts @@ -0,0 +1,105 @@ +/** + * 파 일 명: passport-oneall.service.ts + * 작성일자: 2018-12-20 + * 작 성 자: 박병준 + * 설 명: Passport를 이용한 oneall 인증 관리 + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { + BeforeRoutesInit, + AfterRoutesInit, + ServerSettingsService +} from '@tsed/common'; +import { Service } from '@tsed/di'; +import * as express from 'express'; +import * as passport from 'passport'; +import { + Strategy, + Profile, + StrategyOptionWithRequest +} from '../../common/util/passport/oneall'; +import { UserService } from '../../user/service/user.service'; +import { OAuthService } from './oauth.service'; +import { OAuthProvider } from '../../../../shared/oauth/type/oauth-provider.type'; +import { PassportOAuthService } from './passport-oauth.service'; + +const Oneall = require('oneall'); + +const StrategyName = 'oneall'; + +@Service() +export class PassportOneallService extends PassportOAuthService + implements BeforeRoutesInit, AfterRoutesInit { + oneall: any; + endpoint: string; + + constructor( + oauthService: OAuthService, + userService: UserService, + serverSettingsService: ServerSettingsService + ) { + super(StrategyName, serverSettingsService, oauthService, userService); + } + + $beforeRoutesInit() { + super.$beforeRoutesInit(); + } + + $afterRoutesInit() { + super.$afterRoutesInit(); + } + + protected initStrategy() { + this.endpoint = `https://${ + this.passportStrategyOptions.subdomain + }.api.oneall.com`; + + this.oneall = new Oneall({ + endpoint: this.endpoint, + publickey: this.passportStrategyOptions.publicKey, + privatekey: this.passportStrategyOptions.privateKey + }); + + const strategyOptions: StrategyOptionWithRequest = { + oneall: this.oneall, + passReqToCallback: true + }; + + passport.use( + new Strategy( + strategyOptions, + ( + req: express.Request, + accessToken: string, + profile: Profile, + done: (error, user?, info?) => void + ) => { + this.findOrSave( + req, + OAuthProvider.ONEALL, + profile.id, + user => { + user.nickname = profile.displayName; + if (profile.emails) { + user.email = profile.emails[0].value; + } + }, + oauth => { + oauth.accessToken = accessToken; + oauth.profile = profile; + } + ) + .then(result => { + return done(null, result); + }) + .catch(reason => { + return done(reason); + }); + } + ) + ); + } +} diff --git a/src/server/modules/oauth/service/passport.service.ts b/src/server/modules/oauth/service/passport.service.ts new file mode 100755 index 0000000..5e81944 --- /dev/null +++ b/src/server/modules/oauth/service/passport.service.ts @@ -0,0 +1,32 @@ +/** + * 파 일 명: passport.service.ts + * 작성일자: 2018-12-20 + * 작 성 자: 박병준 + * 설 명: 인증을 위한 Passport 공통 로직. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { ServerSettingsService, BeforeRoutesInit, AfterRoutesInit } from '@tsed/common'; + +export abstract class PassportService implements BeforeRoutesInit, AfterRoutesInit { + protected passportStrategyOptions: any; + + constructor( + protected strategyName: string, + protected serverSettingsService: ServerSettingsService, + ) { + } + + $beforeRoutesInit() { + this.passportStrategyOptions = this.serverSettingsService.get(`passport.${this.strategyName}`) || {} as any; + } + + $afterRoutesInit() { + this.initStrategy(); + } + + protected abstract initStrategy(); + +} diff --git a/src/server/modules/tag/entity/tag.entity.ts b/src/server/modules/tag/entity/tag.entity.ts new file mode 100755 index 0000000..96eccfb --- /dev/null +++ b/src/server/modules/tag/entity/tag.entity.ts @@ -0,0 +1,31 @@ +/** + * 파 일 명: tag.entity.ts + * 작성일자: 2018-12-13 + * 작 성 자: 박병준 + * 설 명: Tag 관리 Entity 스키마를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Property, Required } from '@tsed/common'; +import { Column, Index } from 'typeorm'; + +import { Base } from '../../common/util/entity/base.entity'; + +export abstract class Tag extends Base { + @Column({ + type: 'varchar', + length: 50, + unique: true, + }) + @Required() + @Property() + tagName: string; + + @Column({ + default: 0, + }) + @Property() + tagCount: number; +} diff --git a/src/server/modules/thumbnails/service/thumbnails.service.ts b/src/server/modules/thumbnails/service/thumbnails.service.ts new file mode 100755 index 0000000..1f0faa7 --- /dev/null +++ b/src/server/modules/thumbnails/service/thumbnails.service.ts @@ -0,0 +1,107 @@ +/** + * 파 일 명: thumbnail.service.ts + * 작성일자: 2019-01-01 + * 작 성 자: 박병준 + * 설 명: thumbnail 관리 로직을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { ServerSettingsService } from '@tsed/common'; +import { Service } from '@tsed/di'; +import * as jimp from 'jimp'; +import * as path from 'path'; + +import { Attachments } from '../../attachments/entity/attachments.entity'; +import { AttachmentsService } from '../../attachments/service/attachments.service'; + +export enum ThumbnailsType { + COVER = 'COVER', + SCALE = 'SCALE', +} + +export interface ThumbnailsOption { + type: ThumbnailsType; + option: ThumbnailsCoverOption | ThumbnailsScaleOption; +} + +export interface ThumbnailsCoverOption { + key: string; + width: number; + height: number; + option?: any; +} + +export interface ThumbnailsScaleOption { + key: string; + width: number; + height: number; + option?: any; +} + +@Service() +export class ThumbnailsService { + private attachmentsOptions: any; + + constructor( + protected serverSettingsService: ServerSettingsService, + protected attachmentsService: AttachmentsService, + ) { + } + + $beforeRoutesInit() { + this.attachmentsOptions = this.serverSettingsService.get(`attachments`) || {} as any; + } + + async generate(attachments: Attachments, optionList: ThumbnailsOption[]): Promise { + return new Promise(async (resolve, reject) => { + const filePath = await this.attachmentsService.getPath(attachments); + jimp.read(filePath) + .then(async (image) => { + const _image = image.clone(); + for (const option of optionList) { + switch (option.type) { + case ThumbnailsType.COVER: + { + const _option: ThumbnailsCoverOption = option.option; + const _pathOption: string[] = []; + _pathOption.push(_option.key); + _pathOption.push(_option.width.toString()); + _pathOption.push(_option.height.toString()); + const _targetPath = await this.attachmentsService.getPath(attachments, _pathOption); + + _image + // tslint:disable-next-line:no-bitwise + .cover(_option.width, _option.height, jimp.HORIZONTAL_ALIGN_CENTER | jimp.VERTICAL_ALIGN_TOP) + .write(path.join(_targetPath)); + } + + break; + case ThumbnailsType.SCALE: + { + const _option: ThumbnailsScaleOption = option.option; + const _pathOption: string[] = []; + _pathOption.push(_option.key); + _pathOption.push(_option.width.toString()); + _pathOption.push(_option.height.toString()); + const _targetPath = await this.attachmentsService.getPath(attachments, _pathOption); + + _image + .scaleToFit(_option.width, _option.height, _option.option) + .write(path.join(_targetPath)); + } + break; + default: + break; + } + } + return resolve(); + }) + .catch((error) => { + return reject(error); + }); + }); + } + +} diff --git a/src/server/modules/user-analysis/controller/user-analysis-favor-tag.controller.ts b/src/server/modules/user-analysis/controller/user-analysis-favor-tag.controller.ts new file mode 100755 index 0000000..10b7461 --- /dev/null +++ b/src/server/modules/user-analysis/controller/user-analysis-favor-tag.controller.ts @@ -0,0 +1,79 @@ +/** + * 파 일 명: tag.controller.ts + * 작성일자: 2018-12-21 + * 작 성 자: 박병준 + * 설 명: Tag entry point를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { + Controller, + Req, + Res, + Get, + Required, + PathParams, + BeforeRoutesInit, + Authenticated, + Post, + BodyParams, + Put, + Delete +} from '@tsed/common'; +import * as express from 'express'; +import { UserAnalysisFavorTag } from '../entity/user-analysis-favor-tag.entity'; +import { AbstractController } from '../../common/util/controller/abstract.controller'; +import { UserAnalysisFavorTagService } from '../service/user-analysis-favor-tag.service'; + +@Controller('/user/analysis/favor_tag') +export class UserAnalysisFavorTagController extends AbstractController implements BeforeRoutesInit { + constructor(private userAnalysisFavorTagService: UserAnalysisFavorTagService) { + super(userAnalysisFavorTagService); + } + + $beforeRoutesInit() { } + + @Get('/:id') + @Authenticated({ roles: ['R_USER'] }) + async get( + @Req() req: express.Request, + @Res() res: express.Response, + @Required() @PathParams('id') id: string, + ): Promise { + const model = await this.service.find(id); + + if (model) { + return model; + } + + res.status(203).send('No Content'); + } + + @Get('/') + @Authenticated({ roles: ['R_USER'] }) + async getAll(): Promise { + return this.service.findAll(); + } + + @Put('/') + // @Authenticated({ roles: ['R_USER'] }) + save( + @BodyParams() model: UserAnalysisFavorTag + ) { + return this.service.save(model); + } + + @Post('/') + @Authenticated({ roles: ['R_USER'] }) + async update(@BodyParams() model: UserAnalysisFavorTag): Promise { + return this.service.save(model); + } + + @Delete('/') + @Authenticated({ roles: ['R_USER'] }) + async remove(@BodyParams('id') @Required() id: string): Promise { + return this.service.remove(id); + } +} diff --git a/src/server/modules/user-analysis/controller/user-analysis.controller.ts b/src/server/modules/user-analysis/controller/user-analysis.controller.ts new file mode 100755 index 0000000..8e65637 --- /dev/null +++ b/src/server/modules/user-analysis/controller/user-analysis.controller.ts @@ -0,0 +1,28 @@ +/** + * 파 일 명: tag.controller.ts + * 작성일자: 2018-12-21 + * 작 성 자: 박병준 + * 설 명: Tag entry point를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { + Controller, + BeforeRoutesInit, +} from '@tsed/common'; +import * as express from 'express'; +import { UserAnalysisService } from '../service/user-analysis.service'; +import { UserAnalysisFavorTag } from '../entity/user-analysis-favor-tag.entity'; +import { AbstractController } from '../../common/util/controller/abstract.controller'; + +@Controller('/user/analysis') +export class UserAnalysisController extends AbstractController implements BeforeRoutesInit { + constructor(private userAnalysisService: UserAnalysisService) { + super(userAnalysisService); + } + + $beforeRoutesInit() { } + +} diff --git a/src/server/modules/user-analysis/entity/user-analysis-favor-tag.entity.ts b/src/server/modules/user-analysis/entity/user-analysis-favor-tag.entity.ts new file mode 100755 index 0000000..ed250ba --- /dev/null +++ b/src/server/modules/user-analysis/entity/user-analysis-favor-tag.entity.ts @@ -0,0 +1,38 @@ +/** + * 파 일 명: user-analysis-favor-tag.entity.ts + * 작성일자: 2018-12-13 + * 작 성 자: 박병준 + * 설 명: User 분석용 favor tag Entity 스키마를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Property, } from '@tsed/common'; +import { Entity, Column, ManyToMany, JoinTable, ManyToOne, OneToMany, } from 'typeorm'; + +import { Base } from '../../common/util/entity/base.entity'; +import { ArticleTag } from '../../article/entity/article-tag.entity'; +import { UserFavorTag } from '../../user/entity/user-favor-tag.entity'; + +@Entity() +export class UserAnalysisFavorTag extends Base { + @ManyToMany(type => ArticleTag, { + eager: true, + }) + @JoinTable() + articleTagList: ArticleTag[]; + + @OneToMany(type => UserFavorTag, userFavorTag => userFavorTag.userAnalysisFavorTag) + userFavorTag: UserFavorTag[]; + + @Column() + @Property() + imageName: string; + + @Column({ + default: true, + }) + @Property() + show: boolean; +} diff --git a/src/server/modules/user-analysis/service/user-analysis-favor-tag.service.ts b/src/server/modules/user-analysis/service/user-analysis-favor-tag.service.ts new file mode 100755 index 0000000..3f75a4d --- /dev/null +++ b/src/server/modules/user-analysis/service/user-analysis-favor-tag.service.ts @@ -0,0 +1,23 @@ +import { Service } from '@tsed/di'; +import { TypeORMService } from '@tsed/typeorm'; +import { EntityManager, Repository } from 'typeorm'; + +import { AbstractEntityService } from '../../common/util/service/abstract-entity.service'; +import { UserAnalysisFavorTag } from '../entity/user-analysis-favor-tag.entity'; + +@Service() +export class UserAnalysisFavorTagService extends AbstractEntityService { + constructor(typeORMService: TypeORMService) { + super(typeORMService); + } + + getRepository(entityManager: EntityManager): Repository { + return entityManager.getRepository(UserAnalysisFavorTag); + } + + async findAll(): Promise { + return this.repository.find({ + show: true, + }); + } +} diff --git a/src/server/modules/user-analysis/service/user-analysis.service.ts b/src/server/modules/user-analysis/service/user-analysis.service.ts new file mode 100755 index 0000000..d46b546 --- /dev/null +++ b/src/server/modules/user-analysis/service/user-analysis.service.ts @@ -0,0 +1,17 @@ +import { Service } from '@tsed/di'; +import { TypeORMService } from '@tsed/typeorm'; +import { Like, EntityManager, Repository } from 'typeorm'; + +import { AbstractEntityService } from '../../common/util/service/abstract-entity.service'; +import { UserAnalysisFavorTag } from '../entity/user-analysis-favor-tag.entity'; + +@Service() +export class UserAnalysisService extends AbstractEntityService { + constructor(typeORMService: TypeORMService) { + super(typeORMService); + } + + getRepository(entityManager: EntityManager): Repository { + return entityManager.getRepository(UserAnalysisFavorTag); + } +} diff --git a/src/server/modules/user-support/entity/user-support-event.entity.ts b/src/server/modules/user-support/entity/user-support-event.entity.ts new file mode 100755 index 0000000..70d604e --- /dev/null +++ b/src/server/modules/user-support/entity/user-support-event.entity.ts @@ -0,0 +1,49 @@ +/** + * 파 일 명: user-support-event.entity.ts + * 작성일자: 2018-12-13 + * 작 성 자: 박병준 + * 설 명: User 지원용 event Entity 스키마를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Property, Required } from '@tsed/common'; +import { Entity, Column, Index, OneToOne, JoinColumn } from 'typeorm'; + +import { Base } from '../../common/util/entity/base.entity'; +import { Attachments } from '../../attachments/entity/attachments.entity'; + +@Entity() +export class UserSupportEvent extends Base { + @Column({ + type: 'varchar', + length: 50 + }) + @Index('IDX_UserSupportEvent_title') + @Required() + @Property() + title: string; + + @Column() + @Required() + @Property() + contents: string; + + @Column() + @Property() + startDate: Date; + + @Column() + @Property() + endDate: Date; + + @OneToOne(type => Attachments, { + nullable: true, + eager: true + }) + @JoinColumn() + @Property() + imageAttachements: Attachments; + +} diff --git a/src/server/modules/user-support/entity/user-support-tutorial.entity.ts b/src/server/modules/user-support/entity/user-support-tutorial.entity.ts new file mode 100755 index 0000000..d70e464 --- /dev/null +++ b/src/server/modules/user-support/entity/user-support-tutorial.entity.ts @@ -0,0 +1,41 @@ +/** + * 파 일 명: user-support-tutorial.entity.ts + * 작성일자: 2018-12-13 + * 작 성 자: 박병준 + * 설 명: User 지원용 Tutorial Entity 스키마를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Property, Required } from '@tsed/common'; +import { Entity, Column, Index } from 'typeorm'; + +import { Base } from '../../common/util/entity/base.entity'; + +@Entity() +export class UserSupportTutorial extends Base { + @Column({ + type: 'varchar', + length: 50 + }) + @Index('IDX_UserSupportTutorial_title') + @Required() + @Property() + title: string; + + @Column() + @Required() + @Property() + contents: string; + + @Column() + @Required() + @Property() + attachImgUrl: string; + + @Column() + @Required() + @Property() + page: number; +} diff --git a/src/server/modules/user-support/service/user-support-event.service.ts b/src/server/modules/user-support/service/user-support-event.service.ts new file mode 100755 index 0000000..94110d7 --- /dev/null +++ b/src/server/modules/user-support/service/user-support-event.service.ts @@ -0,0 +1,34 @@ +/** + * 파 일 명: user-support-event.service.ts + * 작성일자: 2019-01-16 + * 작 성 자: 박병준 + * 설 명: User support event 관리 로직을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Service } from '@tsed/di'; +import { TypeORMService } from '@tsed/typeorm'; +import { EntityManager, Repository } from 'typeorm'; + +import { AbstractEntityService } from '../../common/util/service/abstract-entity.service'; +import { UserSupportEvent } from '../entity/user-support-event.entity'; + +@Service() +export class UserSupportEventService extends AbstractEntityService { + constructor(typeORMService: TypeORMService) { + super(typeORMService); + } + + getRepository(entityManager: EntityManager): Repository { + return entityManager.getRepository(UserSupportEvent); + } + + async findAllByPage(size: number, page: number): Promise { + return this.repository.find({ + skip: (page - 1) * size, + take: size, + }); + } +} diff --git a/src/server/modules/user/controller/user-favor-tag.controller.ts b/src/server/modules/user/controller/user-favor-tag.controller.ts new file mode 100755 index 0000000..0b1fb5c --- /dev/null +++ b/src/server/modules/user/controller/user-favor-tag.controller.ts @@ -0,0 +1,76 @@ +/** + * 파 일 명: user-favor-tag.controller.ts + * 작성일자: 2019-01-19 + * 작 성 자: 박병준 + * 설 명: Favor Tag point를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { + Controller, + Req, + Res, + Get, + Required, + PathParams, + BeforeRoutesInit, + Authenticated, + Post, + BodyParams, + Delete, + Put +} from '@tsed/common'; +import * as express from 'express'; +import { AbstractController } from '../../common/util/controller/abstract.controller'; +import { User } from '../../user/entity/user.entity'; +import { UserFavorTag } from '../entity/user-favor-tag.entity'; +import { UserFavorTagService } from '../service/user-favor-tag.service'; + +@Controller('/user/favor_tag') +export class UserFavorTagController extends AbstractController + implements BeforeRoutesInit { + constructor(private userFavorTagService: UserFavorTagService) { + super(userFavorTagService); + } + + $beforeRoutesInit() { } + + @Get('/') + @Authenticated({ roles: ['R_USER'] }) + findAll( + @Req() req: express.Request, + @Res() res: express.Response, + ) { + const sessUser: User = req['user']; + return this.userFavorTagService.findAllByUser(sessUser.id); + } + + @Get('/:uid') + @Authenticated({ roles: ['R_USER'] }) + async findAllByUid( + @Req() req: express.Request, + @Res() res: express.Response, + @PathParams('uid') @Required() uid: string + ): Promise { + const userFavorList = await this.userFavorTagService.findAllByUser(uid); + if (userFavorList) { + return userFavorList; + } + + return null; + } + + @Post('/') + @Authenticated({ roles: ['R_USER'] }) + async updateAll( + @Req() req: express.Request, + @Res() res: express.Response, + @BodyParams('added') addedUserFavorTagList: UserFavorTag[], + @BodyParams('deleted') deletedUserFavorTagList: UserFavorTag[], + ): Promise { + const sessUser: User = req['user']; + return this.userFavorTagService.saveAllList(sessUser, addedUserFavorTagList, deletedUserFavorTagList); + } +} diff --git a/src/server/modules/user/controller/user-followers.controller.ts b/src/server/modules/user/controller/user-followers.controller.ts new file mode 100755 index 0000000..3ebf6f0 --- /dev/null +++ b/src/server/modules/user/controller/user-followers.controller.ts @@ -0,0 +1,89 @@ +/** + * 파 일 명: follow.controller.ts + * 작성일자: 2019-01-15 + * 작 성 자: 이숙영 + * 설 명: Follow entry point를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { + Controller, + Req, + Res, + Get, + Required, + PathParams, + ServerSettingsService, + BeforeRoutesInit, + Authenticated, + Post, + BodyParams, + Delete, + Put +} from '@tsed/common'; +import * as express from 'express'; +import { UserFollowers } from '../entity/user-followers.entity'; +import { UserFollowersService } from '../service/user-followers.service'; +import { AbstractController } from '../../common/util/controller/abstract.controller'; +import { User } from '../../user/entity/user.entity'; + +@Controller('/user/followers') +export class FollowController extends AbstractController + implements BeforeRoutesInit { + constructor(private userFollowersService: UserFollowersService) { + super(userFollowersService); + } + + $beforeRoutesInit() { } + + @Put('/') + @Authenticated({ roles: ['R_USER'] }) + save( + @Req() req: express.Request, + @Res() res: express.Response, + @BodyParams() userFollowers: UserFollowers + ) { + const sessUser: User = req['user']; + if (sessUser.id !== userFollowers.user.id || userFollowers.user.id === userFollowers.target.id) { + res.status(401).send('Unauthorized'); + return; + } + return this.userFollowersService.save(userFollowers); + } + + @Get('/followers/:id') + async getAllfollowers( + @Req() req: express.Request, + @Res() res: express.Response, + @Required() @PathParams('id') id: string + ): Promise { + const followersList = await this.userFollowersService.findAllFollowers(id); + if (followersList) { + return followersList; + } + + res.status(203).send('No Content'); + } + + @Get('/following/:id') + async getAllFollowing( + @Req() req: express.Request, + @Res() res: express.Response, + @Required() @PathParams('id') id: string + ): Promise { + const followingList = await this.userFollowersService.findAllFollowing(id); + if (followingList) { + return followingList; + } + res.status(203).send('No Content'); + } + + + @Delete('/:id') + @Authenticated({ roles: ['R_USER'] }) + async delete(@PathParams('id') @Required() id: string): Promise { + return this.userFollowersService.remove(id); + } +} diff --git a/src/server/modules/user/controller/user.controller.ts b/src/server/modules/user/controller/user.controller.ts new file mode 100755 index 0000000..9444d85 --- /dev/null +++ b/src/server/modules/user/controller/user.controller.ts @@ -0,0 +1,128 @@ +/** + * 파 일 명: tag.controller.ts + * 작성일자: 2018-12-21 + * 작 성 자: 박병준 + * 설 명: Tag entry point를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { + Controller, + Req, + Res, + Get, + Required, + PathParams, + ServerSettingsService, + BeforeRoutesInit, + Authenticated, + Post, + BodyParams, + Delete, + QueryParams +} from '@tsed/common'; +import * as express from 'express'; +import { UserService } from '../service/user.service'; +import { User } from '../entity/user.entity'; +import { AbstractController } from '../../common/util/controller/abstract.controller'; +import { UserFollowersService } from '../service/user-followers.service'; + +@Controller('/user') +export class UserController extends AbstractController + implements BeforeRoutesInit { + constructor(private userService: UserService, + private userFollowersService: UserFollowersService) { + super(userService); + } + + $beforeRoutesInit() { } + + @Get('/:id') + async get( + @Req() req: express.Request, + @Res() res: express.Response, + @Required() @PathParams('id') id: string + ): Promise { + const model = await this.service.find(id); + + if (model) { + // FIXME:: + const followingList = await this.userFollowersService.findAllFollowing(id); + model.followingList = followingList; + + return model; + } + + res.status(203).send('No Content'); + } + + @Get('/nickname/:nickname') + async getByNickname( + @Req() req: express.Request, + @Res() res: express.Response, + @Required() @PathParams('nickname') nickname: string + ): Promise { + const user = await this.userService.findByNickname(nickname); + if (user) { + return user; + } + + return null; + } + + @Get('/search/w/:nickname') + async getAllByWriter( + @Req() req: express.Request, + @Res() res: express.Response, + @Required() @PathParams('nickname') nickname: string + ): Promise { + const userList = await this.userService.findAllByWriter(nickname); + if (userList) { + return userList; + } + + return null; + } + + @Get('/recommendations/:uid/:page') + @Authenticated({ roles: ['R_USER'] }) + async getAllRecommended( + @Required() @PathParams('uid') uid: string, + @Required() @PathParams('page') page: number, + ): Promise { + const users = await this.userService.findAllRecommended(uid, page); + if (users) { + return users; + } + + return null; + } + + @Get('/search/:keyword') + async getAllByKeyword( + @Required() @PathParams('keyword') keyword: string, + ): Promise { + const users = await this.userService.findAllByKeyword(keyword); + if (users) { + return users; + } + return null; + } + + @Post('/') + @Authenticated({ roles: ['R_USER'] }) + async update( + @Req() req: express.Request, + @Res() res: express.Response, + @BodyParams() user: User + ): Promise { + const sessUser: User = req['user']; + if (sessUser.id !== user.id) { + res.status(401).send('Unauthorized'); + return; + } + return this.userService.save(user); + } +} diff --git a/src/server/modules/user/entity/user-favor-tag.entity.ts b/src/server/modules/user/entity/user-favor-tag.entity.ts new file mode 100755 index 0000000..527f61d --- /dev/null +++ b/src/server/modules/user/entity/user-favor-tag.entity.ts @@ -0,0 +1,30 @@ +import { Property } from '@tsed/common'; +import { + Entity, + OneToOne, + JoinColumn, + ManyToOne, + OneToMany, +} from 'typeorm'; +import { Base } from '../../common/util/entity/base.entity'; +import { User } from '../../user/entity/user.entity'; +import { UserAnalysisFavorTag } from '../../user-analysis/entity/user-analysis-favor-tag.entity'; + +@Entity() +export class UserFavorTag extends Base { + @ManyToOne(type => User, user => user.favorTagList) + @Property() + user: User; + + @ManyToOne(type => UserAnalysisFavorTag, userAnalysisFavorTag => userAnalysisFavorTag.userFavorTag, { + eager: true, + }) + @Property() + userAnalysisFavorTag: UserAnalysisFavorTag; + + constructor(user?: User, userAnalysisFavorTag?: UserAnalysisFavorTag) { + super(); + this.user = user; + this.userAnalysisFavorTag = userAnalysisFavorTag; + } +} diff --git a/src/server/modules/user/entity/user-followers.entity.ts b/src/server/modules/user/entity/user-followers.entity.ts new file mode 100755 index 0000000..214b988 --- /dev/null +++ b/src/server/modules/user/entity/user-followers.entity.ts @@ -0,0 +1,26 @@ +import { MinLength, MaxLength, Property } from '@tsed/common'; +import { + Entity, + Column, + OneToMany, + OneToOne, + JoinColumn, + ManyToOne, + Index, + Unique +} from 'typeorm'; +import { Base } from '../../common/util/entity/base.entity'; +import { User } from '../../user/entity/user.entity'; + +@Entity() +@Unique(['user', 'target']) +export class UserFollowers extends Base { + @ManyToOne(type => User, user => user.followersList) + @Property() + user: User; + + @ManyToOne(type => User, user => user.followingList) + // @JoinColumn() + @Property() + target: User; +} diff --git a/src/server/modules/user/entity/user.entity.ts b/src/server/modules/user/entity/user.entity.ts new file mode 100755 index 0000000..7f996af --- /dev/null +++ b/src/server/modules/user/entity/user.entity.ts @@ -0,0 +1,147 @@ +import { MinLength, MaxLength, Property } from '@tsed/common'; +import { + Entity, + Column, + OneToMany, + OneToOne, + JoinColumn, + ManyToOne, + Index +} from 'typeorm'; + +import { Base } from '../../common/util/entity/base.entity'; +import { Article } from '../../article/entity/article.entity'; +import { Attachments } from '../../attachments/entity/attachments.entity'; +import { OAuth } from '../../oauth/entity/oauth.entity'; +import { ArticleSeries } from '../../article/entity/article-series.entity'; +import { ArticleTag } from '../../article/entity/article-tag.entity'; + +import { AgeLimitType } from '../../../../shared/common/type/ageLimit.type'; +import { LangType } from '../../../../shared/common/type/lang.type'; +import { UserFollowers } from './user-followers.entity'; +import { UserFavorTag } from './user-favor-tag.entity'; +import { ArticleBookmarks } from '../../article/entity/article-bookmarks.entity'; +import { ArticleCommentsTag } from '../../article/entity/article-comments-tag.entity'; +import { ArticleLike } from '../../article/entity/article-like.entity'; + + +@Entity() +export class User extends Base { + @Column({ + nullable: true + }) + @Index('IDX_User_nickname') + @Property() + @MinLength(8) + @MaxLength(50) + nickname: string; + + @Column({ + nullable: true + }) + @Property() + followerCount: number; + + @Column({ + nullable: true + }) + @Property() + followingCount: number; + + @Column({ + default: AgeLimitType.ALL + }) + @Property() + ageLimit: AgeLimitType; + + @Column({ + default: LangType.KO + }) + @Property() + displayLanguage: LangType; + + @Column({ + default: false + }) + @Property() + articlePublicYN: boolean; + + @OneToOne(type => Attachments, { + nullable: true, + eager: true + }) + @JoinColumn() + @Property() + profileImageAttachments: Attachments; + + @OneToOne(type => Attachments, { + nullable: true, + eager: true + }) + @JoinColumn() + @Property() + backgroundImageAttachments: Attachments; + + @Column({ + nullable: true + }) + @Property() + introduction: string; + + @Column({ + nullable: true + }) + @Property() + email: string; + + @Column({ + default: false + }) + @Property() + favorShowedYN: boolean; + + @OneToMany(type => Article, article => article.user) + articleList: Article[]; + + @OneToMany(type => ArticleTag, articleTag => articleTag.user) + articleTagList: ArticleTag[]; + + @OneToMany(type => ArticleCommentsTag, articleCommentsTag => articleCommentsTag.user) + articleCommentsTagList: ArticleCommentsTag[]; + + @OneToMany(type => ArticleSeries, articleSeries => articleSeries.user) + articleSeriesList: ArticleSeries[]; + + // @OneToMany(type => Attachments, attachments => attachments.user) ==> 2019.01.07 user 삭제 + @OneToOne(type => Attachments) + attachmentsList: Attachments[]; + + @OneToMany(type => OAuth, oauth => oauth.user) + oauthes: OAuth[]; + + @OneToMany(type => UserFollowers, userFollowers => userFollowers.user) + followersList: UserFollowers[]; + + @OneToMany(type => UserFollowers, userFollowers => userFollowers.target, { + eager: true + }) + @Property() + followingList: UserFollowers[]; + + @OneToMany(type => ArticleBookmarks, articleBookmarks => articleBookmarks.user, { + eager: true + }) + @Property() + articleBookmarksList: ArticleBookmarks[]; + + @OneToMany(type => UserFavorTag, userFavorTag => userFavorTag.user) + favorTagList: UserFavorTag[]; + + @OneToMany(type => ArticleLike, articleLike => articleLike.user) + @Property() + articleLikeList: ArticleLike[]; + + constructor(uid?: string) { + super(uid); + } +} diff --git a/src/server/modules/user/service/user-favor-tag.service.ts b/src/server/modules/user/service/user-favor-tag.service.ts new file mode 100755 index 0000000..8d21632 --- /dev/null +++ b/src/server/modules/user/service/user-favor-tag.service.ts @@ -0,0 +1,84 @@ +/** + * 파 일 명: user-favor-tag.service.ts + * 작성일자: 2019-01-19 + * 작 성 자: 박병준 + * 설 명: Follow 관리 로직을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Service } from '@tsed/di'; +import { TypeORMService } from '@tsed/typeorm'; +import { EntityManager, Repository } from 'typeorm'; + +import { AbstractEntityService } from '../../common/util/service/abstract-entity.service'; +import { UserFavorTag } from '../entity/user-favor-tag.entity'; +import { User } from '../entity/user.entity'; +import { UserAnalysisFavorTag } from '../../user-analysis/entity/user-analysis-favor-tag.entity'; + +@Service() +export class UserFavorTagService extends AbstractEntityService { + constructor(typeORMService: TypeORMService) { + super(typeORMService); + } + + getRepository(entityManager: EntityManager): Repository { + return entityManager.getRepository(UserFavorTag); + } + + + async findAllByUser(uid: string): Promise { + return this.repository.find({ + user: { id: uid }, + }); + } + + async findAllTagNameByUser(uid: string): Promise { + return new Promise(async (resolve, reject) => { + const userFavorTagList = await this.findAllByUser(uid); + const tagList: string[] = []; + if (userFavorTagList && 0 < userFavorTagList.length) { + for (const userFavorTag of userFavorTagList) { + const articleTagList = userFavorTag.userAnalysisFavorTag.articleTagList; + if (articleTagList && 0 < articleTagList.length) { + for (const articleTag of articleTagList) { + tagList.push(articleTag.tagName); + } + } + } + } + return resolve(tagList); + }); + } + + async saveAllList(user: User, addedUserFavorTagList: UserFavorTag[], deletedUserFavorTagList: UserFavorTag[]): Promise { + const queryRunner = this.createQueryRunner(); + await queryRunner.connect(); + await queryRunner.startTransaction(); + + try { + if (deletedUserFavorTagList && 0 < deletedUserFavorTagList.length) { + for (const deletedUserFavorTag of deletedUserFavorTagList) { + await super.removeWithTransaction(queryRunner.manager, deletedUserFavorTag.id); + } + } + + let count = 0; + if (addedUserFavorTagList && 0 < addedUserFavorTagList.length) { + count = addedUserFavorTagList.length; + for (const addedUserFavorTag of addedUserFavorTagList) { + addedUserFavorTag.user = user; + await super.saveWithTransaction(queryRunner.manager, addedUserFavorTag); + } + } + await queryRunner.commitTransaction(); + return count; + } catch (error) { + console.log(error); + await queryRunner.rollbackTransaction(); + } finally { + await queryRunner.release(); + } + } +} diff --git a/src/server/modules/user/service/user-followers.service.ts b/src/server/modules/user/service/user-followers.service.ts new file mode 100755 index 0000000..f17b1af --- /dev/null +++ b/src/server/modules/user/service/user-followers.service.ts @@ -0,0 +1,35 @@ +/** + * 파 일 명: user-followers.service.ts + * 작성일자: 2019-01-15 + * 작 성 자: 이숙영 + * 설 명: Follow 관리 로직을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Service } from '@tsed/di'; +import { TypeORMService } from '@tsed/typeorm'; +import { EntityManager, Repository } from 'typeorm'; + +import { AbstractEntityService } from '../../common/util/service/abstract-entity.service'; +import { UserFollowers } from '../entity/user-followers.entity'; + +@Service() +export class UserFollowersService extends AbstractEntityService { + constructor(typeORMService: TypeORMService) { + super(typeORMService); + } + + getRepository(entityManager: EntityManager): Repository { + return entityManager.getRepository(UserFollowers); + } + + findAllFollowers(id: string): Promise { + return this.repository.find({ relations: ['user', 'target'], where: { target: id } }); + } + + findAllFollowing(id: string): Promise { + return this.repository.find({ relations: ['user', 'target'], where: { user: id } }); + } +} diff --git a/src/server/modules/user/service/user.service.ts b/src/server/modules/user/service/user.service.ts new file mode 100755 index 0000000..5551004 --- /dev/null +++ b/src/server/modules/user/service/user.service.ts @@ -0,0 +1,85 @@ +/** + * 파 일 명: user.service.ts + * 작성일자: 2018-12-20 + * 작 성 자: 박병준 + * 설 명: User 관리 로직을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Service } from '@tsed/di'; +import { TypeORMService } from '@tsed/typeorm'; +import { EntityManager, Repository, Not, Like } from 'typeorm'; + +import { AbstractEntityService } from '../../common/util/service/abstract-entity.service'; +import { User } from '../entity/user.entity'; +import { ArticleService as MongdbArticleService } from '../../../mongodb/article/service/article.service'; +import { UserFavorTagService } from './user-favor-tag.service'; + +@Service() +export class UserService extends AbstractEntityService { + constructor( + typeORMService: TypeORMService, + private mongdbArticleService: MongdbArticleService, + private userFavorTagService: UserFavorTagService, + ) { + super(typeORMService); + } + + getRepository(entityManager: EntityManager): Repository { + return entityManager.getRepository(User); + } + + async findByNickname(nickname: string): Promise { + return this.repository.findOne({ nickname: nickname }); + } + + async findAllByWriter(nickname: string): Promise { + return this.repository.find({ where: { nickname: Like(`%${nickname}%`) } }); + } + + async findWithRandom(size: number): Promise { + return this.repository.createQueryBuilder('user_random').select('id').orderBy('RANDOM()').limit(size).execute(); + } + + async findAllRecommended(uid: string, page: number): Promise { + return new Promise(async (resolve, reject) => { + const pageSize = 10; + page = page > 0 ? page : 1; + const tagNameList = await this.userFavorTagService.findAllTagNameByUser(uid); + + const list = await this.mongdbArticleService.findAllUserByTagName(uid, tagNameList, pageSize, page); + if (!list || 0 === list.length) { + return resolve(undefined); + } + const userList: User[] = []; + for (const item of list) { + userList.push(item._id); + } + return resolve(userList); + }); + } + + async findAllByKeyword(keyword: string): Promise { + return new Promise(async (resolve, reject) => { + this.repository.find({ + where: { nickname: Like(`%${keyword}%`) }, + take: 50, + cache: true, + }) + .then((result) => { + return resolve(result); + }) + .catch((reason) => { + return null; + }); + }); + } + + async save(model: User): Promise { + const _user = await super.save(model); + await this.mongdbArticleService.saveUser(_user); + return _user; + } +} diff --git a/src/server/mongodb/article/model/article-random.model.ts b/src/server/mongodb/article/model/article-random.model.ts new file mode 100755 index 0000000..6b6d2f6 --- /dev/null +++ b/src/server/mongodb/article/model/article-random.model.ts @@ -0,0 +1,24 @@ +import { Property } from '@tsed/common'; +import { Model } from '@tsed/mongoose'; +import { Base } from '../../common/util/model/base.model'; +import { Article as ArticleModel } from '../../../../shared/article/model/article.model'; +import { ArticleType } from '../../../../shared/article/type/article-type.type'; +import { DateUtil } from '../../../../shared/common/util/date.util'; + +@Model() +export class ArticleRandom extends Base { + @Property() + type: ArticleType; + + constructor(article?: ArticleModel) { + super(undefined); + if (article) { + this.id = article.id; + this.type = article.type; + this.createDate = DateUtil.nowUTC(); + this.updateDate = DateUtil.nowUTC(); + this.deleteDate = article.deleteDate; + } + } + +} diff --git a/src/server/mongodb/article/model/article.model.ts b/src/server/mongodb/article/model/article.model.ts new file mode 100755 index 0000000..1da61f0 --- /dev/null +++ b/src/server/mongodb/article/model/article.model.ts @@ -0,0 +1,33 @@ +import { Property } from '@tsed/common'; +import { Model } from '@tsed/mongoose'; +import { Base } from '../../common/util/model/base.model'; +import { Article as ArticleModel } from '../../../../shared/article/model/article.model'; +import { Cartoons } from '../../../../shared/cartoons/model/cartoons.model'; +import { Illustrations } from '../../../../shared/illustrations/model/illustrations.model'; +import { Novel } from '../../../../shared/novel/model/novel.model'; +import { ArticleType } from '../../../../shared/article/type/article-type.type'; + +@Model() +export class Article extends Base { + @Property() + type: ArticleType; + + @Property() + userId: string; + + @Property() + article: Cartoons | Illustrations | Novel; + + constructor(article?: ArticleModel) { + super(undefined); + if (article) { + this.id = article.id; + this.createDate = article.createDate; + this.updateDate = article.updateDate; + this.deleteDate = article.deleteDate; + this.type = article.type; + this.userId = article.user.id; + this.article = article; + } + } +} diff --git a/src/server/mongodb/article/service/article-random.service.ts b/src/server/mongodb/article/service/article-random.service.ts new file mode 100755 index 0000000..7c2fa97 --- /dev/null +++ b/src/server/mongodb/article/service/article-random.service.ts @@ -0,0 +1,65 @@ +import { Service, Inject } from '@tsed/di'; +import { MongooseModel } from '@tsed/mongoose'; + +import { AbstractModelService } from '../../common/util/service/abstract-model.service'; +import { Article } from '../model/article.model'; +import { ArticleRandom } from '../model/article-random.model'; +import { ArticleType } from '../../../../shared/article/type/article-type.type'; +import { ArticleService } from './article.service'; + +@Service() +export class ArticleRandomService extends AbstractModelService { + constructor( + @Inject(ArticleRandom) mongooseModel: MongooseModel, + @Inject(Article) private mongooseModelArticle: MongooseModel
, + private articleService: ArticleService, + ) { + super(mongooseModel); + } + + async genRandom(size: number): Promise { + return new Promise((resolve, reject) => { + const randomList = this.mongooseModelArticle.aggregate([ + { $sample: { size: size } } + ]).exec((err, result) => { + if (err) { + console.log(err); + return reject(err); + } + if (!result || 0 === result.length) { + return resolve(0); + } + for (const article of result) { + this.save(article); + } + return resolve(result.length); + }); + }); + } + + async save(article: Article): Promise { + const articleRandom = new ArticleRandom(article); + + return super.save(articleRandom); + } + + async findAllByPage(articleType: ArticleType, size: number, page: number): Promise { + const randomArticleLIst = await this.mongooseModel + .find({ type: articleType, }, { _id: 0, id: 1 }) + .skip((page - 1) * size) + .limit(size).select('id').exec(); + if (!randomArticleLIst || 0 === randomArticleLIst.length) { + return []; + } + const articleList: Article[] = []; + for (const randomArticle of randomArticleLIst) { + articleList.push(await this.mongooseModelArticle.findOneAndUpdate( + { id: randomArticle.id }, + { $inc: { 'article.viewCount': 1 } }, + { new: true } + )); + } + + return articleList; + } +} diff --git a/src/server/mongodb/article/service/article.service.ts b/src/server/mongodb/article/service/article.service.ts new file mode 100755 index 0000000..29411af --- /dev/null +++ b/src/server/mongodb/article/service/article.service.ts @@ -0,0 +1,153 @@ +import { Service, Inject } from '@tsed/di'; +import { MongooseModel } from '@tsed/mongoose'; + +import { AbstractModelService } from '../../common/util/service/abstract-model.service'; +import { Article } from '../model/article.model'; +import { Article as ArticleModel } from '../../../../shared/article/model/article.model'; +import { User as UserModel } from '../../../../shared/user/model/user.model'; +import { User } from '../../../modules/user/entity/user.entity'; +import { ArticleLike } from 'src/shared/article/model/article-like.model'; + +@Service() +export class ArticleService extends AbstractModelService
{ + constructor( + @Inject(Article) mongooseModel: MongooseModel
, + ) { + super(mongooseModel); + } + + async findByAid(aid: string): Promise
{ + return this.mongooseModel.findOne({ id: aid }).exec(); + } + + async findAllByUid(uid: string, timestamp: number, size: number): Promise { + const _timestamp = 0 === timestamp ? Date.now() : timestamp; + + const conditions = { + userId: uid, + createDate: { + $lt: new Date(_timestamp), + }, + }; + + await this.mongooseModel.updateMany( + conditions, + { $inc: { 'article.viewCount': 1 } }, + ).limit(size).exec(); + + return this.mongooseModel.find(conditions).sort({ 'createDate': -1 }).limit(size).exec(); + } + + async findAllIdByTagName(uid: string, tagNameList: string[], size: number, page: number): Promise { + let conditions: any = {}; + if (tagNameList && 0 < tagNameList.length) { + conditions = { + userId: { $ne: uid }, + 'article.tagList.tagName': { '$in': tagNameList }, + }; + } + + + await this.mongooseModel.updateMany( + conditions, + { $inc: { 'article.viewCount': 1 } }, + ).skip((page - 1) * size) + .limit(size).exec(); + + return this.mongooseModel + .find(conditions) + .skip((page - 1) * size) + .limit(size).select('id').exec(); + } + + async findAllByTagName(uid: string, tagNameList: string[], size: number, page: number): Promise { + let conditions: any = {}; + if (tagNameList && 0 < tagNameList.length) { + conditions = { + userId: { $ne: uid }, + 'article.tagList.tagName': { '$in': tagNameList }, + }; + } + + await this.mongooseModel.updateMany( + conditions, + { $inc: { 'article.viewCount': 1 } }, + ).skip((page - 1) * size) + .limit(size).exec(); + + return this.mongooseModel + .find(conditions) + .skip((page - 1) * size) + .limit(size).exec(); + } + + + async findAllUserByTagName(uid: string, tagNameList: string[], size: number, page: number): Promise<{ _id: User, count: number }[]> { + let conditions: any = {}; + if (tagNameList && 0 < tagNameList.length) { + conditions = { + userId: { $ne: uid }, + 'article.tagList.tagName': { '$in': tagNameList }, + }; + } + const result = await this.mongooseModel.aggregate([ + { '$match': conditions }, + { + $group: { + _id: '$article.user', + count: { $sum: 1 }, + } + } + ]) + .skip((page - 1) * size) + .limit(size).exec(); + + return result; + } + + async saveModel(article: ArticleModel): Promise
{ + let articleMongodb = await this.findByAid(article.id); + if (!articleMongodb) { + articleMongodb = new Article(article); + } else { + articleMongodb.article = article; + } + + return this.save(articleMongodb); + } + + async saveLike(articleLike: ArticleLike): Promise { + const articleMongodb = await this.findByAid(articleLike.article.id); + if (!articleMongodb) { + return; + } + + if (!articleMongodb.article.likeList || 0 === articleMongodb.article.likeList.length) { + articleMongodb.article.likeList = [articleLike]; + } else { + let likeIndex = -1; + for (let index = 0; index < articleMongodb.article.likeList.length; index++) { + const _articleLike = articleMongodb.article.likeList[index]; + if (_articleLike.id === articleLike.id) { + likeIndex = index; + break; + } + } + if (-1 === likeIndex) { + articleMongodb.article.likeList.push(articleLike); + } else { + articleMongodb.article.likeList[likeIndex] = articleLike; + } + } + this.save(articleMongodb); + return 1; + } + + async saveUser(user: UserModel): Promise { + return this.mongooseModel.updateMany( + { userId: user.id }, + { $set: { 'article.user': user } }, + ).exec(); + } + +} diff --git a/src/server/mongodb/common/util/controller/abstract-rest.controller.ts b/src/server/mongodb/common/util/controller/abstract-rest.controller.ts new file mode 100755 index 0000000..10dee14 --- /dev/null +++ b/src/server/mongodb/common/util/controller/abstract-rest.controller.ts @@ -0,0 +1,67 @@ +/** + * 파 일 명: abstract-rest.controller.ts + * 작성일자: 2018-12-17 + * 작 성 자: 박병준 + * 설 명: express에서 사용되는 RESTful Controller의 공통 사항을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import * as express from 'express'; +import { + BodyParams, + Delete, + Get, + PathParams, + Post, + Put, + Required, + Req, + Res, +} from '@tsed/common'; +import { AbstractModelService } from '../service/abstract-model.service'; +import { Base } from '../model/base.model'; +import { AbstractController } from './abstract.controller'; + +export abstract class AbstractRestController extends AbstractController { + + constructor(service: AbstractModelService) { + super(service); + } + + @Get('/:id') + async get( + @Req() req: express.Request, + @Res() res: express.Response, + @Required() @PathParams('id') id: string, + ): Promise { + const model = await this.service.find(id); + + if (model) { + return model; + } + + res.status(203).send('No Content'); + } + + @Get('/') + async getAll(): Promise { + return this.service.findAll(); + } + + @Put('/') + save(@BodyParams() model: T) { + return this.service.save(model); + } + + @Post('/') + async update(@BodyParams() model: T): Promise { + return this.service.save(model); + } + + @Delete('/') + async remove(@BodyParams('id') @Required() id: string): Promise { + return this.service.remove(id); + } +} diff --git a/src/server/mongodb/common/util/controller/abstract.controller.ts b/src/server/mongodb/common/util/controller/abstract.controller.ts new file mode 100755 index 0000000..6dfd116 --- /dev/null +++ b/src/server/mongodb/common/util/controller/abstract.controller.ts @@ -0,0 +1,20 @@ +/** + * 파 일 명: abstract.controller.ts + * 작성일자: 2018-12-17 + * 작 성 자: 박병준 + * 설 명: express에서 사용되는 Controller의 공통 사항을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import * as express from 'express'; +import { AbstractModelService } from '../service/abstract-model.service'; +import { Base } from '../model/base.model'; + +export abstract class AbstractController { + + constructor(protected service: AbstractModelService) { + } + +} diff --git a/src/server/mongodb/common/util/model/base.model.ts b/src/server/mongodb/common/util/model/base.model.ts new file mode 100755 index 0000000..a96dc27 --- /dev/null +++ b/src/server/mongodb/common/util/model/base.model.ts @@ -0,0 +1,35 @@ +/** + * 파 일 명: base.model.ts + * 작성일자: 2018-12-20 + * 작 성 자: 박병준 + * 설 명: base mongodb model 스키마를 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Property, MaxLength, IgnoreProperty } from '@tsed/common'; +import { Indexed } from '@tsed/mongoose'; + +export abstract class Base { + @IgnoreProperty() + _id: string; + + @Indexed() + @Property() + @MaxLength(22) + id: string; + + @Property() + createDate: Date; + + @Property() + updateDate: Date; + + @Property() + deleteDate: Date; + + constructor(id?: string) { + this.id = id; + } +} diff --git a/src/server/mongodb/common/util/service/abstract-model.service.ts b/src/server/mongodb/common/util/service/abstract-model.service.ts new file mode 100755 index 0000000..99e25eb --- /dev/null +++ b/src/server/mongodb/common/util/service/abstract-model.service.ts @@ -0,0 +1,41 @@ +/** + * 파 일 명: abstract-entity.service.ts + * 작성일자: 2018-12-17 + * 작 성 자: 박병준 + * 설 명: express에서 사용되는 mongodb Repository Service의 공통 사항을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { AfterRoutesInit } from '@tsed/common'; +import { MongooseModel } from '@tsed/mongoose'; + +import { Base } from '../model/base.model'; + +export abstract class AbstractModelService implements AfterRoutesInit { + constructor(protected mongooseModel: MongooseModel) { + } + + $afterRoutesInit() { + } + + async find(id: string): Promise { + return this.mongooseModel.findOne({ id: id }).exec(); + } + + async findAll(options = {}): Promise { + return this.mongooseModel.find(options); + } + + async save(model: T): Promise { + const _model = new this.mongooseModel(model); + await _model.save(); + return _model; + } + + async remove(id: string): Promise { + return await this.mongooseModel.findOne({ id: id }).remove().exec(); + } + +} diff --git a/src/server/mongodb/user/model/user-random.model.ts b/src/server/mongodb/user/model/user-random.model.ts new file mode 100755 index 0000000..fc268d9 --- /dev/null +++ b/src/server/mongodb/user/model/user-random.model.ts @@ -0,0 +1,17 @@ +import { Model } from '@tsed/mongoose'; +import { Base } from '../../common/util/model/base.model'; +import { User as UserModel } from '../../../../shared/user/model/user.model'; + +@Model() +export class UserRandom extends Base { + + constructor(user?: UserModel) { + super(undefined); + if (user) { + this.id = user.id; + this.createDate = user.createDate; + this.updateDate = user.updateDate; + this.deleteDate = user.deleteDate; + } + } +} diff --git a/src/server/mongodb/user/service/user-random.service.ts b/src/server/mongodb/user/service/user-random.service.ts new file mode 100755 index 0000000..edbf71f --- /dev/null +++ b/src/server/mongodb/user/service/user-random.service.ts @@ -0,0 +1,47 @@ +import { Service, Inject } from '@tsed/di'; +import { MongooseModel } from '@tsed/mongoose'; + +import { AbstractModelService } from '../../common/util/service/abstract-model.service'; +import { UserRandom } from '../model/user-random.model'; +import { User } from '../../../modules/user/entity/user.entity'; + +@Service() +export class UserRandomService extends AbstractModelService { + constructor( + @Inject(UserRandom) mongooseModel: MongooseModel, + ) { + super(mongooseModel); + } + + async genRandom(userList: User[]): Promise { + if (!userList || 0 === userList.length) { + return 0; + } + + for (const user of userList) { + const userRandom = new UserRandom(); + userRandom.id = user.id; + + await this.save(userRandom); + } + + return userList.length; + } + + async findAllByPage(size: number, page: number): Promise { + const userRandomLIst = await this.mongooseModel + .find() + .skip((page - 1) * size) + .limit(size).exec(); + if (!userRandomLIst || 0 === userRandomLIst.length) { + return null; + } + + const uidList: string[] = []; + for (const userRandom of userRandomLIst) { + uidList.push(userRandom.id); + } + + return uidList; + } +} diff --git a/src/server/test/database.sqlite b/src/server/test/database.sqlite new file mode 100755 index 0000000..49fa19e Binary files /dev/null and b/src/server/test/database.sqlite differ diff --git a/src/server/test/database_backup.sql b/src/server/test/database_backup.sql new file mode 100755 index 0000000..cb47eb3 --- /dev/null +++ b/src/server/test/database_backup.sql @@ -0,0 +1,672 @@ +INSERT INTO meta_comments_tag (id, createDate, updateDate, deleteDate, code, type, styleClass) VALUES ('7n7rRm6sgyNpjkC81Fj68g', '2019-01-25 07:23:12', '2019-01-25 07:23:12', NULL, 'TAG_1', 'article', '001'); +INSERT INTO meta_comments_tag (id, createDate, updateDate, deleteDate, code, type, styleClass) VALUES ('8gChrne6VCT6CCWyiwrE1', '2019-01-25 07:23:12', '2019-01-25 07:23:12', NULL, 'TAG_2', 'article', '001'); +INSERT INTO meta_comments_tag (id, createDate, updateDate, deleteDate, code, type, styleClass) VALUES ('3EMKfGY5meyu7KDtYmj5XT', '2019-01-25 07:23:13', '2019-01-25 07:23:13', NULL, 'TAG_3', 'article', '001'); +INSERT INTO meta_comments_tag (id, createDate, updateDate, deleteDate, code, type, styleClass) VALUES ('kyz7r1pDHJeft58e5SEwFn', '2019-01-25 07:23:13', '2019-01-25 07:23:13', NULL, 'TAG_4', 'article', '001'); +INSERT INTO meta_comments_tag (id, createDate, updateDate, deleteDate, code, type, styleClass) VALUES ('6xVQoPaUza2YfDE3RwSAV3', '2019-01-25 07:23:13', '2019-01-25 07:23:13', NULL, 'TAG_5', 'article', '001'); +INSERT INTO meta_comments_tag (id, createDate, updateDate, deleteDate, code, type, styleClass) VALUES ('3zrrj75m5HnzFeybDzA7uY', '2019-01-25 07:23:13', '2019-01-25 07:23:13', NULL, 'TAG_6', 'article', '002'); +INSERT INTO meta_comments_tag (id, createDate, updateDate, deleteDate, code, type, styleClass) VALUES ('grp5Us4ozVtA4AsCBBi23a', '2019-01-25 07:23:13', '2019-01-25 07:23:13', NULL, 'TAG_7', 'article', '002'); +INSERT INTO meta_comments_tag (id, createDate, updateDate, deleteDate, code, type, styleClass) VALUES ('r4fNGczeUwwxABo7517eiQ', '2019-01-25 07:23:14', '2019-01-25 07:23:14', NULL, 'TAG_8', 'article', '002'); +INSERT INTO meta_comments_tag (id, createDate, updateDate, deleteDate, code, type, styleClass) VALUES ('8M9AkXsFfW8uyaywps4neS', '2019-01-25 07:23:14', '2019-01-25 07:23:14', NULL, 'TAG_9', 'article', '002'); +INSERT INTO meta_comments_tag (id, createDate, updateDate, deleteDate, code, type, styleClass) VALUES ('6N11baKTBvdmMVegN8fXrE', '2019-01-25 07:23:14', '2019-01-25 07:23:14', NULL, 'TAG_10', 'article', '002'); +INSERT INTO meta_comments_tag (id, createDate, updateDate, deleteDate, code, type, styleClass) VALUES ('vrD7s9eRS48cC4JTHb3tde', '2019-01-25 07:23:14', '2019-01-25 07:23:14', NULL, 'TAG_11', 'article', '003'); +INSERT INTO meta_comments_tag (id, createDate, updateDate, deleteDate, code, type, styleClass) VALUES ('qMnLqmXbtdXEnPhwx5SoEm', '2019-01-25 07:23:14', '2019-01-25 07:23:14', NULL, 'TAG_12', 'article', '003'); +INSERT INTO meta_comments_tag (id, createDate, updateDate, deleteDate, code, type, styleClass) VALUES ('8W7HVSatFjKDpWPpTcP6EQ', '2019-01-25 07:23:14', '2019-01-25 07:23:14', NULL, 'TAG_13', 'article', '003'); +INSERT INTO meta_comments_tag (id, createDate, updateDate, deleteDate, code, type, styleClass) VALUES ('etzUJh99whusuah5k9duRc', '2019-01-25 07:23:15', '2019-01-25 07:23:15', NULL, 'TAG_14', 'article', '003'); +INSERT INTO meta_comments_tag (id, createDate, updateDate, deleteDate, code, type, styleClass) VALUES ('chPYEaJFWao5XjkhMNTivx', '2019-01-25 07:23:15', '2019-01-25 07:23:15', NULL, 'TAG_15', 'article', '003'); +INSERT INTO meta_comments_tag (id, createDate, updateDate, deleteDate, code, type, styleClass) VALUES ('p3n3vcKfdCbFkTjuLQECf5', '2019-01-25 07:23:15', '2019-01-25 07:23:15', NULL, 'TAG_16', 'article', '004'); +INSERT INTO meta_comments_tag (id, createDate, updateDate, deleteDate, code, type, styleClass) VALUES ('987fae789FBP81AAj92CQy', '2019-01-25 07:23:15', '2019-01-25 07:23:15', NULL, 'TAG_17', 'article', '004'); +INSERT INTO meta_comments_tag (id, createDate, updateDate, deleteDate, code, type, styleClass) VALUES ('soK2yKUT3hsM8UJ9f7u7u', '2019-01-25 07:23:15', '2019-01-25 07:23:15', NULL, 'TAG_18', 'article', '004'); +INSERT INTO meta_comments_tag (id, createDate, updateDate, deleteDate, code, type, styleClass) VALUES ('qGtHFGA3LLKGrmNuXN4kmq', '2019-01-25 07:23:15', '2019-01-25 07:23:15', NULL, 'TAG_19', 'article', '004'); +INSERT INTO meta_comments_tag (id, createDate, updateDate, deleteDate, code, type, styleClass) VALUES ('avo5FZX5sFUKNFpcfudwzi', '2019-01-25 07:23:16', '2019-01-25 07:23:16', NULL, 'TAG_20', 'article', '004'); +INSERT INTO meta_comments_tag (id, createDate, updateDate, deleteDate, code, type, styleClass) VALUES ('a59ncYNta5iChNa9rCVUnP', '2019-01-25 07:23:16', '2019-01-25 07:23:16', NULL, 'TAG_21', 'article', '005'); +INSERT INTO meta_comments_tag (id, createDate, updateDate, deleteDate, code, type, styleClass) VALUES ('mAMHxDesAHFJyoE2at5mgb', '2019-01-25 07:23:16', '2019-01-25 07:23:16', NULL, 'TAG_24', 'article', '005'); +INSERT INTO meta_comments_tag (id, createDate, updateDate, deleteDate, code, type, styleClass) VALUES ('iKFJcNYmH5SCzqviXM7DfR', '2019-01-25 07:23:16', '2019-01-25 07:23:16', NULL, 'TAG_22', 'article', '005'); +INSERT INTO meta_comments_tag (id, createDate, updateDate, deleteDate, code, type, styleClass) VALUES ('rJTA6tkXr1QxXp15sGKCoU', '2019-01-25 07:23:16', '2019-01-25 07:23:16', NULL, 'TAG_23', 'article', '005'); +INSERT INTO meta_comments_tag (id, createDate, updateDate, deleteDate, code, type, styleClass) VALUES ('iak5JUCaNPKGrjbkysXZCq', '2019-01-25 07:23:16', '2019-01-25 07:23:16', NULL, 'TAG_25', 'article', '005'); +INSERT INTO meta_comments_tag (id, createDate, updateDate, deleteDate, code, type, styleClass) VALUES ('doSmvAGXweGbsd65BVJNXW', '2019-01-25 07:23:17', '2019-01-25 07:23:17', NULL, 'TAG_26', 'author', '006'); +INSERT INTO meta_comments_tag (id, createDate, updateDate, deleteDate, code, type, styleClass) VALUES ('4vKcMGyc77SSTSFCpP83wG', '2019-01-25 07:23:17', '2019-01-25 07:23:17', NULL, 'TAG_27', 'author', '006'); +INSERT INTO meta_comments_tag (id, createDate, updateDate, deleteDate, code, type, styleClass) VALUES ('qaBxqEeQBS5Faz76dvrLVa', '2019-01-25 07:23:17', '2019-01-25 07:23:17', NULL, 'TAG_28', 'author', '006'); +INSERT INTO meta_comments_tag (id, createDate, updateDate, deleteDate, code, type, styleClass) VALUES ('9K19yPm6hrnMjCz6YeFCRY', '2019-01-25 07:23:17', '2019-01-25 07:23:17', NULL, 'TAG_29', 'author', '006'); +INSERT INTO meta_comments_tag (id, createDate, updateDate, deleteDate, code, type, styleClass) VALUES ('cs636dtaHiknKrkqNhtZKv', '2019-01-25 07:23:17', '2019-01-25 07:23:17', NULL, 'TAG_30', 'author', '006'); +INSERT INTO meta_comments_tag (id, createDate, updateDate, deleteDate, code, type, styleClass) VALUES ('q1dJLCEPaCDxLxE2Nxaemj', '2019-01-25 07:23:17', '2019-01-25 07:23:17', NULL, 'TAG_31', 'author', '007'); +INSERT INTO meta_comments_tag (id, createDate, updateDate, deleteDate, code, type, styleClass) VALUES ('6SS5AC3x5hY2cmhfffdNbB', '2019-01-25 07:23:18', '2019-01-25 07:23:18', NULL, 'TAG_32', 'author', '007'); +INSERT INTO meta_comments_tag (id, createDate, updateDate, deleteDate, code, type, styleClass) VALUES ('otHs32nDPSzKWpcLsqadHf', '2019-01-25 07:23:18', '2019-01-25 07:23:18', NULL, 'TAG_33', 'author', '007'); +INSERT INTO meta_comments_tag (id, createDate, updateDate, deleteDate, code, type, styleClass) VALUES ('hgcdnuFdQHHkXUCRxFQCTb', '2019-01-25 07:23:18', '2019-01-25 07:23:18', NULL, 'TAG_34', 'author', '007'); +INSERT INTO meta_comments_tag (id, createDate, updateDate, deleteDate, code, type, styleClass) VALUES ('frHAJHwccPgsYz7jJsEpfK', '2019-01-25 07:23:18', '2019-01-25 07:23:18', NULL, 'TAG_35', 'author', '007'); +INSERT INTO meta_comments_tag (id, createDate, updateDate, deleteDate, code, type, styleClass) VALUES ('6rmjt9aYyYieADZ9UmGqeS', '2019-01-25 07:23:18', '2019-01-25 07:23:18', NULL, 'TAG_36', 'author', '008'); +INSERT INTO meta_comments_tag (id, createDate, updateDate, deleteDate, code, type, styleClass) VALUES ('gepRWmarNjGfPdNx1YSr9M', '2019-01-25 07:23:18', '2019-01-25 07:23:18', NULL, 'TAG_37', 'author', '008'); +INSERT INTO meta_comments_tag (id, createDate, updateDate, deleteDate, code, type, styleClass) VALUES ('43WjC3isPhgHzU5RGGCH6o', '2019-01-25 07:23:18', '2019-01-25 07:23:18', NULL, 'TAG_38', 'author', '008'); +INSERT INTO meta_comments_tag (id, createDate, updateDate, deleteDate, code, type, styleClass) VALUES ('8UH97ycLEynoKKCBzWfHjc', '2019-01-25 07:23:19', '2019-01-25 07:23:19', NULL, 'TAG_39', 'author', '008'); +INSERT INTO meta_comments_tag (id, createDate, updateDate, deleteDate, code, type, styleClass) VALUES ('v4Fx5KMQSHyChVDZtzjjaS', '2019-01-25 07:23:19', '2019-01-25 07:23:19', NULL, 'TAG_40', 'author', '008'); +INSERT INTO meta_comments_tag (id, createDate, updateDate, deleteDate, code, type, styleClass) VALUES ('mQERDaW9qsZ5Q1Xw4unms3', '2019-01-25 07:23:19', '2019-01-25 07:23:19', NULL, 'TAG_41', 'author', '009'); +INSERT INTO meta_comments_tag (id, createDate, updateDate, deleteDate, code, type, styleClass) VALUES ('iPj3sSA5E9Kq4GhBRNSpp2', '2019-01-25 07:23:19', '2019-01-25 07:23:19', NULL, 'TAG_42', 'author', '009'); +INSERT INTO meta_comments_tag (id, createDate, updateDate, deleteDate, code, type, styleClass) VALUES ('utcpS9YLGFp81m2wVHoSQk', '2019-01-25 07:23:19', '2019-01-25 07:23:19', NULL, 'TAG_43', 'author', '009'); +INSERT INTO meta_comments_tag (id, createDate, updateDate, deleteDate, code, type, styleClass) VALUES ('2FXPZp3DptdTRRQ5EJuj8X', '2019-01-25 07:23:20', '2019-01-25 07:23:20', NULL, 'TAG_44', 'author', '009'); +INSERT INTO meta_comments_tag (id, createDate, updateDate, deleteDate, code, type, styleClass) VALUES ('wsai8t1cjzXWbagwW9xNpk', '2019-01-25 07:23:20', '2019-01-25 07:23:20', NULL, 'TAG_45', 'author', '009'); +INSERT INTO meta_comments_tag (id, createDate, updateDate, deleteDate, code, type, styleClass) VALUES ('sAMcXRZq7GBU58TxwEpfEM', '2019-01-25 07:23:20', '2019-01-25 07:23:20', NULL, 'TAG_46', 'author', '010'); +INSERT INTO meta_comments_tag (id, createDate, updateDate, deleteDate, code, type, styleClass) VALUES ('79i8SPhrSmEjLXD7mHQMsM', '2019-01-25 07:23:20', '2019-01-25 07:23:20', NULL, 'TAG_47', 'author', '010'); +INSERT INTO meta_comments_tag (id, createDate, updateDate, deleteDate, code, type, styleClass) VALUES ('nNmX1rJbgQa2oVgawsro5P', '2019-01-25 07:23:20', '2019-01-25 07:23:20', NULL, 'TAG_48', 'author', '010'); +INSERT INTO meta_comments_tag (id, createDate, updateDate, deleteDate, code, type, styleClass) VALUES ('gX5Kr1kRhzGNggWjEpNxit', '2019-01-25 07:23:20', '2019-01-25 07:23:20', NULL, 'TAG_49', 'author', '010'); +INSERT INTO meta_comments_tag (id, createDate, updateDate, deleteDate, code, type, styleClass) VALUES ('iu3torPPNXuJEQ3mkEHYu5', '2019-01-25 07:23:20', '2019-01-25 07:23:20', NULL, 'TAG_50', 'author', '010'); + + +-- Table: attachments +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('i6gzZA36na2p52UecGVVbv', '2019-01-20 13:30:53', '2019-01-20 13:30:53', NULL, '27092018152900-0001.png', 'image/png', 96302); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('uPXMD1i9g9pcUV9RQCaeie', '2019-01-20 13:30:53', '2019-01-20 13:30:53', NULL, '7112307.jpg', 'image/jpeg', 194083); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('jxgFHB7r75gWtbU2SCF6Uq', '2019-01-20 13:31:56', '2019-01-20 13:31:56', NULL, '#큐티허니.jpg', 'image/jpeg', 145272); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('hYVtUGD6C1qxHSDqA3C1Nw', '2019-01-20 13:32:29', '2019-01-20 13:32:29', NULL, '1f1c95f6-7a8c-4149-8125-9dd54b1a6a21.jpg', 'image/jpeg', 138338); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('eZ7V1VU4kfEANBNSr53swk', '2019-01-20 13:33:10', '2019-01-20 13:33:10', NULL, '1f219541-c9a2-4e94-b617-385e63922080.png', 'image/png', 592698); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('eGMrtZp1AcY73f42L5ReXJ', '2019-01-20 13:33:45', '2019-01-20 13:33:45', NULL, '6c5bd5f7-c4b5-4cf2-b1b5-6f2f558b668f.jpg', 'image/jpeg', 87215); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('xnwdBUYuTWuzUbYe8iqVAn', '2019-01-20 13:34:18', '2019-01-20 13:34:18', NULL, '7b143909-0390-422c-8c35-6ae3be21e589.jpg', 'image/jpeg', 253331); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('wvUVj9r89H6GnQJZBpSbhL', '2019-01-20 13:35:20', '2019-01-20 13:35:20', NULL, '7dd8a0a8-ac53-4700-85f5-315d95f66ade.jpg', 'image/jpeg', 396683); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('gJBCrxtVdgmtyaK8Jn8o3R', '2019-01-20 13:36:01', '2019-01-20 13:36:01', NULL, '95ccd627-4a1f-4a0a-9509-3e4cf80a70c0.jpg', 'image/jpeg', 79230); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('gQJEqbgPnhZtuuZYsuJJRC', '2019-01-20 13:36:37', '2019-01-20 13:36:37', NULL, '3113c59d-c656-439b-ab1a-75ce8c18f19c.jpg', 'image/jpeg', 267065); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('wyPXyqf8nM7KSSjUxX3xXB', '2019-01-20 13:37:34', '2019-01-20 13:37:34', NULL, '4808fa3d-6473-4df9-9ccd-5a298a60483e.jpg', 'image/jpeg', 157297); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('o3wuEYzRM2Mu4SN3e5tjpr', '2019-01-20 13:37:47', '2019-01-20 13:37:47', NULL, '27092018152900-0001.png', 'image/png', 96302); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('wow9TiLWKwRicdeXnELnyG', '2019-01-20 13:37:47', '2019-01-20 13:37:47', NULL, '7112307.jpg', 'image/jpeg', 194083); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('evDEB3joyTHc3gm4BcUHCV', '2019-01-20 13:38:36', '2019-01-20 13:38:36', NULL, '70898ad0-d6ea-4b60-bf3f-e10853fc39ba.png', 'image/png', 162664); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('mmvL1FXVJkee43DpHSiQfs', '2019-01-20 13:39:25', '2019-01-20 13:39:25', NULL, '13863073-11fa-4fd7-a8a2-5fc49826f8a3.jpg', 'image/jpeg', 231133); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('oJEoxqaExMwPMZATye1Vdv', '2019-01-20 13:39:58', '2019-01-20 13:39:58', NULL, 'ab1d94f9-ccf7-4770-a050-1a503ecf2048.png', 'image/png', 337275); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('n5wZ3gcbP1D78kkTYfBTvC', '2019-01-20 13:40:37', '2019-01-20 13:40:37', NULL, 'b865febb-8d77-49e4-85e9-682bed406419.jpg', 'image/jpeg', 574449); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('k2cuW1WuzuMucQdCUCc9Y2', '2019-01-20 13:41:08', '2019-01-20 13:41:08', NULL, 'bce0b343-d341-40bd-886d-0ae8eb88610f.png', 'image/png', 65033); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('789HinPETzxmoJoNrEzoUv', '2019-01-20 13:41:38', '2019-01-20 13:41:38', NULL, 'c10771c0-07e1-40b2-9e1c-34ed9ef600fc.png', 'image/png', 499851); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('8L4GTaP7qrPhiAU2W2fhmS', '2019-01-20 13:43:21', '2019-01-20 13:43:21', NULL, 'cfdc400a-3e43-4eb5-af3f-801be9c292c0.jpg', 'image/png', 60015); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('sv8uJiqL5cFNG1u2ZmT5KD', '2019-01-20 13:43:22', '2019-01-20 13:43:22', NULL, 'fe5792db-8071-42f0-b3fd-07dee15a3aa4.png', 'image/png', 257360); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('qSLevjhu5NVwjU86dZ8RA1', '2019-01-20 13:43:59', '2019-01-20 13:43:59', NULL, '2e548007-2c0d-4dac-8d72-a0b680618a2e.png', 'image/png', 463280); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('fBfQRdTAPp1gPBVNpwqYxZ', '2019-01-20 13:44:32', '2019-01-20 13:44:32', NULL, '3fdb682a-e153-41de-87af-ed69d522f8f9.png', 'image/png', 437633); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('ssLiWzdZ3SQgo4zYhWtr7e', '2019-01-20 13:45:13', '2019-01-20 13:45:13', NULL, '7cbcba4c-8df9-4c7b-85ec-bcab4763e35f.jpg', 'image/jpeg', 166326); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('aUEiXK6VQPEfTnt4AxQyHX', '2019-01-20 13:45:44', '2019-01-20 13:45:44', NULL, '19b5be4e-b5e7-4ece-a32b-8707c54f5401.jpg', 'image/jpeg', 378759); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('xbVmmxv6ZA2M5UQVazmW8r', '2019-01-20 13:46:24', '2019-01-20 13:46:24', NULL, '65a9ba61-dfe9-4d7b-a4aa-d23c8322bb4d.jpg', 'image/jpeg', 376845); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('xtwYqxGKuM3X8nnYiGr6wL', '2019-01-20 13:47:54', '2019-01-20 13:47:54', NULL, 'a2d2c827-0b7f-46b3-b963-3a7cb0c566b6.jpg', 'image/jpeg', 474098); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('4LMj9DuGMkRvBP6L6y9gjN', '2019-01-20 13:48:31', '2019-01-20 13:48:31', NULL, 'b9284e82-5039-437f-999d-f4b190d5a27c.jpg', 'image/jpeg', 424827); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('a6JHLsVewt6Qmcq7CwbgtU', '2019-01-20 13:49:55', '2019-01-20 13:49:55', NULL, '7cc8cbc8-e651-4152-90fa-b1cbcb8a0102.jpg', 'image/jpeg', 193065); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('7SB9sFzyfBNs56rLk3yBPc', '2019-01-20 13:50:30', '2019-01-20 13:50:30', NULL, '737715b4-8961-4f12-b2a6-e6473921f5bd.png', 'image/png', 1056292); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('qCA835cvKv57dPiFDEziFQ', '2019-01-20 13:51:20', '2019-01-20 13:51:20', NULL, '2c02fc75-dcf6-4563-b1f2-64df1896f363.jpg', 'image/jpeg', 309095); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('9KGEK1GgBsz11JHJx7sGMp', '2019-01-20 13:52:02', '2019-01-20 13:52:02', NULL, '#하츠네미쿠.png', 'image/png', 426882); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('uNsxFiWtk43jP5yVrXBwLM', '2019-01-20 13:52:31', '2019-01-20 13:52:31', NULL, 'b004d1aa-1f3a-4147-ad26-8a9f8963311a.png', 'image/png', 153487); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('dcQEXcbhaoyp2Xbz8mQPwn', '2019-01-20 13:52:55', '2019-01-20 13:52:55', NULL, '#하츠네미쿠.png', 'image/png', 39629); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('SLJk95UWeRsRE8ccPA5Lq', '2019-01-20 13:52:55', '2019-01-20 13:52:55', NULL, 'e3324ee0-a31c-453d-9a79-9b0b0fcdcc91.jpg', 'image/jpeg', 323054); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('xfkj7n8Ss24vrrdcbGDBuf', '2019-01-20 14:03:26', '2019-01-20 14:03:26', NULL, '1f6aa9ff39dd91c1cc46e9f70ab4b25c.jpg', 'image/jpeg', 277785); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('v1GNhsqStcmcabnWYgBQSN', '2019-01-20 14:04:33', '2019-01-20 14:04:33', NULL, '8359ec7d76cc0435d6906f006f9c505b.png', 'image/png', 597239); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('3yXhsLkofznmrmoWNKfttV', '2019-01-20 14:05:03', '2019-01-20 14:05:03', NULL, 'dd9f541d3bca4fd15247e99ae14b1300.png', 'image/png', 373895); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('94CKZTyn2qqbQ7UwHqpLmP', '2019-01-20 14:05:29', '2019-01-20 14:05:29', NULL, 'images (1).jpeg', 'image/jpeg', 10468); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('sPDXjxtuP8aTjaPDg8nv61', '2019-01-20 14:06:10', '2019-01-20 14:06:10', NULL, 'images.jpeg', 'image/jpeg', 11318); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('qxhrvzcJT6PXtaagRJHACo', '2019-01-20 14:07:55', '2019-01-20 14:07:55', NULL, '#모모#지효.jpeg', 'image/jpeg', 66708); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('7WDdAxmeMJG4wWkdRr8jUj', '2019-01-20 14:08:28', '2019-01-20 14:08:28', NULL, '#모모.jpg', 'image/jpeg', 131071); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('82BpyASKAhBpDDAmPg3XAR', '2019-01-20 14:09:14', '2019-01-20 14:09:14', NULL, '#사나#미나#모모.jpg', 'image/jpeg', 309639); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('qAroeqmBg4hPEEuDVzTsi', '2019-01-20 14:09:47', '2019-01-20 14:09:47', NULL, '#쯔위.jpg', 'image/jpeg', 110325); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('uPKAQJgvxDNoDZdUr5SxhR', '2019-01-20 14:11:01', '2019-01-20 14:11:01', NULL, '1476429558.jpg', 'image/jpeg', 79052); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('uF1qEbmxZJ8mvPWwZDNpuQ', '2019-01-20 14:11:01', '2019-01-20 14:11:01', NULL, '1476147384 (2).jpg', 'image/jpeg', 51624); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('h2Lg497sMRXb9VHTSERRWR', '2019-01-20 14:11:01', '2019-01-20 14:11:01', NULL, 'Cp1VAjmUAAAkBkr.jpg', 'image/jpeg', 70762); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('snckkf4yQG32TsLvgVBQVT', '2019-01-20 14:11:01', '2019-01-20 14:11:01', NULL, '14482114_1098313513597223_7939238220498206720_n.jpg', 'image/gif', 750194); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('fXBEgH8juB2WzpzP5yQmRb', '2019-01-20 14:12:01', '2019-01-20 14:12:01', NULL, '99593B4E5A2F799B26.jpeg', 'image/jpeg', 103202); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('PcjNfj6Ra6bMxstNHNRGH', '2019-01-20 14:12:01', '2019-01-20 14:12:01', NULL, '995FAA4E5A2F799B24.jpeg', 'image/jpeg', 95482); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('utJRxdfg9wvgEvufWwGtTu', '2019-01-20 14:13:41', '2019-01-20 14:13:41', NULL, '#지효#정연#채연#미나#사나.JPG', 'image/jpeg', 74897); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('xdSoEqmZ7zUqfHKB9RmwSQ', '2019-01-20 14:13:41', '2019-01-20 14:13:41', NULL, '#다현#모모#나연#쯔위.PNG', 'image/png', 134683); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('5hYwk8uWt9RvxnHsTiE6hm', '2019-01-20 14:13:41', '2019-01-20 14:13:41', NULL, '#다현#모모#나연#쯔위.PNG', 'image/gif', 248711); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('qHDpPmHU5bAMHtZxu18RQF', '2019-01-20 14:15:47', '2019-01-20 14:15:47', NULL, '#무라세사에.jpg', 'image/jpeg', 150599); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('jWc89h53EQmdpLqQhEjMZK', '2019-01-20 14:15:47', '2019-01-20 14:15:47', NULL, '#사쿠라.jpg', 'image/jpeg', 188503); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('bCFkovnBGKcjsNHMCp7zZ3', '2019-01-20 14:15:47', '2019-01-20 14:15:47', NULL, '#고토모에.jpg', 'image/jpeg', 221874); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('sjCZXE7fwFnQbmnL6YZwEG', '2019-01-20 14:15:47', '2019-01-20 14:15:47', NULL, '#시타오미우.jpg', 'image/jpeg', 225251); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('qYKi12ZzTTc1ntjwomovMH', '2019-01-20 14:15:47', '2019-01-20 14:15:47', NULL, '#미야와키사쿠라.jpg', 'image/jpeg', 237117); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('epuAyA14JamFEUYmeEmeyr', '2019-01-20 14:15:47', '2019-01-20 14:15:47', NULL, '#아사히나나미.jpg', 'image/jpeg', 165833); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('dNRMRgcpvx6NWDSHamiAQP', '2019-01-20 14:15:47', '2019-01-20 14:15:47', NULL, '#최예나.jpg', 'image/jpeg', 248873); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('goPiUz5qAjfQhBnYF3ZofU', '2019-01-20 14:15:47', '2019-01-20 14:15:47', NULL, '#타케우치미유.jpg', 'image/jpeg', 212653); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('vasDjwnkuv5MkmQLLVCWsf', '2019-01-20 14:15:47', '2019-01-20 14:15:47', NULL, '#야부키나코.jpg', 'image/jpeg', 195758); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('vJ2acJSthHCTY26vSwf53t', '2019-01-20 14:15:47', '2019-01-20 14:15:47', NULL, '#고토모에.jpg', 'image/gif', 1542735); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('wEE6ZWmFFh7Lxc9hzFTsuX', '2019-01-20 14:16:28', '2019-01-20 14:16:28', NULL, 'a3e52719ce4576f24c61117eb66d59ca.jpg', 'image/jpeg', 83563); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('mhjAYR5huiQBG2BT3uYNab', '2019-01-20 14:17:14', '2019-01-20 14:17:14', NULL, 'Driju6NUcAAaCJ7.jpg', 'image/jpeg', 104388); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('68ekAgNLoiQCRBQWgPzxP5', '2019-01-20 14:17:55', '2019-01-20 14:17:55', NULL, 'thumb-qrikmbwei_1541809092_1541808880281267_600x2392.jpg', 'image/jpeg', 160392); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('wtTzJeNHwosqpTgWQTAKFd', '2019-01-20 14:18:56', '2019-01-20 14:18:56', NULL, 'b6c3c273d1d50f24a20778f3a6204b67.jpg', 'image/jpeg', 57899); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('sdQBdEN7C96RWnXPKNDvJA', '2019-01-20 14:19:33', '2019-01-20 14:19:33', NULL, 'd669ac5e3fab7d4711e24ec3f3b3d3aa.jpg', 'image/jpeg', 53981); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('jfpeymzPRG4EkFKfzg1PX9', '2019-01-20 14:20:17', '2019-01-20 14:20:17', NULL, '#장원영.jpg', 'image/jpeg', 43348); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('tJQdHSEyJoBM4T71ez86yk', '2019-01-20 14:20:48', '2019-01-20 14:20:48', NULL, '2aee2549d0518c0eba5eeef9fdb93d9e.jpg', 'image/jpeg', 48014); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('upjx2XAhY7A3TP2VjC2Aem', '2019-01-20 14:21:25', '2019-01-20 14:21:25', NULL, 'DiJvAB5UwAANmZ1.jpg', 'image/jpeg', 109903); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('uCzFeWxCRdFDyRJDzZkH3h', '2019-01-20 14:22:01', '2019-01-20 14:22:01', NULL, 'DrTkDrAVsAAdv5r.jpg', 'image/jpeg', 100557); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('hX1uuZmWxpUPnTZcsSPyv6', '2019-01-20 14:22:38', '2019-01-20 14:22:38', NULL, 'f241bb30b53270626d6594df280de6d1.jpg', 'image/jpeg', 56286); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('gkhbfYAJy8sbc4PA2YETJo', '2019-01-20 14:24:01', '2019-01-20 14:24:01', NULL, 'DrprqLKUUAAQFnd.jpg', 'image/jpeg', 203965); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('xu2jXsPT4Qy54gFj7bpwKA', '2019-01-20 14:24:36', '2019-01-20 14:24:36', NULL, 'Dpj7N8uU4AAzoFi.jpg', 'image/jpeg', 117573); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('nHTZXwXjcsPkjPMTfGQCA', '2019-01-20 14:25:48', '2019-01-20 14:25:48', NULL, '#권은비.jpg', 'image/jpeg', 40411); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('5HgEYopyjXivTetmykTnnK', '2019-01-20 14:25:48', '2019-01-20 14:25:48', NULL, '#최예나.jpg', 'image/jpeg', 38921); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('5Fmyc7xf45VkB3tFFaZteY', '2019-01-20 14:25:48', '2019-01-20 14:25:48', NULL, '#김민주.jpg', 'image/jpeg', 35816); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('nNgDo1H8HcYkuMQsJaLSpi', '2019-01-20 14:25:48', '2019-01-20 14:25:48', NULL, '#히토미.jpg', 'image/jpeg', 43055); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('b2gbJQtB4T1ZYtxcvVyf1', '2019-01-20 14:25:48', '2019-01-20 14:25:48', NULL, '#김채원.jpg', 'image/jpeg', 42326); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('8KXLzvW1yYZjXFE6qpHaud', '2019-01-20 14:25:48', '2019-01-20 14:25:48', NULL, '#야부키나코.jpg', 'image/gif', 253982); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('e7QXRdV6Js4PbYFfnztVy6', '2019-01-20 14:25:48', '2019-01-20 14:25:48', NULL, '#야부키나코.jpg', 'image/jpeg', 49882); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('8ULs8N9C8YCSHxojXtr5NB', '2019-01-20 14:25:48', '2019-01-20 14:25:48', NULL, '#사쿠라#이채연.jpg', 'image/jpeg', 67688); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('uf3pgiUt1zGsfeVo3EF7yt', '2019-01-20 14:25:48', '2019-01-20 14:25:48', NULL, '#조유리.jpg', 'image/jpeg', 53284); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('9jAtmPkGiET1m4E18824AR', '2019-01-20 14:26:41', '2019-01-20 14:26:41', NULL, '3ada211163979f5a9a3ae50425a3de77.jpg', 'image/jpeg', 134131); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('qYNTGeZzVg6zur1QcAxyJS', '2019-01-20 14:27:12', '2019-01-20 14:27:12', NULL, '#김민주.jpg', 'image/jpeg', 35816); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('iwCJMEJ68nTrP2wzegxhmK', '2019-01-20 14:27:12', '2019-01-20 14:27:12', NULL, '#권은비.jpg', 'image/jpeg', 40411); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('xxgX7RvBURP7dNsbvXWc3V', '2019-01-20 14:27:12', '2019-01-20 14:27:12', NULL, '#김채원.jpg', 'image/jpeg', 42326); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('drUZD96bif7xeiHS8xuKuj', '2019-01-20 14:27:12', '2019-01-20 14:27:12', NULL, '#히토미.jpg', 'image/jpeg', 43055); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('9HziwGsMHpVV9kVjYpDReE', '2019-01-20 14:27:12', '2019-01-20 14:27:12', NULL, '#사쿠라#이채연.jpg', 'image/jpeg', 67688); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('bwYfhLWCbW3rjQBwJ5BoDi', '2019-01-20 14:27:12', '2019-01-20 14:27:12', NULL, '#야부키나코.jpg', 'image/gif', 253982); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('ttozusABk4KSN72NBpCo9E', '2019-01-20 14:27:12', '2019-01-20 14:27:12', NULL, '#최예나.jpg', 'image/jpeg', 38921); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('fyCuGajfHwRGHiGzkQr9ay', '2019-01-20 14:27:12', '2019-01-20 14:27:12', NULL, '#조유리.jpg', 'image/jpeg', 53284); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('bFdA2m4ye4fxudPezJTsNv', '2019-01-20 14:27:12', '2019-01-20 14:27:12', NULL, '#야부키나코.jpg', 'image/jpeg', 49882); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('7aXQtsTsxoj2qVAvMUwg3K', '2019-01-20 14:27:47', '2019-01-20 14:27:47', NULL, 'af31576102a6ec5534a8224572cd9cac.jpg', 'image/jpeg', 87533); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('5xovnSWEVDeadTxufHHz8Q', '2019-01-20 14:28:26', '2019-01-20 14:28:26', NULL, 'DovuOQSU0AAdTbS.jpg', 'image/jpeg', 106227); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('f4dGuCPSqs1fiCwhDaskmB', '2019-01-20 14:30:15', '2019-01-20 14:30:15', NULL, 'append_141101851505c32a2ad51d7d.jpg', 'image/jpeg', 91921); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('v6v1xKKT9SxffeuNknTTHC', '2019-01-20 14:30:15', '2019-01-20 14:30:15', NULL, 'SdF55xz.jpg', 'image/gif', 292187); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('ah8ajEJrwiDYED6iVMiptT', '2019-01-20 14:33:12', '2019-01-20 14:33:12', NULL, '01.jpg', 'image/gif', 144688); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('mAcqXsfHWwBjERZHZ1BYvy', '2019-01-20 14:33:12', '2019-01-20 14:33:12', NULL, 'append_141101852465c329c17f01d0.jpg', 'image/jpeg', 478469); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('b1UrPqvcy6CoEMdVfHz2eh', '2019-01-20 14:34:15', '2019-01-20 14:34:15', NULL, 'append_141101852465c329c786d4a6.jpg', 'image/jpeg', 202809); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('pMisd8E9PgqkCrQtLqGCGi', '2019-01-20 14:34:15', '2019-01-20 14:34:15', NULL, '01.png', 'image/gif', 180116); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('vXDXQGeUCzjUi2tUboaHWf', '2019-01-20 14:35:19', '2019-01-20 14:35:19', NULL, '01.png', 'image/png', 145812); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('tSyiFMzxQxjowyyLHFeZS5', '2019-01-20 14:35:20', '2019-01-20 14:35:20', NULL, '02.png', 'image/png', 619633); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('tZHmcWN6dBnDYGjr5aJUHR', '2019-01-20 14:35:20', '2019-01-20 14:35:20', NULL, '04.png', 'image/png', 550411); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('mbio6p29HzQj6STENjD2fP', '2019-01-20 14:35:20', '2019-01-20 14:35:20', NULL, '05.png', 'image/png', 545091); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('j3CoPs1VQK92ZXKS1asihF', '2019-01-20 14:35:20', '2019-01-20 14:35:20', NULL, '03.png', 'image/png', 572005); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('pDjw582YUkaomhg3qMaGzb', '2019-01-20 14:35:20', '2019-01-20 14:35:20', NULL, '06.png', 'image/png', 598139); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('nstj92eswPzxpGiygdRVx5', '2019-01-20 14:35:20', '2019-01-20 14:35:20', NULL, '07.png', 'image/png', 576774); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('vENr4C19cBeZrmb6yzE8JJ', '2019-01-20 14:36:07', '2019-01-20 14:36:07', NULL, '02.png', 'image/png', 452962); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('ssNp1ZxMZiYVeYSrg9G7fb', '2019-01-20 14:36:07', '2019-01-20 14:36:07', NULL, '03.png', 'image/png', 520042); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('9j88A9cH79tMWAJFFRtafk', '2019-01-20 14:36:07', '2019-01-20 14:36:07', NULL, '06.png', 'image/png', 496468); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('qXgkqPTvbXmLEKfRQWWgc3', '2019-01-20 14:36:07', '2019-01-20 14:36:07', NULL, '01.png', 'image/png', 495782); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('2BPSwJo3xmkhKB9D7Mgnsh', '2019-01-20 14:36:07', '2019-01-20 14:36:07', NULL, '04.png', 'image/png', 523505); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('oaEVR4pYHdJ472npM1C2uD', '2019-01-20 14:36:07', '2019-01-20 14:36:07', NULL, '05.png', 'image/png', 551952); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('qcNRMkQeKkKhhd61pPQbNS', '2019-01-20 14:36:07', '2019-01-20 14:36:07', NULL, '07.png', 'image/png', 524082); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('GvXzzkvH5Vfjhw5BH7Au', '2019-01-20 14:36:07', '2019-01-20 14:36:07', NULL, '08.png', 'image/png', 542382); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('fZnf5Fc12bbBRdCoVBadoq', '2019-01-20 14:36:07', '2019-01-20 14:36:07', NULL, '10.png', 'image/png', 498582); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('4fTvse7VtQq56Huq3X788C', '2019-01-20 14:36:07', '2019-01-20 14:36:07', NULL, '09.png', 'image/png', 523524); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('vJa2wQ2PcKUKJ4uFwywhaR', '2019-01-20 14:36:08', '2019-01-20 14:36:08', NULL, '11.jpg', 'image/jpeg', 3656171); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('mALkzjs71P9YTvf1AJgZGK', '2019-01-20 14:36:45', '2019-01-20 14:36:45', NULL, '01.png', 'image/png', 1088940); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('7j36rZg4H1wKUZRjViwpP6', '2019-01-20 14:36:45', '2019-01-20 14:36:45', NULL, '02.png', 'image/png', 1486335); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('2y1Ve4QMshTctPswE9ncWb', '2019-01-20 14:37:44', '2019-01-20 14:37:44', NULL, '01.png', 'image/gif', 78570); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('mXgJXnUvCEMZEaKQGdehV4', '2019-01-20 14:37:44', '2019-01-20 14:37:44', NULL, '04.png', 'image/png', 183897); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('9ctyvaMXf1SrSd6frHJczk', '2019-01-20 14:37:44', '2019-01-20 14:37:44', NULL, '02.png', 'image/png', 216993); +INSERT INTO attachments (id, createDate, updateDate, deleteDate, name, mimeType, size) VALUES ('xmy6q2V4YGegp1Z6nTHgye', '2019-01-20 14:37:44', '2019-01-20 14:37:44', NULL, '03.png', 'image/png', 220739); + + +-- Table: user +INSERT INTO user (id, createDate, updateDate, deleteDate, nickname, followerCount, followingCount, ageLimit, displayLanguage, articlePublicYN, introduction, email, favorShowedYN, profileImageAttachmentsId, backgroundImageAttachmentsId) VALUES ('qa1wJMzpVVTsWQouF76HgE', '2019-01-20 13:28:43', '2019-01-20 13:37:47', NULL, '동경유유', NULL, NULL, 0, 'KO', 0, NULL, 'chaiwooseok@gmail.com', 1, 'o3wuEYzRM2Mu4SN3e5tjpr', 'wow9TiLWKwRicdeXnELnyG'); +INSERT INTO user (id, createDate, updateDate, deleteDate, nickname, followerCount, followingCount, ageLimit, displayLanguage, articlePublicYN, introduction, email, favorShowedYN, profileImageAttachmentsId, backgroundImageAttachmentsId) VALUES ('4FEtyVhf6mnZYWXxBS6QyN', '2019-01-20 13:42:39', '2019-01-20 13:43:22', NULL, 'V3알바중', NULL, NULL, 0, 'KO', 0, '히히히', NULL, 1, '8L4GTaP7qrPhiAU2W2fhmS', 'sv8uJiqL5cFNG1u2ZmT5KD'); +INSERT INTO user (id, createDate, updateDate, deleteDate, nickname, followerCount, followingCount, ageLimit, displayLanguage, articlePublicYN, introduction, email, favorShowedYN, profileImageAttachmentsId, backgroundImageAttachmentsId) VALUES ('5cABtQnwV4k6aTVoFcDwyK', '2019-01-20 13:49:11', '2019-01-20 13:52:55', NULL, '우사기', NULL, NULL, 0, 'KO', 0, NULL, NULL, 1, 'dcQEXcbhaoyp2Xbz8mQPwn', 'SLJk95UWeRsRE8ccPA5Lq'); + +-- Table: oauth +INSERT INTO oauth (id, createDate, updateDate, deleteDate, oauthId, accessToken, refreshToken, oauthProvider, oauthUse, profile, userId) VALUES ('pHaDrFMbCjSmUDdJMEKh7V', '2019-01-20 13:28:43', '2019-01-20 13:28:43', NULL, 'f174ae31-b50b-473b-8a94-a2bf65a1021a', 'c38bd553-2e91-4fc3-8f4f-1fa6c4b32bcd', NULL, 'ONEALL', 'REGI', '{"id":"f174ae31-b50b-473b-8a94-a2bf65a1021a","displayName":"최 우석","provider":"google","_raw":"{\"identity_token\":\"c38bd553-2e91-4fc3-8f4f-1fa6c4b32bcd\",\"date_creation\":\"Mon, 07 Jan 2019 08:59:32 +0100\",\"date_last_update\":\"Sun, 20 Jan 2019 14:28:41 +0100\",\"provider\":\"google\",\"provider_identity_uid\":\"PIU8FC5A5C5B4C027DA4A332D0872A66F3F\",\"source\":{\"name\":\"Google\",\"key\":\"google\",\"access_token\":{\"key\":\"ya29.GlyXBpvwbVMlsKtelG5JaR7NokrmMvQ9uZqtAGJf7dy6nDNftaLJdVFHZLm3RAEEjzc4bstuwLOIQfQs1_cmykWeFlcnLDfXjVhVkUn93F2D2Ja7Qxyswbkc7xc04Q\",\"date_expiration\":\"01/20/2019 15:28:40\"},\"refresh_token\":{\"key\":\"1/yXt9HOXBV1h4kpdDB3KTm4nIxWpc1oKJEgvksJxMLCg\"}},\"id\":\"https://plus.google.com/110488002385183093075\",\"displayName\":\"최 우석\",\"name\":{\"formatted\":\"최 우석\",\"givenName\":\"최\",\"familyName\":\"우석\"},\"preferredUsername\":\"우석최\",\"profileUrl\":\"https://plus.google.com/110488002385183093075\",\"gender\":\"male\",\"loginLocation\":{\"ip_address\":\"54.180.12.41\",\"country\":{\"short\":\"United States\",\"long\":\"United States of America\",\"iso_alpha2\":\"US\"}},\"emails\":[{\"value\":\"chaiwooseok@gmail.com\",\"is_verified\":true}],\"urls\":[{\"value\":\"https://plus.google.com/110488002385183093075\",\"type\":\"profile\"}],\"accounts\":[{\"domain\":\"google.com\",\"userid\":\"110488002385183093075\"}],\"locales\":[{\"value\":\"ko\",\"description\":\"Korean\"}],\"browser\":{\"agent\":\"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36\",\"type\":\"Chrome\",\"version\":{\"major\":\"71\",\"full\":\"71.0.3578.98\"},\"platform\":{\"name\":\"Linux\",\"type\":\"Desktop\"}}}","_json":{"identity_token":"c38bd553-2e91-4fc3-8f4f-1fa6c4b32bcd","date_creation":"Mon, 07 Jan 2019 08:59:32 +0100","date_last_update":"Sun, 20 Jan 2019 14:28:41 +0100","provider":"google","provider_identity_uid":"PIU8FC5A5C5B4C027DA4A332D0872A66F3F","source":{"name":"Google","key":"google","access_token":{"key":"ya29.GlyXBpvwbVMlsKtelG5JaR7NokrmMvQ9uZqtAGJf7dy6nDNftaLJdVFHZLm3RAEEjzc4bstuwLOIQfQs1_cmykWeFlcnLDfXjVhVkUn93F2D2Ja7Qxyswbkc7xc04Q","date_expiration":"01/20/2019 15:28:40"},"refresh_token":{"key":"1/yXt9HOXBV1h4kpdDB3KTm4nIxWpc1oKJEgvksJxMLCg"}},"id":"https://plus.google.com/110488002385183093075","displayName":"최 우석","name":{"formatted":"최 우석","givenName":"최","familyName":"우석"},"preferredUsername":"우석최","profileUrl":"https://plus.google.com/110488002385183093075","gender":"male","loginLocation":{"ip_address":"54.180.12.41","country":{"short":"United States","long":"United States of America","iso_alpha2":"US"}},"emails":[{"value":"chaiwooseok@gmail.com","is_verified":true}],"urls":[{"value":"https://plus.google.com/110488002385183093075","type":"profile"}],"accounts":[{"domain":"google.com","userid":"110488002385183093075"}],"locales":[{"value":"ko","description":"Korean"}],"browser":{"agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36","type":"Chrome","version":{"major":"71","full":"71.0.3578.98"},"platform":{"name":"Linux","type":"Desktop"}}},"name":{"familyName":"우석","givenName":"최"},"emails":[{"value":"chaiwooseok@gmail.com"}]}', 'qa1wJMzpVVTsWQouF76HgE'); +INSERT INTO oauth (id, createDate, updateDate, deleteDate, oauthId, accessToken, refreshToken, oauthProvider, oauthUse, profile, userId) VALUES ('oAX48kzjYkgZawYUdGJzWc', '2019-01-20 13:42:39', '2019-01-20 13:42:39', NULL, '1003304731', 'hEhbeNtMJ20LdXNp8SFNFbR-4E3xB48dSs13Qwo8BhkAAAFoa3-5OQ', 'JvKFUDUzt3Ak3yEZgrbvwVMNYp48kh71t9RDUQo8BhkAAAFoa3-5NA', 'KAKAO', 'REGI', '{"id":1003304731,"properties":{"profile_image":"http://k.kakaocdn.net/dn/bJl5Yk/btqrHXYrQ34/KKhrEz9qm0uuoZAnRxl5Y0/profile_640x640s.jpg","nickname":"최우석","thumbnail_image":"http://k.kakaocdn.net/dn/bJl5Yk/btqrHXYrQ34/KKhrEz9qm0uuoZAnRxl5Y0/profile_110x110c.jpg"}}', '4FEtyVhf6mnZYWXxBS6QyN'); +INSERT INTO oauth (id, createDate, updateDate, deleteDate, oauthId, accessToken, refreshToken, oauthProvider, oauthUse, profile, userId) VALUES ('t45aVmNNFeWsLCZTM9yHi2', '2019-01-20 13:49:11', '2019-01-20 13:49:11', NULL, 'c188488b-773e-480a-99b4-74e6d0a5a792', 'dea484de-4c49-4514-9005-352a1dab5d8c', NULL, 'ONEALL', 'REGI', '{"id":"c188488b-773e-480a-99b4-74e6d0a5a792","displayName":"우사기","provider":"instagram","_raw":"{\"identity_token\":\"dea484de-4c49-4514-9005-352a1dab5d8c\",\"date_creation\":\"Tue, 15 Jan 2019 05:18:01 +0100\",\"date_last_update\":\"Sun, 20 Jan 2019 14:49:09 +0100\",\"provider\":\"instagram\",\"provider_identity_uid\":\"PIU7D35AB6D396EDFE1C220492125DC3E8A\",\"source\":{\"name\":\"Instagram\",\"key\":\"instagram\",\"access_token\":{\"key\":\"2035919622.bfd96e6.fc2b2ea12c174f1a84fc0b0e685656c1\"}},\"id\":\"https://api.instagram.com/users/2035919622\",\"displayName\":\"우사기\",\"name\":{\"formatted\":\"우사기\"},\"preferredUsername\":\"cws.tokyo\",\"profileUrl\":\"http://instagram.com/cws.tokyo/\",\"thumbnailUrl\":\"https://scontent.cdninstagram.com/vp/bcc13892015a8e830f909c77e3b39544/5CC76CE3/t51.2885-19/s150x150/12301293_1636127959983371_349403343_a.jpg?_nc_ht=scontent.cdninstagram.com\",\"pictureUrl\":\"https://scontent.cdninstagram.com/vp/bcc13892015a8e830f909c77e3b39544/5CC76CE3/t51.2885-19/s150x150/12301293_1636127959983371_349403343_a.jpg?_nc_ht=scontent.cdninstagram.com\",\"loginLocation\":{\"ip_address\":\"165.243.5.20\",\"country\":{\"short\":\"South Korea\",\"long\":\"Republic of Korea\",\"iso_alpha2\":\"KR\"}},\"aboutMe\":\"부산 동경 설을 돌아다니며 살고 있는 직딩...\\n어느덧 아저씨 반열 쿨럭\",\"urls\":[{\"value\":\"http://instagram.com/cws.tokyo/\",\"type\":\"profile\"}],\"accounts\":[{\"domain\":\"instagram.com\",\"userid\":\"2035919622\"}],\"photos\":[{\"value\":\"https://scontent.cdninstagram.com/vp/bcc13892015a8e830f909c77e3b39544/5CC76CE3/t51.2885-19/s150x150/12301293_1636127959983371_349403343_a.jpg?_nc_ht=scontent.cdninstagram.com\",\"size\":\"4:M\"}],\"statistics\":[{\"followers\":\"62\"},{\"followings\":\"993\"},{\"medias\":\"6\"}],\"browser\":{\"agent\":\"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36\",\"type\":\"Chrome\",\"version\":{\"major\":\"71\",\"full\":\"71.0.3578.98\"},\"platform\":{\"name\":\"Windows\",\"type\":\"Desktop\"}}}","_json":{"identity_token":"dea484de-4c49-4514-9005-352a1dab5d8c","date_creation":"Tue, 15 Jan 2019 05:18:01 +0100","date_last_update":"Sun, 20 Jan 2019 14:49:09 +0100","provider":"instagram","provider_identity_uid":"PIU7D35AB6D396EDFE1C220492125DC3E8A","source":{"name":"Instagram","key":"instagram","access_token":{"key":"2035919622.bfd96e6.fc2b2ea12c174f1a84fc0b0e685656c1"}},"id":"https://api.instagram.com/users/2035919622","displayName":"우사기","name":{"formatted":"우사기"},"preferredUsername":"cws.tokyo","profileUrl":"http://instagram.com/cws.tokyo/","thumbnailUrl":"https://scontent.cdninstagram.com/vp/bcc13892015a8e830f909c77e3b39544/5CC76CE3/t51.2885-19/s150x150/12301293_1636127959983371_349403343_a.jpg?_nc_ht=scontent.cdninstagram.com","pictureUrl":"https://scontent.cdninstagram.com/vp/bcc13892015a8e830f909c77e3b39544/5CC76CE3/t51.2885-19/s150x150/12301293_1636127959983371_349403343_a.jpg?_nc_ht=scontent.cdninstagram.com","loginLocation":{"ip_address":"165.243.5.20","country":{"short":"South Korea","long":"Republic of Korea","iso_alpha2":"KR"}},"aboutMe":"부산 동경 설을 돌아다니며 살고 있는 직딩...\n어느덧 아저씨 반열 쿨럭","urls":[{"value":"http://instagram.com/cws.tokyo/","type":"profile"}],"accounts":[{"domain":"instagram.com","userid":"2035919622"}],"photos":[{"value":"https://scontent.cdninstagram.com/vp/bcc13892015a8e830f909c77e3b39544/5CC76CE3/t51.2885-19/s150x150/12301293_1636127959983371_349403343_a.jpg?_nc_ht=scontent.cdninstagram.com","size":"4:M"}],"statistics":[{"followers":"62"},{"followings":"993"},{"medias":"6"}],"browser":{"agent":"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36","type":"Chrome","version":{"major":"71","full":"71.0.3578.98"},"platform":{"name":"Windows","type":"Desktop"}}},"name":{}}', '5cABtQnwV4k6aTVoFcDwyK'); + + +-- Table: user_analysis_favor_tag +INSERT INTO user_analysis_favor_tag (id, createDate, updateDate, deleteDate, imageName, show) VALUES ('kMP9DqKYEUTxBMc9bDGZbR', '2019-01-21 00:44:12', '2019-01-21 00:44:12', NULL, 'BL.jpg', 1); +INSERT INTO user_analysis_favor_tag (id, createDate, updateDate, deleteDate, imageName, show) VALUES ('oLxgvzjYScqeQgSFzgAVow', '2019-01-21 00:44:12', '2019-01-21 00:44:12', NULL, 'GL.jpg', 1); +INSERT INTO user_analysis_favor_tag (id, createDate, updateDate, deleteDate, imageName, show) VALUES ('7RFmmh7AR24nxkymdy97oe', '2019-01-21 00:44:12', '2019-01-21 00:44:12', NULL, 'SF.jpg', 1); +INSERT INTO user_analysis_favor_tag (id, createDate, updateDate, deleteDate, imageName, show) VALUES ('4kULUVf5kDfKZbsjDfqGP4', '2019-01-21 00:44:12', '2019-01-21 00:44:12', NULL, '개그.jpg', 1); +INSERT INTO user_analysis_favor_tag (id, createDate, updateDate, deleteDate, imageName, show) VALUES ('dzA8nZCqcm76SaXiWnfrhp', '2019-01-21 00:44:12', '2019-01-21 00:44:12', NULL, '도박.jpg', 1); +INSERT INTO user_analysis_favor_tag (id, createDate, updateDate, deleteDate, imageName, show) VALUES ('8ZMk8fbv7tmMhxbmyK6WDY', '2019-01-21 00:44:12', '2019-01-21 00:44:12', NULL, '동물.png', 1); +INSERT INTO user_analysis_favor_tag (id, createDate, updateDate, deleteDate, imageName, show) VALUES ('jG916vsMhCrH8aGpLqKTUC', '2019-01-21 00:44:12', '2019-01-21 00:44:12', NULL, '드라마.png', 1); +INSERT INTO user_analysis_favor_tag (id, createDate, updateDate, deleteDate, imageName, show) VALUES ('8S6ZmwUPhyfa7WbPC1H8U7', '2019-01-21 00:44:13', '2019-01-21 00:44:13', NULL, '모험_판타지.png', 1); +INSERT INTO user_analysis_favor_tag (id, createDate, updateDate, deleteDate, imageName, show) VALUES ('dTbWc3W4Me5Lz6aNNEQ77w', '2019-01-21 00:44:13', '2019-01-21 00:44:13', NULL, '미식.png', 1); +INSERT INTO user_analysis_favor_tag (id, createDate, updateDate, deleteDate, imageName, show) VALUES ('rRh6SWQRgbHGUVyVgCQYEX', '2019-01-21 00:44:13', '2019-01-21 00:44:13', NULL, '베틀_액션.png', 1); +INSERT INTO user_analysis_favor_tag (id, createDate, updateDate, deleteDate, imageName, show) VALUES ('76L46FhcDMNXF1B5Cp695E', '2019-01-21 00:44:13', '2019-01-21 00:44:13', NULL, '비즈니스.png', 1); +INSERT INTO user_analysis_favor_tag (id, createDate, updateDate, deleteDate, imageName, show) VALUES ('ooRdydQFtmk72tT67wt58Q', '2019-01-21 00:44:13', '2019-01-21 00:44:13', NULL, '세기말_종말.png', 1); +INSERT INTO user_analysis_favor_tag (id, createDate, updateDate, deleteDate, imageName, show) VALUES ('wm7QNPFMwVLiuFpGv4jqx7', '2019-01-21 00:44:13', '2019-01-21 00:44:13', NULL, '스포츠.jpg', 1); +INSERT INTO user_analysis_favor_tag (id, createDate, updateDate, deleteDate, imageName, show) VALUES ('gc3CqGsLsEtGkRJUyVrJbY', '2019-01-21 00:44:13', '2019-01-21 00:44:13', NULL, '심쿵.png', 1); +INSERT INTO user_analysis_favor_tag (id, createDate, updateDate, deleteDate, imageName, show) VALUES ('PCq2hi8prf4aDsjYnj1CR', '2019-01-21 00:44:13', '2019-01-21 00:44:13', NULL, '아이돌.jpg', 1); +INSERT INTO user_analysis_favor_tag (id, createDate, updateDate, deleteDate, imageName, show) VALUES ('w5dmX6YXhZtyfWzZSKtm72', '2019-01-21 00:44:13', '2019-01-21 00:44:13', NULL, '역사_시대극.jpg', 1); +INSERT INTO user_analysis_favor_tag (id, createDate, updateDate, deleteDate, imageName, show) VALUES ('77xreQyZSafZkTAtBtmGAE', '2019-01-21 00:44:14', '2019-01-21 00:44:14', NULL, '연애_사랑.jpg', 1); +INSERT INTO user_analysis_favor_tag (id, createDate, updateDate, deleteDate, imageName, show) VALUES ('6KD3Qemnpqek3DZRTjqHMy', '2019-01-21 00:44:14', '2019-01-21 00:44:14', NULL, '이세계.jpg', 1); +INSERT INTO user_analysis_favor_tag (id, createDate, updateDate, deleteDate, imageName, show) VALUES ('oXdLoR1p879BxFyNHeFeUM', '2019-01-21 00:44:14', '2019-01-21 00:44:14', NULL, '일상.png', 1); +INSERT INTO user_analysis_favor_tag (id, createDate, updateDate, deleteDate, imageName, show) VALUES ('sRoXWJUYsRZbpbyTN3YFTR', '2019-01-21 00:44:14', '2019-01-21 00:44:14', NULL, '호러.jpg', 1); + + +-- Table: article_series +INSERT INTO article_series (id, createDate, updateDate, deleteDate, title, description, type, userId) VALUES ('75Y4qjTU1bGpGFWJXhMyzE', '2019-01-20 14:04:22', '2019-01-20 14:04:22', NULL, 'WANNAONE', '시리즈갑니다.', 'Cartoons', '4FEtyVhf6mnZYWXxBS6QyN'); +INSERT INTO article_series (id, createDate, updateDate, deleteDate, title, description, type, userId) VALUES ('tiCFn94Kmsc7Vm6Rewf3H4', '2019-01-20 14:07:46', '2019-01-20 14:07:46', NULL, 'TWICE', '트와이스 팬아트', 'Cartoons', '5cABtQnwV4k6aTVoFcDwyK'); +INSERT INTO article_series (id, createDate, updateDate, deleteDate, title, description, type, userId) VALUES ('bve7Pn9xeyK5VFdVzMDoPe', '2019-01-20 14:16:56', '2019-01-20 14:16:56', NULL, '사쿠라', '', 'Cartoons', 'qa1wJMzpVVTsWQouF76HgE'); +INSERT INTO article_series (id, createDate, updateDate, deleteDate, title, description, type, userId) VALUES ('tau6Dw8sQ7keogu7ztSC3F', '2019-01-20 14:18:38', '2019-01-20 14:18:38', NULL, '안유진', '', 'Cartoons', 'qa1wJMzpVVTsWQouF76HgE'); +INSERT INTO article_series (id, createDate, updateDate, deleteDate, title, description, type, userId) VALUES ('9ei5oAZAPyrWmaHgWrUKVv', '2019-01-20 14:20:02', '2019-01-20 14:20:02', NULL, '장원영', '', 'Cartoons', 'qa1wJMzpVVTsWQouF76HgE'); +INSERT INTO article_series (id, createDate, updateDate, deleteDate, title, description, type, userId) VALUES ('dtGAKQ6GUoE5MdR6hyt9WE', '2019-01-20 14:23:44', '2019-01-20 14:23:44', NULL, '아이즈원', '', 'Cartoons', '5cABtQnwV4k6aTVoFcDwyK'); +INSERT INTO article_series (id, createDate, updateDate, deleteDate, title, description, type, userId) VALUES ('v86iEv2Ti1hj7tPSFkzDdE', '2019-01-20 14:35:05', '2019-01-20 14:35:05', NULL, 'MIDNIGHT TO DOWN', '', 'Cartoons', '4FEtyVhf6mnZYWXxBS6QyN'); + +-- Table: article_tag +INSERT INTO article_tag (id, createDate, updateDate, deleteDate, tagName, tagCount, userId) VALUES ('5gaWMsGZ43WdzXeAaMj7uH', '2019-01-20 13:31:56', '2019-01-20 13:31:56', NULL, '모험', 0, NULL); +INSERT INTO article_tag (id, createDate, updateDate, deleteDate, tagName, tagCount, userId) VALUES ('6ok3iJuqN2zgMvVcdUFjom', '2019-01-20 13:31:56', '2019-01-20 13:31:56', NULL, '판타지', 0, NULL); +INSERT INTO article_tag (id, createDate, updateDate, deleteDate, tagName, tagCount, userId) VALUES ('8E5trRdsgSrjK5vZbcZ4xZ', '2019-01-20 13:31:56', '2019-01-20 13:31:56', NULL, '드라마', 0, NULL); +INSERT INTO article_tag (id, createDate, updateDate, deleteDate, tagName, tagCount, userId) VALUES ('4AptmmB3gsVGua7GRMZRDB', '2019-01-20 13:32:30', '2019-01-20 13:32:30', NULL, 'GL', 0, NULL); +INSERT INTO article_tag (id, createDate, updateDate, deleteDate, tagName, tagCount, userId) VALUES ('fWC7ggN2githpoTheCn1iY', '2019-01-20 13:33:10', '2019-01-20 13:33:10', NULL, '귀염', 0, NULL); +INSERT INTO article_tag (id, createDate, updateDate, deleteDate, tagName, tagCount, userId) VALUES ('ku6dVqR5UR9WnhrFn6Xaar', '2019-01-20 13:33:45', '2019-01-20 13:33:45', NULL, '아이돌', 0, NULL); +INSERT INTO article_tag (id, createDate, updateDate, deleteDate, tagName, tagCount, userId) VALUES ('a8QWzqra9pqhrYiZcCAUfU', '2019-01-20 13:34:18', '2019-01-20 13:34:18', NULL, '개그', 0, NULL); +INSERT INTO article_tag (id, createDate, updateDate, deleteDate, tagName, tagCount, userId) VALUES ('jCUmXSMAePXFvYkz6iWVBE', '2019-01-20 13:35:21', '2019-01-20 13:35:21', NULL, '역사', 0, NULL); +INSERT INTO article_tag (id, createDate, updateDate, deleteDate, tagName, tagCount, userId) VALUES ('qMCv5tAHLJF2ugKvrJRJGS', '2019-01-20 13:35:21', '2019-01-20 13:35:21', NULL, '시대극', 0, NULL); +INSERT INTO article_tag (id, createDate, updateDate, deleteDate, tagName, tagCount, userId) VALUES ('g2pnATRxziuYjkLgtgL5V1', '2019-01-20 13:36:01', '2019-01-20 13:36:01', NULL, '미소녀', 0, NULL); +INSERT INTO article_tag (id, createDate, updateDate, deleteDate, tagName, tagCount, userId) VALUES ('4kp1zgaqtgJN1iQaYaK62r', '2019-01-20 13:36:37', '2019-01-20 13:36:37', NULL, 'BL', 0, NULL); +INSERT INTO article_tag (id, createDate, updateDate, deleteDate, tagName, tagCount, userId) VALUES ('8rNvky5sdFJVDiLB7iLXfW', '2019-01-20 13:36:37', '2019-01-20 13:36:37', NULL, '심쿵', 0, NULL); +INSERT INTO article_tag (id, createDate, updateDate, deleteDate, tagName, tagCount, userId) VALUES ('seWs39EcaF2dJ2aBUVFCCN', '2019-01-20 13:37:34', '2019-01-20 13:37:34', NULL, '페러디', 0, NULL); +INSERT INTO article_tag (id, createDate, updateDate, deleteDate, tagName, tagCount, userId) VALUES ('vKHN7Wwnw48Gee34RfqErt', '2019-01-20 13:39:25', '2019-01-20 13:39:25', NULL, '미식', 0, NULL); +INSERT INTO article_tag (id, createDate, updateDate, deleteDate, tagName, tagCount, userId) VALUES ('2sfQs1qaLtSewon75S7kk9', '2019-01-20 13:43:59', '2019-01-20 13:43:59', NULL, '새해복', 0, NULL); +INSERT INTO article_tag (id, createDate, updateDate, deleteDate, tagName, tagCount, userId) VALUES ('9Nc8RBHq3egN2fjQ4driFN', '2019-01-20 13:45:13', '2019-01-20 13:45:13', NULL, '이쁘다', 0, NULL); +INSERT INTO article_tag (id, createDate, updateDate, deleteDate, tagName, tagCount, userId) VALUES ('9C8W1raUp1sMeejQDhNLw8', '2019-01-20 13:45:45', '2019-01-20 13:45:45', NULL, '돼지', 0, NULL); +INSERT INTO article_tag (id, createDate, updateDate, deleteDate, tagName, tagCount, userId) VALUES ('t69gnT45GZfsiDMZW5zSWV', '2019-01-20 13:46:24', '2019-01-20 13:46:24', NULL, '세기말', 0, NULL); +INSERT INTO article_tag (id, createDate, updateDate, deleteDate, tagName, tagCount, userId) VALUES ('isqfMJJzXeF3t9frdv8u8J', '2019-01-20 13:46:24', '2019-01-20 13:46:24', NULL, '종말', 0, NULL); +INSERT INTO article_tag (id, createDate, updateDate, deleteDate, tagName, tagCount, userId) VALUES ('bxeaubXweEJNLrjG9Mq33R', '2019-01-20 13:51:20', '2019-01-20 13:51:20', NULL, '팥타지', 0, NULL); +INSERT INTO article_tag (id, createDate, updateDate, deleteDate, tagName, tagCount, userId) VALUES ('8LonhL4vQAyaWU4yAC3QEm', '2019-01-20 13:51:20', '2019-01-20 13:51:20', NULL, '이세계', 0, NULL); +INSERT INTO article_tag (id, createDate, updateDate, deleteDate, tagName, tagCount, userId) VALUES ('t118FC5D3sXxR6Bo7pvzTw', '2019-01-20 13:52:31', '2019-01-20 13:52:31', NULL, '하츠네 미쿠', 0, NULL); +INSERT INTO article_tag (id, createDate, updateDate, deleteDate, tagName, tagCount, userId) VALUES ('6czEzqptzyvB2NUoCqCYhj', '2019-01-20 14:03:27', '2019-01-20 14:03:27', NULL, '일상', 0, NULL); +INSERT INTO article_tag (id, createDate, updateDate, deleteDate, tagName, tagCount, userId) VALUES ('wtctv46uWwdw17BtoAnzfV', '2019-01-20 14:07:55', '2019-01-20 14:07:55', NULL, '모모', 0, NULL); +INSERT INTO article_tag (id, createDate, updateDate, deleteDate, tagName, tagCount, userId) VALUES ('62A6ftEeEBaeJpF5ffjVPs', '2019-01-20 14:07:55', '2019-01-20 14:07:55', NULL, '지효', 0, NULL); +INSERT INTO article_tag (id, createDate, updateDate, deleteDate, tagName, tagCount, userId) VALUES ('pfWdVmUaKd9VrsY7xtKSg3', '2019-01-20 14:07:55', '2019-01-20 14:07:55', NULL, 'TWICE', 0, NULL); +INSERT INTO article_tag (id, createDate, updateDate, deleteDate, tagName, tagCount, userId) VALUES ('kic3DCBBDUP4P7ARgoLYfz', '2019-01-20 14:09:14', '2019-01-20 14:09:14', NULL, '사나', 0, NULL); +INSERT INTO article_tag (id, createDate, updateDate, deleteDate, tagName, tagCount, userId) VALUES ('oafS2avgJSZmK5jpKjLJiy', '2019-01-20 14:09:14', '2019-01-20 14:09:14', NULL, '미나', 0, NULL); +INSERT INTO article_tag (id, createDate, updateDate, deleteDate, tagName, tagCount, userId) VALUES ('8vqXMKzXSTkxUdXF4FxTk6', '2019-01-20 14:09:47', '2019-01-20 14:09:47', NULL, '쯔위', 0, NULL); +INSERT INTO article_tag (id, createDate, updateDate, deleteDate, tagName, tagCount, userId) VALUES ('4C7YJ4pcmc9uNcQJr5JHcT', '2019-01-20 14:15:47', '2019-01-20 14:15:47', NULL, '프듀48', 0, NULL); +INSERT INTO article_tag (id, createDate, updateDate, deleteDate, tagName, tagCount, userId) VALUES ('6NyRHKcjjGYeH7Mi1avFyD', '2019-01-20 14:17:14', '2019-01-20 14:17:14', NULL, '아이즈원', 0, NULL); +INSERT INTO article_tag (id, createDate, updateDate, deleteDate, tagName, tagCount, userId) VALUES ('w8Scdd3pref5VimcDRXQyd', '2019-01-20 14:17:55', '2019-01-20 14:17:55', NULL, '사쿠라', 0, NULL); +INSERT INTO article_tag (id, createDate, updateDate, deleteDate, tagName, tagCount, userId) VALUES ('sAuYRmqnWqBatQvnbdHDPC', '2019-01-20 14:18:56', '2019-01-20 14:18:56', NULL, '안유진', 0, NULL); +INSERT INTO article_tag (id, createDate, updateDate, deleteDate, tagName, tagCount, userId) VALUES ('mM271wZfnJvdVMpRfSPBv9', '2019-01-20 14:20:17', '2019-01-20 14:20:17', NULL, '장원영', 0, NULL); +INSERT INTO article_tag (id, createDate, updateDate, deleteDate, tagName, tagCount, userId) VALUES ('58X3Q8Jijrr8cV3zDnwAAU', '2019-01-20 14:30:16', '2019-01-20 14:30:16', NULL, '김민주', 0, NULL); +INSERT INTO article_tag (id, createDate, updateDate, deleteDate, tagName, tagCount, userId) VALUES ('eASCfQyeLoUnrJohjEd2EV', '2019-01-20 14:35:20', '2019-01-20 14:35:20', NULL, '동물', 0, NULL); +INSERT INTO article_tag (id, createDate, updateDate, deleteDate, tagName, tagCount, userId) VALUES ('xu36RwDcXW6QN7k4ZrWsXL', '2019-01-20 14:35:20', '2019-01-20 14:35:20', NULL, '세게말', 0, NULL); +INSERT INTO article_tag (id, createDate, updateDate, deleteDate, tagName, tagCount, userId) VALUES ('duf2KPwZ4HQZ6TNN2zf2Eu', '2019-01-20 14:37:44', '2019-01-20 14:37:44', NULL, '배틀', 0, NULL); +INSERT INTO article_tag (id, createDate, updateDate, deleteDate, tagName, tagCount, userId) VALUES ('tdR2dhcDcmtQfXwcyzdWa2', '2019-01-20 14:37:44', '2019-01-20 14:37:44', NULL, '액션', 0, NULL); +INSERT INTO article_tag (id, createDate, updateDate, deleteDate, tagName, tagCount, userId) VALUES ('oVhoJA1AXsnRhcqvf2MdAe', '2019-01-21 00:44:04', '2019-01-21 00:44:04', NULL, 'SF', 0, NULL); +INSERT INTO article_tag (id, createDate, updateDate, deleteDate, tagName, tagCount, userId) VALUES ('bjc7Esp7zAZdaGjLBkQUuF', '2019-01-21 00:44:04', '2019-01-21 00:44:04', NULL, '도박', 0, NULL); +INSERT INTO article_tag (id, createDate, updateDate, deleteDate, tagName, tagCount, userId) VALUES ('jg2q8NwSx5GvGZtMpSCuvo', '2019-01-21 00:44:05', '2019-01-21 00:44:05', NULL, '베틀', 0, NULL); +INSERT INTO article_tag (id, createDate, updateDate, deleteDate, tagName, tagCount, userId) VALUES ('iBSWXWYGZPqhnpJVyNsymX', '2019-01-21 00:44:05', '2019-01-21 00:44:05', NULL, '비즈니스', 0, NULL); +INSERT INTO article_tag (id, createDate, updateDate, deleteDate, tagName, tagCount, userId) VALUES ('sRskhhDchmcEqZmDvxr5Gk', '2019-01-21 00:44:05', '2019-01-21 00:44:05', NULL, '스포츠', 0, NULL); +INSERT INTO article_tag (id, createDate, updateDate, deleteDate, tagName, tagCount, userId) VALUES ('pTARgrRWK3yHs17qf5sfmj', '2019-01-21 00:44:05', '2019-01-21 00:44:05', NULL, '호러', 0, NULL); +INSERT INTO article_tag (id, createDate, updateDate, deleteDate, tagName, tagCount, userId) VALUES ('sw3kASgddqbPVc3EgEZ9fE', '2019-01-21 00:44:05', '2019-01-21 00:44:05', NULL, '연애', 0, NULL); +INSERT INTO article_tag (id, createDate, updateDate, deleteDate, tagName, tagCount, userId) VALUES ('tmdwkv3EcvwzqEdy84FkG3', '2019-01-21 00:44:05', '2019-01-21 00:44:05', NULL, '사랑', 0, NULL); + + + +-- Table: article +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('mUmh9QZmzvKtQ9PaKu9Q1h', '2019-01-20 13:31:57', '2019-01-20 13:31:57', NULL, 'Cartoons', '큐티허니', '오리지널 일러스트', 0, 0, 0, 0, 1, 'qa1wJMzpVVTsWQouF76HgE', NULL); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('j1Zfqatb51pfrrZxNYvvom', '2019-01-20 13:32:30', '2019-01-20 13:32:30', NULL, 'Cartoons', '분위기 쩐다', '꺄~ 멋있어', 0, 0, 0, 0, 1, 'qa1wJMzpVVTsWQouF76HgE', NULL); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('xceSGS1EU41aGJ3Wk7Vd6s', '2019-01-20 13:33:11', '2019-01-20 13:33:11', NULL, 'Cartoons', '귀염이들', '프리티큐어였던가?', 0, 0, 0, 0, 1, 'qa1wJMzpVVTsWQouF76HgE', NULL); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('pTE2b5XM1PDgnMAPFrnnQ7', '2019-01-20 13:33:45', '2019-01-20 13:33:45', NULL, 'Cartoons', '러브라이브', '못그려도 봐주세요', 0, 0, 0, 0, 1, 'qa1wJMzpVVTsWQouF76HgE', NULL); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('oEPfFR9heKkLVoyumiGw2g', '2019-01-20 13:34:18', '2019-01-20 13:34:18', NULL, 'Cartoons', '서양식 그림?', '음... 이건 뭐지?', 0, 0, 0, 0, 1, 'qa1wJMzpVVTsWQouF76HgE', NULL); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('iXBQZo5PmX7orF2bhyAv2M', '2019-01-20 13:35:21', '2019-01-20 13:35:21', NULL, 'Cartoons', '분위기쩐다', '이런 것이야 말로 지대로 된 일러스트', 0, 0, 0, 0, 1, 'qa1wJMzpVVTsWQouF76HgE', NULL); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('7jYNgHwoCzSQP4vWG7n2Jk', '2019-01-20 13:36:02', '2019-01-20 13:36:02', NULL, 'Cartoons', '이뿌당', '분위기 잡고 한 샷~', 0, 0, 0, 0, 1, 'qa1wJMzpVVTsWQouF76HgE', NULL); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('cUba4rXbxtVxTvf4FzvPks', '2019-01-20 13:36:38', '2019-01-20 13:36:38', NULL, 'Cartoons', '마누라가 좋아하는 ', '취향저격 아이돌 팬픽', 0, 0, 0, 0, 1, 'qa1wJMzpVVTsWQouF76HgE', NULL); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('ewztryggNV86XdMNBqXvwi', '2019-01-20 13:37:34', '2019-01-20 13:37:34', NULL, 'Cartoons', '팬픽은 아니지만', '페러디정도는 ', 0, 0, 0, 0, 1, 'qa1wJMzpVVTsWQouF76HgE', NULL); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('mBYLJ9iafR7mhJrhMjgYpF', '2019-01-20 13:38:36', '2019-01-20 13:38:36', NULL, 'Cartoons', '합성 실패', '이건나원참', 0, 0, 0, 0, 1, 'qa1wJMzpVVTsWQouF76HgE', NULL); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('8uZrh7igLpwT6EQRAQMVnm', '2019-01-20 13:39:25', '2019-01-20 13:39:25', NULL, 'Cartoons', '이정도 쯤이야', '우리 언니 그려줘야지', 0, 0, 0, 0, 1, 'qa1wJMzpVVTsWQouF76HgE', NULL); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('gu5TjGh9LEf6TdbDBXaPAh', '2019-01-20 13:39:58', '2019-01-20 13:39:58', NULL, 'Cartoons', '서양풍은', '어려워...', 0, 0, 0, 0, 1, 'qa1wJMzpVVTsWQouF76HgE', NULL); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('mWk8kyZvmFk74UquAZADP2', '2019-01-20 13:40:38', '2019-01-20 13:40:38', NULL, 'Cartoons', '존애', '모처럼 맘먹고 그린 그림', 0, 0, 0, 0, 1, 'qa1wJMzpVVTsWQouF76HgE', NULL); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('iCugVaoTNQ6L1iipgyvoWK', '2019-01-20 13:41:08', '2019-01-20 13:41:08', NULL, 'Cartoons', '스케치 한장', '간만의 로리 스케치', 0, 0, 0, 0, 1, 'qa1wJMzpVVTsWQouF76HgE', NULL); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('wARdfMVh8qjoJCaX7S1Zgu', '2019-01-20 13:41:39', '2019-01-20 13:41:39', NULL, 'Cartoons', '이정도는', '로리 다음은 언니', 0, 0, 0, 0, 1, 'qa1wJMzpVVTsWQouF76HgE', NULL); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('kEXD2jT8sKc4avJMMG8aN4', '2019-01-20 13:44:00', '2019-01-20 13:44:00', NULL, 'Cartoons', '새해 복 많이 받으세요.', '2019', 0, 0, 0, 0, 1, '4FEtyVhf6mnZYWXxBS6QyN', NULL); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('oyQE4AG4xJF84RpW9xJBPP', '2019-01-20 13:44:32', '2019-01-20 13:44:32', NULL, 'Cartoons', '새해복2019', '많이 받으세요.', 0, 0, 0, 0, 1, '4FEtyVhf6mnZYWXxBS6QyN', NULL); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('9VtCkNFCqtn4qE4cxm8XfN', '2019-01-20 13:45:14', '2019-01-20 13:45:14', NULL, 'Cartoons', '새해복인즐 알았는데', '생일축이네 ㅎㅎㅎ', 0, 0, 0, 0, 1, '4FEtyVhf6mnZYWXxBS6QyN', NULL); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('ebf5UncQ1k2XnAayBTF5CB', '2019-01-20 13:45:45', '2019-01-20 13:45:45', NULL, 'Cartoons', '돼지해라고 그러내', '새해 복 많이 받으세요.', 0, 0, 0, 0, 1, '4FEtyVhf6mnZYWXxBS6QyN', NULL); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('rKKU99z7zsU7KswMNsAgry', '2019-01-20 13:46:24', '2019-01-20 13:46:24', NULL, 'Cartoons', '2019년 끝난건가?', '새해복이라기엔 일러가 무서웡', 0, 0, 0, 0, 1, '4FEtyVhf6mnZYWXxBS6QyN', NULL); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('VFMYivaGjYU4UwQ5GUe2U', '2019-01-20 13:47:54', '2019-01-20 13:47:54', NULL, 'Cartoons', '개인 취향이야말로', '장사는 안되지만...', 0, 0, 0, 0, 1, '4FEtyVhf6mnZYWXxBS6QyN', NULL); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('vhuPV3T2i2S1AHuyxkGvDf', '2019-01-20 13:48:31', '2019-01-20 13:48:31', NULL, 'Cartoons', '배부른 새해', '2019 새해 복 많이 받으세요.', 0, 0, 0, 0, 1, '4FEtyVhf6mnZYWXxBS6QyN', NULL); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('hqBgydvz9ABLs5k2EMCbMu', '2019-01-20 13:49:55', '2019-01-20 13:49:55', NULL, 'Cartoons', '차단될지도', '모르지만 일단 업로드', 0, 1, 0, 0, 1, '5cABtQnwV4k6aTVoFcDwyK', NULL); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('s2dodnS9CiaSYifrABx4Kc', '2019-01-20 13:50:31', '2019-01-20 13:50:31', NULL, 'Cartoons', '이런 것 안올리면 뭘 올려', '심쿵한 일러 많이 올려야지', 0, 1, 0, 0, 1, '5cABtQnwV4k6aTVoFcDwyK', NULL); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('j39YYVAD8USTkj5ND89J1S', '2019-01-20 13:51:21', '2019-01-20 13:51:21', NULL, 'Cartoons', '무섭다', '모노노케 히메가 이렇게 무서웠나?', 0, 2, 0, 0, 1, '5cABtQnwV4k6aTVoFcDwyK', NULL); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('4jK72MTAvNgHVhLWeJNxhd', '2019-01-20 13:52:02', '2019-01-20 13:52:02', NULL, 'Cartoons', '하츠네 미쿠 1', '올라갑니다.', 0, 0, 0, 0, 1, '5cABtQnwV4k6aTVoFcDwyK', NULL); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('fWbzvp7UwwYbTfSA4SuDGZ', '2019-01-20 13:52:32', '2019-01-20 13:52:32', NULL, 'Cartoons', '데뷔하자', '오케바리', 0, 0, 0, 0, 1, '5cABtQnwV4k6aTVoFcDwyK', NULL); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('mToog8Mvyi1c68cQA4xUTF', '2019-01-20 14:03:27', '2019-01-20 14:03:27', NULL, 'Cartoons', '소녀시대', '약어가 SNSD 인건 다아시죠?', 0, 0, 0, 0, 1, '4FEtyVhf6mnZYWXxBS6QyN', NULL); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('tWu6fiwPNUzPbt66kQret3', '2019-01-20 14:04:33', '2019-01-20 14:04:33', NULL, 'Cartoons', '원너원', '1234', 0, 0, 0, 0, 1, '4FEtyVhf6mnZYWXxBS6QyN', '75Y4qjTU1bGpGFWJXhMyzE'); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('tvLRXbp6UrQ9pG8Q3A6MGs', '2019-01-20 14:05:04', '2019-01-20 14:05:04', NULL, 'Cartoons', '워너원', '그 두번째', 0, 0, 0, 0, 1, '4FEtyVhf6mnZYWXxBS6QyN', '75Y4qjTU1bGpGFWJXhMyzE'); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('uE26CHoMdji3dsAy4rkCaS', '2019-01-20 14:05:29', '2019-01-20 14:05:29', NULL, 'Cartoons', '워너원', '그 3번째', 0, 0, 0, 0, 1, '4FEtyVhf6mnZYWXxBS6QyN', '75Y4qjTU1bGpGFWJXhMyzE'); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('9LiQotvX3JTqeyyeBHhjQ6', '2019-01-20 14:06:10', '2019-01-20 14:06:10', NULL, 'Cartoons', '원너원', '그 마지막', 0, 0, 0, 0, 1, '4FEtyVhf6mnZYWXxBS6QyN', '75Y4qjTU1bGpGFWJXhMyzE'); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('op1gvTyzQ1pgSvMxBhf4A6', '2019-01-20 14:07:56', '2019-01-20 14:07:56', NULL, 'Cartoons', '모모 지효', '귀엽다', 0, 0, 0, 0, 1, '5cABtQnwV4k6aTVoFcDwyK', 'tiCFn94Kmsc7Vm6Rewf3H4'); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('pqzGnHsaMCR1zSh9SPoL3y', '2019-01-20 14:08:29', '2019-01-20 14:08:29', NULL, 'Cartoons', '모모', '꿀벅지 빼고도 귀엽다.', 0, 0, 0, 0, 1, '5cABtQnwV4k6aTVoFcDwyK', 'tiCFn94Kmsc7Vm6Rewf3H4'); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('dVtmXd12UC8nJcVER5vw9C', '2019-01-20 14:09:14', '2019-01-20 14:09:14', NULL, 'Cartoons', '최강 일본 라인', '사나 미나 꺄~ 모모 쌕', 0, 0, 0, 0, 1, '5cABtQnwV4k6aTVoFcDwyK', 'tiCFn94Kmsc7Vm6Rewf3H4'); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('q4nJ6PDVnqYCtryFz6BP7b', '2019-01-20 14:09:48', '2019-01-20 14:09:48', NULL, 'Cartoons', '미소녀 끝판왕', '쯔위 납시요', 0, 0, 0, 0, 1, '5cABtQnwV4k6aTVoFcDwyK', 'tiCFn94Kmsc7Vm6Rewf3H4'); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('vgTJJbzvjQPZXPZ3w7dkFW', '2019-01-20 14:11:02', '2019-01-20 14:11:02', NULL, 'Cartoons', 'TWICE', '멤버들 모두 쵝오', 0, 0, 0, 0, 1, '5cABtQnwV4k6aTVoFcDwyK', 'tiCFn94Kmsc7Vm6Rewf3H4'); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('wNB8yqWJfNPD2JGsXwMUFg', '2019-01-20 14:12:01', '2019-01-20 14:12:01', NULL, 'Cartoons', '나연이 쵝오', '꺄~~~', 0, 0, 0, 0, 1, '5cABtQnwV4k6aTVoFcDwyK', 'tiCFn94Kmsc7Vm6Rewf3H4'); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('bfF8CKJoNuMu7VX8bwdTtS', '2019-01-20 14:13:41', '2019-01-20 14:13:41', NULL, 'Cartoons', '멤버 총 출동', '이뿌다', 0, 0, 0, 0, 1, '5cABtQnwV4k6aTVoFcDwyK', 'tiCFn94Kmsc7Vm6Rewf3H4'); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('8RS5c64CgPPzbowZENByW4', '2019-01-20 14:15:48', '2019-01-20 14:15:48', NULL, 'Cartoons', '프듀 끝났다', '무슨 낙으로 살지?', 0, 0, 0, 0, 1, 'qa1wJMzpVVTsWQouF76HgE', NULL); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('kkdoBAy28SwxpKLDXjHVFH', '2019-01-20 14:16:29', '2019-01-20 14:16:29', NULL, 'Cartoons', '3인방', '음... 언제 데뷔하지?', 0, 0, 0, 0, 1, 'qa1wJMzpVVTsWQouF76HgE', NULL); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('tBFSQ1JWTA4ZAwFAtkngE8', '2019-01-20 14:17:15', '2019-01-20 14:17:15', NULL, 'Cartoons', '사쿠라', '이쁘다', 0, 0, 0, 0, 1, 'qa1wJMzpVVTsWQouF76HgE', 'bve7Pn9xeyK5VFdVzMDoPe'); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('sukr923QasCjjpp3fD3jUT', '2019-01-20 14:17:55', '2019-01-20 14:17:55', NULL, 'Cartoons', '사쿠라 초 카와 윙크', '이젠 잘하던데...', 0, 0, 0, 0, 1, 'qa1wJMzpVVTsWQouF76HgE', 'bve7Pn9xeyK5VFdVzMDoPe'); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('upJf7vRA3Rkv7bo5syn56T', '2019-01-20 14:18:57', '2019-01-20 14:18:57', NULL, 'Cartoons', '막내에서 2번째', '장신 2nd 막내', 0, 0, 0, 0, 1, 'qa1wJMzpVVTsWQouF76HgE', 'tau6Dw8sQ7keogu7ztSC3F'); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('f9apdBMUNjcNaZakYAJ7Dn', '2019-01-20 14:19:33', '2019-01-20 14:19:33', NULL, 'Cartoons', '귀엽다', '유진이 귀염 폭발', 0, 0, 0, 0, 1, 'qa1wJMzpVVTsWQouF76HgE', 'tau6Dw8sQ7keogu7ztSC3F'); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('pSrkFCFzCXwQziZyLYNAnV', '2019-01-20 14:20:18', '2019-01-20 14:20:18', NULL, 'Cartoons', '최강 막내', '이게 장원영이야', 0, 0, 0, 0, 1, 'qa1wJMzpVVTsWQouF76HgE', '9ei5oAZAPyrWmaHgWrUKVv'); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('b2ow7KxrVhnRn3KqWJ89ps', '2019-01-20 14:20:49', '2019-01-20 14:20:49', NULL, 'Cartoons', '막내가 잴 크데', 'ㅋㅋㅋ', 0, 0, 0, 0, 1, 'qa1wJMzpVVTsWQouF76HgE', '9ei5oAZAPyrWmaHgWrUKVv'); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('wnAbzbxa76uUXrZzGTrKKE', '2019-01-20 14:21:26', '2019-01-20 14:21:26', NULL, 'Cartoons', '원영이 커엽다', '꺄~~~~~', 0, 0, 0, 0, 1, 'qa1wJMzpVVTsWQouF76HgE', '9ei5oAZAPyrWmaHgWrUKVv'); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('dYHKWNPW296FaaZ6KE6wHv', '2019-01-20 14:22:02', '2019-01-20 14:22:02', NULL, 'Cartoons', '원영인 어딜 봐도 ', '기여버~~', 0, 0, 0, 0, 1, 'qa1wJMzpVVTsWQouF76HgE', '9ei5oAZAPyrWmaHgWrUKVv'); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('wR2TgVbWiijMCMhvXVHQYC', '2019-01-20 14:22:39', '2019-01-20 14:22:39', NULL, 'Cartoons', '급이 다른 팬아트', '넘 이뻐요', 0, 0, 0, 0, 1, 'qa1wJMzpVVTsWQouF76HgE', '9ei5oAZAPyrWmaHgWrUKVv'); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('jE9fy92oUrzvDZ4jcrqq1S', '2019-01-20 14:24:01', '2019-01-20 14:24:01', NULL, 'Cartoons', '아이즈원', '아이즈온미', 0, 0, 0, 0, 1, '5cABtQnwV4k6aTVoFcDwyK', 'dtGAKQ6GUoE5MdR6hyt9WE'); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('drWD6ENe5AJsm4JZbyKmks', '2019-01-20 14:24:37', '2019-01-20 14:24:37', NULL, 'Cartoons', '아이즈원', '3만 하나로 ㅎㅎㅎ', 0, 0, 0, 0, 1, '5cABtQnwV4k6aTVoFcDwyK', 'dtGAKQ6GUoE5MdR6hyt9WE'); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('dGBeqcBgDq6BUtd8mdqAgc', '2019-01-20 14:25:49', '2019-01-20 14:25:49', NULL, 'Cartoons', '데뷔 축하해', '얼릉 만나러 가야지', 0, 0, 0, 0, 1, '5cABtQnwV4k6aTVoFcDwyK', 'dtGAKQ6GUoE5MdR6hyt9WE'); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('5nfgFzH5wVjEbWdmT4Hvii', '2019-01-20 14:26:41', '2019-01-20 14:26:41', NULL, 'Cartoons', '데뷔 축하해', '일본 데뷔도 가자`', 0, 0, 0, 0, 1, '5cABtQnwV4k6aTVoFcDwyK', 'dtGAKQ6GUoE5MdR6hyt9WE'); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('gtie3aYUVRPuAk5bTr5ZoD', '2019-01-20 14:27:48', '2019-01-20 14:27:48', NULL, 'Cartoons', '위즈원 사랑해요', '아이즈원도 사랑해요', 0, 0, 0, 0, 1, '5cABtQnwV4k6aTVoFcDwyK', 'dtGAKQ6GUoE5MdR6hyt9WE'); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('fPDbmwj79iogkmzhWz51PB', '2019-01-20 14:28:27', '2019-01-20 14:28:27', NULL, 'Cartoons', '일본에서도 대박나라', '위즈원 아이즈원 홧팅', 0, 0, 0, 0, 1, '5cABtQnwV4k6aTVoFcDwyK', 'dtGAKQ6GUoE5MdR6hyt9WE'); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('pQ1UMbidNuGUwHkxVoCXgb', '2019-01-20 14:30:16', '2019-01-20 14:30:16', NULL, 'Cartoons', '개굴이', '민주는 개굴이', 0, 0, 0, 0, 1, '5cABtQnwV4k6aTVoFcDwyK', 'dtGAKQ6GUoE5MdR6hyt9WE'); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('o4bz6n39BtWkDLWMD2UM12', '2019-01-20 14:33:12', '2019-01-20 14:33:12', NULL, 'Cartoons', '새해복', '만화로도 있네', 0, 0, 0, 0, 1, '4FEtyVhf6mnZYWXxBS6QyN', NULL); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('bawdAxFjUs1HpRZheshTNe', '2019-01-20 14:34:16', '2019-01-20 14:34:16', NULL, 'Cartoons', '포켓몬 세해복', '많이 받으세요.', 0, 0, 0, 0, 1, '4FEtyVhf6mnZYWXxBS6QyN', NULL); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('2JG5vg5NY9NExyZFck2xZv', '2019-01-20 14:35:20', '2019-01-20 14:35:20', NULL, 'Cartoons', '시리즈로 가보자', '1탄입니다.', 0, 0, 0, 0, 1, '4FEtyVhf6mnZYWXxBS6QyN', 'v86iEv2Ti1hj7tPSFkzDdE'); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('qmcfEynLBQ3exw4QYQG2yn', '2019-01-20 14:36:09', '2019-01-20 14:36:09', NULL, 'Cartoons', '시리즈', '그 두번째', 0, 0, 0, 0, 1, '4FEtyVhf6mnZYWXxBS6QyN', 'v86iEv2Ti1hj7tPSFkzDdE'); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('bwW3gxH9AP37rzQpvVPurD', '2019-01-20 14:36:45', '2019-01-20 14:36:45', NULL, 'Cartoons', '두장짜리', '만화임', 0, 0, 0, 0, 1, '4FEtyVhf6mnZYWXxBS6QyN', NULL); +INSERT INTO article (id, createDate, updateDate, deleteDate, type, title, description, original, ageLimit, commentsCount, viewCount, publicYN, userId, articleSeriesId) VALUES ('rGHigoHVKHWUXM3N8E48Sb', '2019-01-20 14:37:44', '2019-01-20 14:37:44', NULL, 'Cartoons', '멋지다', '흑백 일러스트', 0, 0, 0, 0, 1, '4FEtyVhf6mnZYWXxBS6QyN', NULL); + +-- Table: article_attachments +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('4etYAvamLu925jd9B42uGy', '2019-01-20 13:31:56', '2019-01-20 13:31:57', NULL, 'Cover', 0, 'Cartoons', 'jxgFHB7r75gWtbU2SCF6Uq', 'mUmh9QZmzvKtQ9PaKu9Q1h'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('cW5yAfh59YVRFKywWDayBE', '2019-01-20 13:32:30', '2019-01-20 13:32:30', NULL, 'Cover', 0, 'Cartoons', 'hYVtUGD6C1qxHSDqA3C1Nw', 'j1Zfqatb51pfrrZxNYvvom'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('4mfJ9xW8gXibWdaqjpy8fh', '2019-01-20 13:33:10', '2019-01-20 13:33:11', NULL, 'Cover', 0, 'Cartoons', 'eZ7V1VU4kfEANBNSr53swk', 'xceSGS1EU41aGJ3Wk7Vd6s'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('cMaiwJF5fZ3q24acZEJ7pk', '2019-01-20 13:33:45', '2019-01-20 13:33:45', NULL, 'Cover', 0, 'Cartoons', 'eGMrtZp1AcY73f42L5ReXJ', 'pTE2b5XM1PDgnMAPFrnnQ7'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('3ALuNmwxhkuwH78FgHCoBd', '2019-01-20 13:34:18', '2019-01-20 13:34:18', NULL, 'Cover', 0, 'Cartoons', 'xnwdBUYuTWuzUbYe8iqVAn', 'oEPfFR9heKkLVoyumiGw2g'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('e6J2VAqLrhRXmCSHCRrvrY', '2019-01-20 13:35:21', '2019-01-20 13:35:21', NULL, 'Cover', 0, 'Cartoons', 'wvUVj9r89H6GnQJZBpSbhL', 'iXBQZo5PmX7orF2bhyAv2M'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('ne6C1hF7Eyu5sFWvcHg1hM', '2019-01-20 13:36:01', '2019-01-20 13:36:02', NULL, 'Cover', 0, 'Cartoons', 'gJBCrxtVdgmtyaK8Jn8o3R', '7jYNgHwoCzSQP4vWG7n2Jk'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('q9jzkHkmKCLnaLj7NxHXGN', '2019-01-20 13:36:37', '2019-01-20 13:36:38', NULL, 'Cover', 0, 'Cartoons', 'gQJEqbgPnhZtuuZYsuJJRC', 'cUba4rXbxtVxTvf4FzvPks'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('svQJ4U3zzA3n5rQKQqUmkZ', '2019-01-20 13:37:34', '2019-01-20 13:37:34', NULL, 'Cover', 0, 'Cartoons', 'wyPXyqf8nM7KSSjUxX3xXB', 'ewztryggNV86XdMNBqXvwi'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('iQSb9kyhSk16MxmzDtf51u', '2019-01-20 13:38:36', '2019-01-20 13:38:36', NULL, 'Cover', 0, 'Cartoons', 'evDEB3joyTHc3gm4BcUHCV', 'mBYLJ9iafR7mhJrhMjgYpF'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('vaAakknAzRG8X8o24vXe41', '2019-01-20 13:39:25', '2019-01-20 13:39:25', NULL, 'Cover', 0, 'Cartoons', 'mmvL1FXVJkee43DpHSiQfs', '8uZrh7igLpwT6EQRAQMVnm'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('bX8tGM8Aopfb8jj2J2r8FC', '2019-01-20 13:39:58', '2019-01-20 13:39:58', NULL, 'Cover', 0, 'Cartoons', 'oJEoxqaExMwPMZATye1Vdv', 'gu5TjGh9LEf6TdbDBXaPAh'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('fTYaNHxxzZNX86Peuj5y81', '2019-01-20 13:40:37', '2019-01-20 13:40:38', NULL, 'Cover', 0, 'Cartoons', 'n5wZ3gcbP1D78kkTYfBTvC', 'mWk8kyZvmFk74UquAZADP2'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('bYUwabbjbu8yBjcbFFwgNa', '2019-01-20 13:41:08', '2019-01-20 13:41:08', NULL, 'Cover', 0, 'Cartoons', 'k2cuW1WuzuMucQdCUCc9Y2', 'iCugVaoTNQ6L1iipgyvoWK'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('2Pcppmh84vx1HyQ4Q1Q5sz', '2019-01-20 13:41:38', '2019-01-20 13:41:39', NULL, 'Cover', 0, 'Cartoons', '789HinPETzxmoJoNrEzoUv', 'wARdfMVh8qjoJCaX7S1Zgu'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('E8qugVz1eTA4HhMrkaQxg', '2019-01-20 13:43:59', '2019-01-20 13:44:00', NULL, 'Cover', 0, 'Cartoons', 'qSLevjhu5NVwjU86dZ8RA1', 'kEXD2jT8sKc4avJMMG8aN4'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('qipnPmhvs8KuuS7HAJXqUZ', '2019-01-20 13:44:32', '2019-01-20 13:44:32', NULL, 'Cover', 0, 'Cartoons', 'fBfQRdTAPp1gPBVNpwqYxZ', 'oyQE4AG4xJF84RpW9xJBPP'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('mbRbhguLFzT8npsBeC7JpK', '2019-01-20 13:45:13', '2019-01-20 13:45:14', NULL, 'Cover', 0, 'Cartoons', 'ssLiWzdZ3SQgo4zYhWtr7e', '9VtCkNFCqtn4qE4cxm8XfN'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('wFBJg1HdNy2Qg1MDmbUzwK', '2019-01-20 13:45:45', '2019-01-20 13:45:45', NULL, 'Cover', 0, 'Cartoons', 'aUEiXK6VQPEfTnt4AxQyHX', 'ebf5UncQ1k2XnAayBTF5CB'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('61ePNvrd97v7dMjqsgB4UK', '2019-01-20 13:46:24', '2019-01-20 13:46:24', NULL, 'Cover', 0, 'Cartoons', 'xbVmmxv6ZA2M5UQVazmW8r', 'rKKU99z7zsU7KswMNsAgry'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('nojvKd1wMrEt1Z9gDoNjFq', '2019-01-20 13:47:54', '2019-01-20 13:47:54', NULL, 'Cover', 0, 'Cartoons', 'xtwYqxGKuM3X8nnYiGr6wL', 'VFMYivaGjYU4UwQ5GUe2U'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('eoYPvedjqR4K7ChheBuF4N', '2019-01-20 13:48:31', '2019-01-20 13:48:31', NULL, 'Cover', 0, 'Cartoons', '4LMj9DuGMkRvBP6L6y9gjN', 'vhuPV3T2i2S1AHuyxkGvDf'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('5vRQHFeCSSqWjUPqvMNcMv', '2019-01-20 13:49:55', '2019-01-20 13:49:55', NULL, 'Cover', 0, 'Cartoons', 'a6JHLsVewt6Qmcq7CwbgtU', 'hqBgydvz9ABLs5k2EMCbMu'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('hzarEh6FHsX7dgUQgpqtTR', '2019-01-20 13:50:31', '2019-01-20 13:50:31', NULL, 'Cover', 0, 'Cartoons', '7SB9sFzyfBNs56rLk3yBPc', 's2dodnS9CiaSYifrABx4Kc'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('esCXBBnPb6RMrFr9Un1omd', '2019-01-20 13:51:20', '2019-01-20 13:51:21', NULL, 'Cover', 0, 'Cartoons', 'qCA835cvKv57dPiFDEziFQ', 'j39YYVAD8USTkj5ND89J1S'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('24oSyDKAgfGCo6EttJNZHx', '2019-01-20 13:52:02', '2019-01-20 13:52:02', NULL, 'Cover', 0, 'Cartoons', '9KGEK1GgBsz11JHJx7sGMp', '4jK72MTAvNgHVhLWeJNxhd'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('jVoWjzxc33zQWzRV744TNT', '2019-01-20 13:52:31', '2019-01-20 13:52:32', NULL, 'Cover', 0, 'Cartoons', 'uNsxFiWtk43jP5yVrXBwLM', 'fWbzvp7UwwYbTfSA4SuDGZ'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('mGnZkjFQ3gJNfoWmFXp5SA', '2019-01-20 14:03:27', '2019-01-20 14:03:27', NULL, 'Cover', 0, 'Cartoons', 'xfkj7n8Ss24vrrdcbGDBuf', 'mToog8Mvyi1c68cQA4xUTF'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('gXiMBizLrYTQKJgXXWasJf', '2019-01-20 14:04:33', '2019-01-20 14:04:33', NULL, 'Cover', 0, 'Cartoons', 'v1GNhsqStcmcabnWYgBQSN', 'tWu6fiwPNUzPbt66kQret3'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('2wctN4MXqir6zBndiZEp1s', '2019-01-20 14:05:03', '2019-01-20 14:05:04', NULL, 'Cover', 0, 'Cartoons', '3yXhsLkofznmrmoWNKfttV', 'tvLRXbp6UrQ9pG8Q3A6MGs'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('dketz7amL61bFnKt2R3pnS', '2019-01-20 14:05:29', '2019-01-20 14:05:29', NULL, 'Cover', 0, 'Cartoons', '94CKZTyn2qqbQ7UwHqpLmP', 'uE26CHoMdji3dsAy4rkCaS'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('9SuCGPUhzuSUPDRRZv5g3g', '2019-01-20 14:06:10', '2019-01-20 14:06:10', NULL, 'Cover', 0, 'Cartoons', 'sPDXjxtuP8aTjaPDg8nv61', '9LiQotvX3JTqeyyeBHhjQ6'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('wVEQ5m1S4mHfQM1369RMud', '2019-01-20 14:07:55', '2019-01-20 14:07:56', NULL, 'Cover', 0, 'Cartoons', 'qxhrvzcJT6PXtaagRJHACo', 'op1gvTyzQ1pgSvMxBhf4A6'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('eBSqeA8g4rq3Ar872MCBiV', '2019-01-20 14:08:28', '2019-01-20 14:08:29', NULL, 'Cover', 0, 'Cartoons', '7WDdAxmeMJG4wWkdRr8jUj', 'pqzGnHsaMCR1zSh9SPoL3y'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('rsMSAZCrmNDq3eRZfFVaYK', '2019-01-20 14:09:14', '2019-01-20 14:09:14', NULL, 'Cover', 0, 'Cartoons', '82BpyASKAhBpDDAmPg3XAR', 'dVtmXd12UC8nJcVER5vw9C'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('dgpf7C98ohDpeY3kZ9UU1K', '2019-01-20 14:09:47', '2019-01-20 14:09:48', NULL, 'Cover', 0, 'Cartoons', 'qAroeqmBg4hPEEuDVzTsi', 'q4nJ6PDVnqYCtryFz6BP7b'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('q3mZJWvH6sScXNyH4CCtKo', '2019-01-20 14:11:01', '2019-01-20 14:11:02', NULL, 'Cover', 0, 'Cartoons', 'snckkf4yQG32TsLvgVBQVT', 'vgTJJbzvjQPZXPZ3w7dkFW'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('dtqEkBTuN4NEA2VJ5i3PZs', '2019-01-20 14:11:01', '2019-01-20 14:11:02', NULL, 'Contents', 1, 'Cartoons', 'uF1qEbmxZJ8mvPWwZDNpuQ', 'vgTJJbzvjQPZXPZ3w7dkFW'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('bLe6zd57973yKgdH51x4ar', '2019-01-20 14:11:01', '2019-01-20 14:11:02', NULL, 'Contents', 2, 'Cartoons', 'uPKAQJgvxDNoDZdUr5SxhR', 'vgTJJbzvjQPZXPZ3w7dkFW'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('pMZz5nr8gRha3gM4oAtUA9', '2019-01-20 14:11:01', '2019-01-20 14:11:02', NULL, 'Contents', 3, 'Cartoons', 'h2Lg497sMRXb9VHTSERRWR', 'vgTJJbzvjQPZXPZ3w7dkFW'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('iGqDqczQiDLjvfiBrK1Cmc', '2019-01-20 14:12:01', '2019-01-20 14:12:01', NULL, 'Cover', 0, 'Cartoons', 'PcjNfj6Ra6bMxstNHNRGH', 'wNB8yqWJfNPD2JGsXwMUFg'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('mAuLbjRyxrVRiETMgfrpCT', '2019-01-20 14:12:01', '2019-01-20 14:12:01', NULL, 'Contents', 1, 'Cartoons', 'fXBEgH8juB2WzpzP5yQmRb', 'wNB8yqWJfNPD2JGsXwMUFg'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('pXa7NacbUitRZoPnybG4aj', '2019-01-20 14:13:41', '2019-01-20 14:13:41', NULL, 'Cover', 0, 'Cartoons', '5hYwk8uWt9RvxnHsTiE6hm', 'bfF8CKJoNuMu7VX8bwdTtS'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('kiVjzuVzvpBJuL7rYEMwpT', '2019-01-20 14:13:41', '2019-01-20 14:13:41', NULL, 'Contents', 1, 'Cartoons', 'xdSoEqmZ7zUqfHKB9RmwSQ', 'bfF8CKJoNuMu7VX8bwdTtS'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('sqeqjXXCGPaLAmKf86C4FD', '2019-01-20 14:13:41', '2019-01-20 14:13:41', NULL, 'Contents', 2, 'Cartoons', 'utJRxdfg9wvgEvufWwGtTu', 'bfF8CKJoNuMu7VX8bwdTtS'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('xxtCnrAHDxR8CBDk5Tw2DG', '2019-01-20 14:15:47', '2019-01-20 14:15:48', NULL, 'Cover', 0, 'Cartoons', 'vJ2acJSthHCTY26vSwf53t', '8RS5c64CgPPzbowZENByW4'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('qf4kkhEnh8yRhA7vXB2kW5', '2019-01-20 14:15:47', '2019-01-20 14:15:48', NULL, 'Contents', 1, 'Cartoons', 'bCFkovnBGKcjsNHMCp7zZ3', '8RS5c64CgPPzbowZENByW4'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('jj8YPfYjtZVdpZViWzoUfG', '2019-01-20 14:15:47', '2019-01-20 14:15:48', NULL, 'Contents', 2, 'Cartoons', 'qHDpPmHU5bAMHtZxu18RQF', '8RS5c64CgPPzbowZENByW4'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('ikTBo5LyGWB64CgUNXAvW4', '2019-01-20 14:15:47', '2019-01-20 14:15:48', NULL, 'Contents', 3, 'Cartoons', 'qYKi12ZzTTc1ntjwomovMH', '8RS5c64CgPPzbowZENByW4'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('25y3THGauNUUcfY1AC4VsV', '2019-01-20 14:15:47', '2019-01-20 14:15:48', NULL, 'Contents', 4, 'Cartoons', 'jWc89h53EQmdpLqQhEjMZK', '8RS5c64CgPPzbowZENByW4'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('vb4ZQM6WJ57e5PTidD6Us5', '2019-01-20 14:15:47', '2019-01-20 14:15:48', NULL, 'Contents', 5, 'Cartoons', 'sjCZXE7fwFnQbmnL6YZwEG', '8RS5c64CgPPzbowZENByW4'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('wbdZUmXpNuKCxJAVsR2FzR', '2019-01-20 14:15:47', '2019-01-20 14:15:48', NULL, 'Contents', 6, 'Cartoons', 'epuAyA14JamFEUYmeEmeyr', '8RS5c64CgPPzbowZENByW4'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('47ZXvwrtzyR57ij2pNxFCU', '2019-01-20 14:15:47', '2019-01-20 14:15:48', NULL, 'Contents', 7, 'Cartoons', 'vasDjwnkuv5MkmQLLVCWsf', '8RS5c64CgPPzbowZENByW4'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('fnRy6ZhV5PCrnqC4fZM1se', '2019-01-20 14:15:47', '2019-01-20 14:15:48', NULL, 'Contents', 8, 'Cartoons', 'dNRMRgcpvx6NWDSHamiAQP', '8RS5c64CgPPzbowZENByW4'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('nRP2327yYW98iPY63Pxdvi', '2019-01-20 14:15:47', '2019-01-20 14:15:48', NULL, 'Contents', 9, 'Cartoons', 'goPiUz5qAjfQhBnYF3ZofU', '8RS5c64CgPPzbowZENByW4'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('mwa47YVXE3yeCshb1e8DxG', '2019-01-20 14:16:28', '2019-01-20 14:16:29', NULL, 'Cover', 0, 'Cartoons', 'wEE6ZWmFFh7Lxc9hzFTsuX', 'kkdoBAy28SwxpKLDXjHVFH'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('7e74xNHtU5SWmhCQLzxqzA', '2019-01-20 14:17:14', '2019-01-20 14:17:15', NULL, 'Cover', 0, 'Cartoons', 'mhjAYR5huiQBG2BT3uYNab', 'tBFSQ1JWTA4ZAwFAtkngE8'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('uhvHGNkK1DH2b2yXD451Aw', '2019-01-20 14:17:55', '2019-01-20 14:17:55', NULL, 'Cover', 0, 'Cartoons', '68ekAgNLoiQCRBQWgPzxP5', 'sukr923QasCjjpp3fD3jUT'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('vTUhLabdDTrUchbhZmbYbB', '2019-01-20 14:18:56', '2019-01-20 14:18:57', NULL, 'Cover', 0, 'Cartoons', 'wtTzJeNHwosqpTgWQTAKFd', 'upJf7vRA3Rkv7bo5syn56T'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('jFqmXHuiiRooao96vcNdGU', '2019-01-20 14:19:33', '2019-01-20 14:19:33', NULL, 'Cover', 0, 'Cartoons', 'sdQBdEN7C96RWnXPKNDvJA', 'f9apdBMUNjcNaZakYAJ7Dn'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('xbtbCZ1H2nGQMUG3tS9znm', '2019-01-20 14:20:17', '2019-01-20 14:20:18', NULL, 'Cover', 0, 'Cartoons', 'jfpeymzPRG4EkFKfzg1PX9', 'pSrkFCFzCXwQziZyLYNAnV'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('gzbUuemq3BoK8JDk2UZL57', '2019-01-20 14:20:48', '2019-01-20 14:20:49', NULL, 'Cover', 0, 'Cartoons', 'tJQdHSEyJoBM4T71ez86yk', 'b2ow7KxrVhnRn3KqWJ89ps'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('rxGyxTAnYmkrQSz8gCdvAn', '2019-01-20 14:21:25', '2019-01-20 14:21:26', NULL, 'Cover', 0, 'Cartoons', 'upjx2XAhY7A3TP2VjC2Aem', 'wnAbzbxa76uUXrZzGTrKKE'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('okqZYr1esNc4Lu699bhMar', '2019-01-20 14:22:01', '2019-01-20 14:22:02', NULL, 'Cover', 0, 'Cartoons', 'uCzFeWxCRdFDyRJDzZkH3h', 'dYHKWNPW296FaaZ6KE6wHv'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('ocBxN9xtu4vRDagJjGuCBp', '2019-01-20 14:22:39', '2019-01-20 14:22:39', NULL, 'Cover', 0, 'Cartoons', 'hX1uuZmWxpUPnTZcsSPyv6', 'wR2TgVbWiijMCMhvXVHQYC'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('rC2NN9EkqBDdCf8R8XanuZ', '2019-01-20 14:24:01', '2019-01-20 14:24:01', NULL, 'Cover', 0, 'Cartoons', 'gkhbfYAJy8sbc4PA2YETJo', 'jE9fy92oUrzvDZ4jcrqq1S'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('f49rNAHAv2Qzjg2GcffLYd', '2019-01-20 14:24:36', '2019-01-20 14:24:37', NULL, 'Cover', 0, 'Cartoons', 'xu2jXsPT4Qy54gFj7bpwKA', 'drWD6ENe5AJsm4JZbyKmks'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('fzBM6SjVjSjzqG9rYnAmZn', '2019-01-20 14:25:48', '2019-01-20 14:27:12', NULL, 'Contents', 0, 'Cartoons', 'iwCJMEJ68nTrP2wzegxhmK', 'dGBeqcBgDq6BUtd8mdqAgc'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('jz9KajnWj5AfhD57HZapmF', '2019-01-20 14:25:48', '2019-01-20 14:27:12', NULL, 'Contents', 1, 'Cartoons', 'qYNTGeZzVg6zur1QcAxyJS', 'dGBeqcBgDq6BUtd8mdqAgc'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('kWZKCSnxRRYnxoQQh1t4VW', '2019-01-20 14:25:48', '2019-01-20 14:27:12', NULL, 'Contents', 2, 'Cartoons', 'xxgX7RvBURP7dNsbvXWc3V', 'dGBeqcBgDq6BUtd8mdqAgc'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('5HqbWuHLLjoYCPeDxzG78Q', '2019-01-20 14:25:48', '2019-01-20 14:27:12', NULL, 'Cover', 3, 'Cartoons', 'bwYfhLWCbW3rjQBwJ5BoDi', 'dGBeqcBgDq6BUtd8mdqAgc'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('opB7LrQFopmjav4wqJJ5m4', '2019-01-20 14:25:49', '2019-01-20 14:27:12', NULL, 'Contents', 5, 'Cartoons', 'ttozusABk4KSN72NBpCo9E', 'dGBeqcBgDq6BUtd8mdqAgc'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('uN2pW9ZoV6BiQzSsAw3Ro5', '2019-01-20 14:25:49', '2019-01-20 14:27:12', NULL, 'Contents', 4, 'Cartoons', 'drUZD96bif7xeiHS8xuKuj', 'dGBeqcBgDq6BUtd8mdqAgc'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('s1Y1s67koTFqixmd4iRJfL', '2019-01-20 14:25:49', '2019-01-20 14:27:12', NULL, 'Contents', 6, 'Cartoons', '9HziwGsMHpVV9kVjYpDReE', 'dGBeqcBgDq6BUtd8mdqAgc'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('jTxbDaqKavFxnRgeeo7yG6', '2019-01-20 14:25:49', '2019-01-20 14:27:12', NULL, 'Contents', 7, 'Cartoons', 'bFdA2m4ye4fxudPezJTsNv', 'dGBeqcBgDq6BUtd8mdqAgc'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('jYfKvx4RcPZoDknq9rt2ih', '2019-01-20 14:25:49', '2019-01-20 14:27:12', NULL, 'Contents', 8, 'Cartoons', 'fyCuGajfHwRGHiGzkQr9ay', 'dGBeqcBgDq6BUtd8mdqAgc'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('aKgKZwVcfk3HppTDkASv4e', '2019-01-20 14:26:41', '2019-01-20 14:26:41', NULL, 'Cover', 0, 'Cartoons', '9jAtmPkGiET1m4E18824AR', '5nfgFzH5wVjEbWdmT4Hvii'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('2iBK94y4ZUiggky5ii331j', '2019-01-20 14:27:47', '2019-01-20 14:27:48', NULL, 'Cover', 0, 'Cartoons', '7aXQtsTsxoj2qVAvMUwg3K', 'gtie3aYUVRPuAk5bTr5ZoD'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('4b6fjNvjTjjsnQQt6XXo6E', '2019-01-20 14:28:26', '2019-01-20 14:28:27', NULL, 'Cover', 0, 'Cartoons', '5xovnSWEVDeadTxufHHz8Q', 'fPDbmwj79iogkmzhWz51PB'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('qknF6zkQKx6XCjZKFtNadd', '2019-01-20 14:30:15', '2019-01-20 14:30:16', NULL, 'Cover', 0, 'Cartoons', 'f4dGuCPSqs1fiCwhDaskmB', 'pQ1UMbidNuGUwHkxVoCXgb'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('7nvx8EK2Uz8By9kGc8HToB', '2019-01-20 14:30:15', '2019-01-20 14:30:16', NULL, 'Contents', 1, 'Cartoons', 'v6v1xKKT9SxffeuNknTTHC', 'pQ1UMbidNuGUwHkxVoCXgb'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('dzogBXi5z6bwM6uP2Wd2gs', '2019-01-20 14:33:12', '2019-01-20 14:33:13', NULL, 'Cover', 0, 'Cartoons', 'ah8ajEJrwiDYED6iVMiptT', 'o4bz6n39BtWkDLWMD2UM12'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('div25e52vumJu9LH4PpzwA', '2019-01-20 14:33:12', '2019-01-20 14:33:13', NULL, 'Contents', 1, 'Cartoons', 'mAcqXsfHWwBjERZHZ1BYvy', 'o4bz6n39BtWkDLWMD2UM12'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('onwxpKFr3cvNbu2YMp7eck', '2019-01-20 14:34:15', '2019-01-20 14:34:16', NULL, 'Cover', 0, 'Cartoons', 'pMisd8E9PgqkCrQtLqGCGi', 'bawdAxFjUs1HpRZheshTNe'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('jcE67M7vWNdZaVP9FeyJ4d', '2019-01-20 14:34:15', '2019-01-20 14:34:16', NULL, 'Contents', 1, 'Cartoons', 'b1UrPqvcy6CoEMdVfHz2eh', 'bawdAxFjUs1HpRZheshTNe'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('sCikPSd1BpnPnDp6D8eXVv', '2019-01-20 14:35:20', '2019-01-20 14:35:20', NULL, 'Cover', 0, 'Cartoons', 'vXDXQGeUCzjUi2tUboaHWf', '2JG5vg5NY9NExyZFck2xZv'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('dpThfq3pHrhy4EVXKhLoc5', '2019-01-20 14:35:20', '2019-01-20 14:35:20', NULL, 'Contents', 1, 'Cartoons', 'tSyiFMzxQxjowyyLHFeZS5', '2JG5vg5NY9NExyZFck2xZv'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('jJ6UzE4EPkT8iEcQqPAMJJ', '2019-01-20 14:35:20', '2019-01-20 14:35:20', NULL, 'Contents', 2, 'Cartoons', 'j3CoPs1VQK92ZXKS1asihF', '2JG5vg5NY9NExyZFck2xZv'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('rbRbvH5vJfUF4DxeLpCjMb', '2019-01-20 14:35:20', '2019-01-20 14:35:20', NULL, 'Contents', 3, 'Cartoons', 'tZHmcWN6dBnDYGjr5aJUHR', '2JG5vg5NY9NExyZFck2xZv'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('5DErw25vsg2AcWjn7PjwRs', '2019-01-20 14:35:20', '2019-01-20 14:35:20', NULL, 'Contents', 4, 'Cartoons', 'mbio6p29HzQj6STENjD2fP', '2JG5vg5NY9NExyZFck2xZv'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('j1Ckdp5KzAUNcXCnE15gYc', '2019-01-20 14:35:20', '2019-01-20 14:35:20', NULL, 'Contents', 5, 'Cartoons', 'pDjw582YUkaomhg3qMaGzb', '2JG5vg5NY9NExyZFck2xZv'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('7hkdyL9SksBsoCTa2yJdX4', '2019-01-20 14:35:20', '2019-01-20 14:35:20', NULL, 'Contents', 6, 'Cartoons', 'nstj92eswPzxpGiygdRVx5', '2JG5vg5NY9NExyZFck2xZv'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('puBHzoRDM6utzcvwHu6ETP', '2019-01-20 14:36:08', '2019-01-20 14:36:09', NULL, 'Cover', 0, 'Cartoons', 'qXgkqPTvbXmLEKfRQWWgc3', 'qmcfEynLBQ3exw4QYQG2yn'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('q4eGDPSawHpWNWahMJysGk', '2019-01-20 14:36:08', '2019-01-20 14:36:09', NULL, 'Contents', 1, 'Cartoons', 'vENr4C19cBeZrmb6yzE8JJ', 'qmcfEynLBQ3exw4QYQG2yn'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('sKmiCxxYmkLBP33cDEU5e2', '2019-01-20 14:36:08', '2019-01-20 14:36:09', NULL, 'Contents', 2, 'Cartoons', 'ssNp1ZxMZiYVeYSrg9G7fb', 'qmcfEynLBQ3exw4QYQG2yn'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('4HXf7BF9WmXYQ7PYctH7As', '2019-01-20 14:36:08', '2019-01-20 14:36:09', NULL, 'Contents', 3, 'Cartoons', '2BPSwJo3xmkhKB9D7Mgnsh', 'qmcfEynLBQ3exw4QYQG2yn'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('cfWHp1DbuN8VxCA3M3DxTZ', '2019-01-20 14:36:08', '2019-01-20 14:36:09', NULL, 'Contents', 4, 'Cartoons', 'oaEVR4pYHdJ472npM1C2uD', 'qmcfEynLBQ3exw4QYQG2yn'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('jvZR6yPRdHSgXst9DvjNUE', '2019-01-20 14:36:08', '2019-01-20 14:36:09', NULL, 'Contents', 5, 'Cartoons', '9j88A9cH79tMWAJFFRtafk', 'qmcfEynLBQ3exw4QYQG2yn'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('gHRfC81h9z3EGdgRjJUi6y', '2019-01-20 14:36:08', '2019-01-20 14:36:09', NULL, 'Contents', 6, 'Cartoons', 'qcNRMkQeKkKhhd61pPQbNS', 'qmcfEynLBQ3exw4QYQG2yn'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('7N4w4MJyYt7aJvDytSDHPB', '2019-01-20 14:36:08', '2019-01-20 14:36:09', NULL, 'Contents', 7, 'Cartoons', 'GvXzzkvH5Vfjhw5BH7Au', 'qmcfEynLBQ3exw4QYQG2yn'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('2VDVPub57cYW2Cv9PxKZem', '2019-01-20 14:36:08', '2019-01-20 14:36:09', NULL, 'Contents', 8, 'Cartoons', '4fTvse7VtQq56Huq3X788C', 'qmcfEynLBQ3exw4QYQG2yn'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('jiFDY1ar3XMESd8FqKoNei', '2019-01-20 14:36:08', '2019-01-20 14:36:09', NULL, 'Contents', 9, 'Cartoons', 'fZnf5Fc12bbBRdCoVBadoq', 'qmcfEynLBQ3exw4QYQG2yn'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('tmHucoGGVJFBJMyYt7GNKK', '2019-01-20 14:36:08', '2019-01-20 14:36:09', NULL, 'Contents', 10, 'Cartoons', 'vJa2wQ2PcKUKJ4uFwywhaR', 'qmcfEynLBQ3exw4QYQG2yn'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('bBiTqAoQw3tbh4uHicwg8p', '2019-01-20 14:36:45', '2019-01-20 14:36:45', NULL, 'Cover', 0, 'Cartoons', 'mALkzjs71P9YTvf1AJgZGK', 'bwW3gxH9AP37rzQpvVPurD'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('fd5rMv4GFsFDn7tBr9eYEh', '2019-01-20 14:36:45', '2019-01-20 14:36:45', NULL, 'Contents', 1, 'Cartoons', '7j36rZg4H1wKUZRjViwpP6', 'bwW3gxH9AP37rzQpvVPurD'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('hDp6MsbwjfL24uq4o71ZdD', '2019-01-20 14:37:44', '2019-01-20 14:37:44', NULL, 'Cover', 0, 'Cartoons', '2y1Ve4QMshTctPswE9ncWb', 'rGHigoHVKHWUXM3N8E48Sb'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('6aBrMbsxZMPy3oVVxPEL8i', '2019-01-20 14:37:44', '2019-01-20 14:37:44', NULL, 'Contents', 1, 'Cartoons', '9ctyvaMXf1SrSd6frHJczk', 'rGHigoHVKHWUXM3N8E48Sb'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('csQ3291RiMw5xas6eiiiRN', '2019-01-20 14:37:44', '2019-01-20 14:37:44', NULL, 'Contents', 2, 'Cartoons', 'xmy6q2V4YGegp1Z6nTHgye', 'rGHigoHVKHWUXM3N8E48Sb'); +INSERT INTO article_attachments (id, createDate, updateDate, deleteDate, imageType, sequence, type, attachmentsId, articleId) VALUES ('3saojz8vGZzyuaSuNEopPM', '2019-01-20 14:37:44', '2019-01-20 14:37:44', NULL, 'Contents', 3, 'Cartoons', 'mXgJXnUvCEMZEaKQGdehV4', 'rGHigoHVKHWUXM3N8E48Sb'); + + + +-- Table: article_tag_list_article_tag +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('mUmh9QZmzvKtQ9PaKu9Q1h', '5gaWMsGZ43WdzXeAaMj7uH'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('mUmh9QZmzvKtQ9PaKu9Q1h', '6ok3iJuqN2zgMvVcdUFjom'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('mUmh9QZmzvKtQ9PaKu9Q1h', '8E5trRdsgSrjK5vZbcZ4xZ'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('j1Zfqatb51pfrrZxNYvvom', '4AptmmB3gsVGua7GRMZRDB'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('j1Zfqatb51pfrrZxNYvvom', '8E5trRdsgSrjK5vZbcZ4xZ'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('xceSGS1EU41aGJ3Wk7Vd6s', '4AptmmB3gsVGua7GRMZRDB'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('xceSGS1EU41aGJ3Wk7Vd6s', 'fWC7ggN2githpoTheCn1iY'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('pTE2b5XM1PDgnMAPFrnnQ7', '4AptmmB3gsVGua7GRMZRDB'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('pTE2b5XM1PDgnMAPFrnnQ7', '8E5trRdsgSrjK5vZbcZ4xZ'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('pTE2b5XM1PDgnMAPFrnnQ7', 'ku6dVqR5UR9WnhrFn6Xaar'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('oEPfFR9heKkLVoyumiGw2g', '4AptmmB3gsVGua7GRMZRDB'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('oEPfFR9heKkLVoyumiGw2g', 'a8QWzqra9pqhrYiZcCAUfU'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('iXBQZo5PmX7orF2bhyAv2M', '4AptmmB3gsVGua7GRMZRDB'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('iXBQZo5PmX7orF2bhyAv2M', 'jCUmXSMAePXFvYkz6iWVBE'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('iXBQZo5PmX7orF2bhyAv2M', 'qMCv5tAHLJF2ugKvrJRJGS'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('7jYNgHwoCzSQP4vWG7n2Jk', 'g2pnATRxziuYjkLgtgL5V1'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('7jYNgHwoCzSQP4vWG7n2Jk', '4AptmmB3gsVGua7GRMZRDB'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('7jYNgHwoCzSQP4vWG7n2Jk', '8E5trRdsgSrjK5vZbcZ4xZ'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('cUba4rXbxtVxTvf4FzvPks', '4kp1zgaqtgJN1iQaYaK62r'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('cUba4rXbxtVxTvf4FzvPks', '8rNvky5sdFJVDiLB7iLXfW'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('cUba4rXbxtVxTvf4FzvPks', 'ku6dVqR5UR9WnhrFn6Xaar'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('ewztryggNV86XdMNBqXvwi', 'seWs39EcaF2dJ2aBUVFCCN'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('ewztryggNV86XdMNBqXvwi', '4AptmmB3gsVGua7GRMZRDB'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('ewztryggNV86XdMNBqXvwi', '8E5trRdsgSrjK5vZbcZ4xZ'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('ewztryggNV86XdMNBqXvwi', '5gaWMsGZ43WdzXeAaMj7uH'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('ewztryggNV86XdMNBqXvwi', '6ok3iJuqN2zgMvVcdUFjom'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('mBYLJ9iafR7mhJrhMjgYpF', 'ku6dVqR5UR9WnhrFn6Xaar'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('8uZrh7igLpwT6EQRAQMVnm', '4AptmmB3gsVGua7GRMZRDB'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('8uZrh7igLpwT6EQRAQMVnm', '8E5trRdsgSrjK5vZbcZ4xZ'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('8uZrh7igLpwT6EQRAQMVnm', 'vKHN7Wwnw48Gee34RfqErt'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('8uZrh7igLpwT6EQRAQMVnm', '8rNvky5sdFJVDiLB7iLXfW'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('gu5TjGh9LEf6TdbDBXaPAh', '4AptmmB3gsVGua7GRMZRDB'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('gu5TjGh9LEf6TdbDBXaPAh', '5gaWMsGZ43WdzXeAaMj7uH'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('gu5TjGh9LEf6TdbDBXaPAh', '6ok3iJuqN2zgMvVcdUFjom'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('mWk8kyZvmFk74UquAZADP2', '5gaWMsGZ43WdzXeAaMj7uH'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('mWk8kyZvmFk74UquAZADP2', '6ok3iJuqN2zgMvVcdUFjom'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('mWk8kyZvmFk74UquAZADP2', 'jCUmXSMAePXFvYkz6iWVBE'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('mWk8kyZvmFk74UquAZADP2', 'qMCv5tAHLJF2ugKvrJRJGS'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('iCugVaoTNQ6L1iipgyvoWK', '4AptmmB3gsVGua7GRMZRDB'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('iCugVaoTNQ6L1iipgyvoWK', '8rNvky5sdFJVDiLB7iLXfW'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('wARdfMVh8qjoJCaX7S1Zgu', '4AptmmB3gsVGua7GRMZRDB'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('wARdfMVh8qjoJCaX7S1Zgu', '8E5trRdsgSrjK5vZbcZ4xZ'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('kEXD2jT8sKc4avJMMG8aN4', 'a8QWzqra9pqhrYiZcCAUfU'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('kEXD2jT8sKc4avJMMG8aN4', '2sfQs1qaLtSewon75S7kk9'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('oyQE4AG4xJF84RpW9xJBPP', '4AptmmB3gsVGua7GRMZRDB'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('oyQE4AG4xJF84RpW9xJBPP', '2sfQs1qaLtSewon75S7kk9'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('oyQE4AG4xJF84RpW9xJBPP', 'a8QWzqra9pqhrYiZcCAUfU'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('oyQE4AG4xJF84RpW9xJBPP', '8E5trRdsgSrjK5vZbcZ4xZ'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('9VtCkNFCqtn4qE4cxm8XfN', '4AptmmB3gsVGua7GRMZRDB'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('9VtCkNFCqtn4qE4cxm8XfN', '9Nc8RBHq3egN2fjQ4driFN'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('9VtCkNFCqtn4qE4cxm8XfN', '2sfQs1qaLtSewon75S7kk9'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('ebf5UncQ1k2XnAayBTF5CB', '4AptmmB3gsVGua7GRMZRDB'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('ebf5UncQ1k2XnAayBTF5CB', '2sfQs1qaLtSewon75S7kk9'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('ebf5UncQ1k2XnAayBTF5CB', '9C8W1raUp1sMeejQDhNLw8'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('rKKU99z7zsU7KswMNsAgry', '4AptmmB3gsVGua7GRMZRDB'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('rKKU99z7zsU7KswMNsAgry', 't69gnT45GZfsiDMZW5zSWV'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('rKKU99z7zsU7KswMNsAgry', 'isqfMJJzXeF3t9frdv8u8J'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('rKKU99z7zsU7KswMNsAgry', '2sfQs1qaLtSewon75S7kk9'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('VFMYivaGjYU4UwQ5GUe2U', '4AptmmB3gsVGua7GRMZRDB'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('VFMYivaGjYU4UwQ5GUe2U', '8rNvky5sdFJVDiLB7iLXfW'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('vhuPV3T2i2S1AHuyxkGvDf', '4AptmmB3gsVGua7GRMZRDB'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('vhuPV3T2i2S1AHuyxkGvDf', '8E5trRdsgSrjK5vZbcZ4xZ'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('vhuPV3T2i2S1AHuyxkGvDf', 'vKHN7Wwnw48Gee34RfqErt'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('hqBgydvz9ABLs5k2EMCbMu', '4AptmmB3gsVGua7GRMZRDB'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('hqBgydvz9ABLs5k2EMCbMu', '8rNvky5sdFJVDiLB7iLXfW'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('s2dodnS9CiaSYifrABx4Kc', '4AptmmB3gsVGua7GRMZRDB'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('s2dodnS9CiaSYifrABx4Kc', '8rNvky5sdFJVDiLB7iLXfW'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('j39YYVAD8USTkj5ND89J1S', '5gaWMsGZ43WdzXeAaMj7uH'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('j39YYVAD8USTkj5ND89J1S', '6ok3iJuqN2zgMvVcdUFjom'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('j39YYVAD8USTkj5ND89J1S', 'bxeaubXweEJNLrjG9Mq33R'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('j39YYVAD8USTkj5ND89J1S', '8LonhL4vQAyaWU4yAC3QEm'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('4jK72MTAvNgHVhLWeJNxhd', 'ku6dVqR5UR9WnhrFn6Xaar'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('fWbzvp7UwwYbTfSA4SuDGZ', 't118FC5D3sXxR6Bo7pvzTw'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('fWbzvp7UwwYbTfSA4SuDGZ', 'ku6dVqR5UR9WnhrFn6Xaar'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('mToog8Mvyi1c68cQA4xUTF', '4AptmmB3gsVGua7GRMZRDB'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('mToog8Mvyi1c68cQA4xUTF', '6czEzqptzyvB2NUoCqCYhj'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('mToog8Mvyi1c68cQA4xUTF', 'ku6dVqR5UR9WnhrFn6Xaar'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('tWu6fiwPNUzPbt66kQret3', '4kp1zgaqtgJN1iQaYaK62r'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('tWu6fiwPNUzPbt66kQret3', 'ku6dVqR5UR9WnhrFn6Xaar'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('tvLRXbp6UrQ9pG8Q3A6MGs', '4kp1zgaqtgJN1iQaYaK62r'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('tvLRXbp6UrQ9pG8Q3A6MGs', 'ku6dVqR5UR9WnhrFn6Xaar'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('uE26CHoMdji3dsAy4rkCaS', '4kp1zgaqtgJN1iQaYaK62r'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('uE26CHoMdji3dsAy4rkCaS', 'ku6dVqR5UR9WnhrFn6Xaar'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('9LiQotvX3JTqeyyeBHhjQ6', '4kp1zgaqtgJN1iQaYaK62r'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('9LiQotvX3JTqeyyeBHhjQ6', 'ku6dVqR5UR9WnhrFn6Xaar'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('op1gvTyzQ1pgSvMxBhf4A6', '4AptmmB3gsVGua7GRMZRDB'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('op1gvTyzQ1pgSvMxBhf4A6', 'wtctv46uWwdw17BtoAnzfV'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('op1gvTyzQ1pgSvMxBhf4A6', '62A6ftEeEBaeJpF5ffjVPs'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('op1gvTyzQ1pgSvMxBhf4A6', 'ku6dVqR5UR9WnhrFn6Xaar'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('op1gvTyzQ1pgSvMxBhf4A6', 'pfWdVmUaKd9VrsY7xtKSg3'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('pqzGnHsaMCR1zSh9SPoL3y', '4AptmmB3gsVGua7GRMZRDB'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('pqzGnHsaMCR1zSh9SPoL3y', 'ku6dVqR5UR9WnhrFn6Xaar'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('pqzGnHsaMCR1zSh9SPoL3y', 'wtctv46uWwdw17BtoAnzfV'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('pqzGnHsaMCR1zSh9SPoL3y', 'pfWdVmUaKd9VrsY7xtKSg3'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('dVtmXd12UC8nJcVER5vw9C', '4AptmmB3gsVGua7GRMZRDB'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('dVtmXd12UC8nJcVER5vw9C', 'ku6dVqR5UR9WnhrFn6Xaar'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('dVtmXd12UC8nJcVER5vw9C', 'pfWdVmUaKd9VrsY7xtKSg3'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('dVtmXd12UC8nJcVER5vw9C', 'kic3DCBBDUP4P7ARgoLYfz'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('dVtmXd12UC8nJcVER5vw9C', 'oafS2avgJSZmK5jpKjLJiy'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('dVtmXd12UC8nJcVER5vw9C', 'wtctv46uWwdw17BtoAnzfV'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('q4nJ6PDVnqYCtryFz6BP7b', '4AptmmB3gsVGua7GRMZRDB'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('q4nJ6PDVnqYCtryFz6BP7b', 'ku6dVqR5UR9WnhrFn6Xaar'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('q4nJ6PDVnqYCtryFz6BP7b', 'pfWdVmUaKd9VrsY7xtKSg3'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('q4nJ6PDVnqYCtryFz6BP7b', '8vqXMKzXSTkxUdXF4FxTk6'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('vgTJJbzvjQPZXPZ3w7dkFW', 'ku6dVqR5UR9WnhrFn6Xaar'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('vgTJJbzvjQPZXPZ3w7dkFW', '4AptmmB3gsVGua7GRMZRDB'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('vgTJJbzvjQPZXPZ3w7dkFW', 'pfWdVmUaKd9VrsY7xtKSg3'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('wNB8yqWJfNPD2JGsXwMUFg', 'ku6dVqR5UR9WnhrFn6Xaar'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('wNB8yqWJfNPD2JGsXwMUFg', '4AptmmB3gsVGua7GRMZRDB'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('wNB8yqWJfNPD2JGsXwMUFg', 'pfWdVmUaKd9VrsY7xtKSg3'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('bfF8CKJoNuMu7VX8bwdTtS', 'ku6dVqR5UR9WnhrFn6Xaar'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('bfF8CKJoNuMu7VX8bwdTtS', 'pfWdVmUaKd9VrsY7xtKSg3'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('bfF8CKJoNuMu7VX8bwdTtS', '4AptmmB3gsVGua7GRMZRDB'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('8RS5c64CgPPzbowZENByW4', '4AptmmB3gsVGua7GRMZRDB'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('8RS5c64CgPPzbowZENByW4', 'ku6dVqR5UR9WnhrFn6Xaar'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('8RS5c64CgPPzbowZENByW4', '4C7YJ4pcmc9uNcQJr5JHcT'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('kkdoBAy28SwxpKLDXjHVFH', '4AptmmB3gsVGua7GRMZRDB'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('kkdoBAy28SwxpKLDXjHVFH', '4C7YJ4pcmc9uNcQJr5JHcT'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('kkdoBAy28SwxpKLDXjHVFH', 'ku6dVqR5UR9WnhrFn6Xaar'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('tBFSQ1JWTA4ZAwFAtkngE8', 'ku6dVqR5UR9WnhrFn6Xaar'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('tBFSQ1JWTA4ZAwFAtkngE8', '6NyRHKcjjGYeH7Mi1avFyD'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('tBFSQ1JWTA4ZAwFAtkngE8', '4AptmmB3gsVGua7GRMZRDB'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('sukr923QasCjjpp3fD3jUT', 'ku6dVqR5UR9WnhrFn6Xaar'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('sukr923QasCjjpp3fD3jUT', 'w8Scdd3pref5VimcDRXQyd'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('sukr923QasCjjpp3fD3jUT', '6NyRHKcjjGYeH7Mi1avFyD'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('sukr923QasCjjpp3fD3jUT', '4AptmmB3gsVGua7GRMZRDB'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('upJf7vRA3Rkv7bo5syn56T', 'ku6dVqR5UR9WnhrFn6Xaar'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('upJf7vRA3Rkv7bo5syn56T', 'sAuYRmqnWqBatQvnbdHDPC'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('upJf7vRA3Rkv7bo5syn56T', '6NyRHKcjjGYeH7Mi1avFyD'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('upJf7vRA3Rkv7bo5syn56T', '4AptmmB3gsVGua7GRMZRDB'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('f9apdBMUNjcNaZakYAJ7Dn', 'sAuYRmqnWqBatQvnbdHDPC'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('f9apdBMUNjcNaZakYAJ7Dn', '6NyRHKcjjGYeH7Mi1avFyD'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('f9apdBMUNjcNaZakYAJ7Dn', '4AptmmB3gsVGua7GRMZRDB'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('f9apdBMUNjcNaZakYAJ7Dn', 'ku6dVqR5UR9WnhrFn6Xaar'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('pSrkFCFzCXwQziZyLYNAnV', '6NyRHKcjjGYeH7Mi1avFyD'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('pSrkFCFzCXwQziZyLYNAnV', 'ku6dVqR5UR9WnhrFn6Xaar'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('pSrkFCFzCXwQziZyLYNAnV', 'mM271wZfnJvdVMpRfSPBv9'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('pSrkFCFzCXwQziZyLYNAnV', '4AptmmB3gsVGua7GRMZRDB'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('b2ow7KxrVhnRn3KqWJ89ps', 'mM271wZfnJvdVMpRfSPBv9'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('b2ow7KxrVhnRn3KqWJ89ps', 'ku6dVqR5UR9WnhrFn6Xaar'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('b2ow7KxrVhnRn3KqWJ89ps', '6NyRHKcjjGYeH7Mi1avFyD'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('b2ow7KxrVhnRn3KqWJ89ps', '4AptmmB3gsVGua7GRMZRDB'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('wnAbzbxa76uUXrZzGTrKKE', '6NyRHKcjjGYeH7Mi1avFyD'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('wnAbzbxa76uUXrZzGTrKKE', 'mM271wZfnJvdVMpRfSPBv9'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('wnAbzbxa76uUXrZzGTrKKE', 'ku6dVqR5UR9WnhrFn6Xaar'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('wnAbzbxa76uUXrZzGTrKKE', '4AptmmB3gsVGua7GRMZRDB'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('dYHKWNPW296FaaZ6KE6wHv', 'mM271wZfnJvdVMpRfSPBv9'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('dYHKWNPW296FaaZ6KE6wHv', '6NyRHKcjjGYeH7Mi1avFyD'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('dYHKWNPW296FaaZ6KE6wHv', 'ku6dVqR5UR9WnhrFn6Xaar'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('dYHKWNPW296FaaZ6KE6wHv', '4AptmmB3gsVGua7GRMZRDB'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('wR2TgVbWiijMCMhvXVHQYC', 'mM271wZfnJvdVMpRfSPBv9'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('wR2TgVbWiijMCMhvXVHQYC', '6NyRHKcjjGYeH7Mi1avFyD'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('wR2TgVbWiijMCMhvXVHQYC', 'ku6dVqR5UR9WnhrFn6Xaar'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('wR2TgVbWiijMCMhvXVHQYC', '4AptmmB3gsVGua7GRMZRDB'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('jE9fy92oUrzvDZ4jcrqq1S', '6NyRHKcjjGYeH7Mi1avFyD'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('jE9fy92oUrzvDZ4jcrqq1S', 'ku6dVqR5UR9WnhrFn6Xaar'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('jE9fy92oUrzvDZ4jcrqq1S', '4AptmmB3gsVGua7GRMZRDB'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('drWD6ENe5AJsm4JZbyKmks', '6NyRHKcjjGYeH7Mi1avFyD'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('drWD6ENe5AJsm4JZbyKmks', 'ku6dVqR5UR9WnhrFn6Xaar'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('drWD6ENe5AJsm4JZbyKmks', '4AptmmB3gsVGua7GRMZRDB'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('5nfgFzH5wVjEbWdmT4Hvii', '6NyRHKcjjGYeH7Mi1avFyD'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('5nfgFzH5wVjEbWdmT4Hvii', 'ku6dVqR5UR9WnhrFn6Xaar'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('5nfgFzH5wVjEbWdmT4Hvii', '4AptmmB3gsVGua7GRMZRDB'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('dGBeqcBgDq6BUtd8mdqAgc', '6NyRHKcjjGYeH7Mi1avFyD'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('dGBeqcBgDq6BUtd8mdqAgc', 'ku6dVqR5UR9WnhrFn6Xaar'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('dGBeqcBgDq6BUtd8mdqAgc', '4AptmmB3gsVGua7GRMZRDB'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('gtie3aYUVRPuAk5bTr5ZoD', '6NyRHKcjjGYeH7Mi1avFyD'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('gtie3aYUVRPuAk5bTr5ZoD', 'ku6dVqR5UR9WnhrFn6Xaar'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('gtie3aYUVRPuAk5bTr5ZoD', '4AptmmB3gsVGua7GRMZRDB'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('fPDbmwj79iogkmzhWz51PB', '6NyRHKcjjGYeH7Mi1avFyD'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('fPDbmwj79iogkmzhWz51PB', 'ku6dVqR5UR9WnhrFn6Xaar'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('fPDbmwj79iogkmzhWz51PB', '4AptmmB3gsVGua7GRMZRDB'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('pQ1UMbidNuGUwHkxVoCXgb', '6NyRHKcjjGYeH7Mi1avFyD'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('pQ1UMbidNuGUwHkxVoCXgb', 'ku6dVqR5UR9WnhrFn6Xaar'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('pQ1UMbidNuGUwHkxVoCXgb', '4AptmmB3gsVGua7GRMZRDB'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('pQ1UMbidNuGUwHkxVoCXgb', '58X3Q8Jijrr8cV3zDnwAAU'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('o4bz6n39BtWkDLWMD2UM12', 'a8QWzqra9pqhrYiZcCAUfU'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('bawdAxFjUs1HpRZheshTNe', '5gaWMsGZ43WdzXeAaMj7uH'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('bawdAxFjUs1HpRZheshTNe', '6ok3iJuqN2zgMvVcdUFjom'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('2JG5vg5NY9NExyZFck2xZv', 'eASCfQyeLoUnrJohjEd2EV'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('2JG5vg5NY9NExyZFck2xZv', '8E5trRdsgSrjK5vZbcZ4xZ'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('2JG5vg5NY9NExyZFck2xZv', 'xu36RwDcXW6QN7k4ZrWsXL'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('2JG5vg5NY9NExyZFck2xZv', 'isqfMJJzXeF3t9frdv8u8J'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('qmcfEynLBQ3exw4QYQG2yn', 'eASCfQyeLoUnrJohjEd2EV'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('qmcfEynLBQ3exw4QYQG2yn', 't69gnT45GZfsiDMZW5zSWV'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('qmcfEynLBQ3exw4QYQG2yn', 'isqfMJJzXeF3t9frdv8u8J'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('bwW3gxH9AP37rzQpvVPurD', 'a8QWzqra9pqhrYiZcCAUfU'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('bwW3gxH9AP37rzQpvVPurD', '8E5trRdsgSrjK5vZbcZ4xZ'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('rGHigoHVKHWUXM3N8E48Sb', '4AptmmB3gsVGua7GRMZRDB'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('rGHigoHVKHWUXM3N8E48Sb', 'duf2KPwZ4HQZ6TNN2zf2Eu'); +INSERT INTO article_tag_list_article_tag (articleId, articleTagId) VALUES ('rGHigoHVKHWUXM3N8E48Sb', 'tdR2dhcDcmtQfXwcyzdWa2'); + +-- Table: user_analysis_favor_tag_article_tag_list_article_tag +INSERT INTO user_analysis_favor_tag_article_tag_list_article_tag (userAnalysisFavorTagId, articleTagId) VALUES ('kMP9DqKYEUTxBMc9bDGZbR', '4kp1zgaqtgJN1iQaYaK62r'); +INSERT INTO user_analysis_favor_tag_article_tag_list_article_tag (userAnalysisFavorTagId, articleTagId) VALUES ('oLxgvzjYScqeQgSFzgAVow', '4AptmmB3gsVGua7GRMZRDB'); +INSERT INTO user_analysis_favor_tag_article_tag_list_article_tag (userAnalysisFavorTagId, articleTagId) VALUES ('7RFmmh7AR24nxkymdy97oe', 'oVhoJA1AXsnRhcqvf2MdAe'); +INSERT INTO user_analysis_favor_tag_article_tag_list_article_tag (userAnalysisFavorTagId, articleTagId) VALUES ('4kULUVf5kDfKZbsjDfqGP4', 'a8QWzqra9pqhrYiZcCAUfU'); +INSERT INTO user_analysis_favor_tag_article_tag_list_article_tag (userAnalysisFavorTagId, articleTagId) VALUES ('dzA8nZCqcm76SaXiWnfrhp', 'bjc7Esp7zAZdaGjLBkQUuF'); +INSERT INTO user_analysis_favor_tag_article_tag_list_article_tag (userAnalysisFavorTagId, articleTagId) VALUES ('8ZMk8fbv7tmMhxbmyK6WDY', 'eASCfQyeLoUnrJohjEd2EV'); +INSERT INTO user_analysis_favor_tag_article_tag_list_article_tag (userAnalysisFavorTagId, articleTagId) VALUES ('jG916vsMhCrH8aGpLqKTUC', '8E5trRdsgSrjK5vZbcZ4xZ'); +INSERT INTO user_analysis_favor_tag_article_tag_list_article_tag (userAnalysisFavorTagId, articleTagId) VALUES ('8S6ZmwUPhyfa7WbPC1H8U7', '5gaWMsGZ43WdzXeAaMj7uH'); +INSERT INTO user_analysis_favor_tag_article_tag_list_article_tag (userAnalysisFavorTagId, articleTagId) VALUES ('8S6ZmwUPhyfa7WbPC1H8U7', '6ok3iJuqN2zgMvVcdUFjom'); +INSERT INTO user_analysis_favor_tag_article_tag_list_article_tag (userAnalysisFavorTagId, articleTagId) VALUES ('dTbWc3W4Me5Lz6aNNEQ77w', 'vKHN7Wwnw48Gee34RfqErt'); +INSERT INTO user_analysis_favor_tag_article_tag_list_article_tag (userAnalysisFavorTagId, articleTagId) VALUES ('rRh6SWQRgbHGUVyVgCQYEX', 'jg2q8NwSx5GvGZtMpSCuvo'); +INSERT INTO user_analysis_favor_tag_article_tag_list_article_tag (userAnalysisFavorTagId, articleTagId) VALUES ('rRh6SWQRgbHGUVyVgCQYEX', 'tdR2dhcDcmtQfXwcyzdWa2'); +INSERT INTO user_analysis_favor_tag_article_tag_list_article_tag (userAnalysisFavorTagId, articleTagId) VALUES ('76L46FhcDMNXF1B5Cp695E', 'iBSWXWYGZPqhnpJVyNsymX'); +INSERT INTO user_analysis_favor_tag_article_tag_list_article_tag (userAnalysisFavorTagId, articleTagId) VALUES ('ooRdydQFtmk72tT67wt58Q', 't69gnT45GZfsiDMZW5zSWV'); +INSERT INTO user_analysis_favor_tag_article_tag_list_article_tag (userAnalysisFavorTagId, articleTagId) VALUES ('ooRdydQFtmk72tT67wt58Q', 'isqfMJJzXeF3t9frdv8u8J'); +INSERT INTO user_analysis_favor_tag_article_tag_list_article_tag (userAnalysisFavorTagId, articleTagId) VALUES ('wm7QNPFMwVLiuFpGv4jqx7', 'sRskhhDchmcEqZmDvxr5Gk'); +INSERT INTO user_analysis_favor_tag_article_tag_list_article_tag (userAnalysisFavorTagId, articleTagId) VALUES ('gc3CqGsLsEtGkRJUyVrJbY', '8rNvky5sdFJVDiLB7iLXfW'); +INSERT INTO user_analysis_favor_tag_article_tag_list_article_tag (userAnalysisFavorTagId, articleTagId) VALUES ('PCq2hi8prf4aDsjYnj1CR', 'ku6dVqR5UR9WnhrFn6Xaar'); +INSERT INTO user_analysis_favor_tag_article_tag_list_article_tag (userAnalysisFavorTagId, articleTagId) VALUES ('w5dmX6YXhZtyfWzZSKtm72', 'jCUmXSMAePXFvYkz6iWVBE'); +INSERT INTO user_analysis_favor_tag_article_tag_list_article_tag (userAnalysisFavorTagId, articleTagId) VALUES ('w5dmX6YXhZtyfWzZSKtm72', 'qMCv5tAHLJF2ugKvrJRJGS'); +INSERT INTO user_analysis_favor_tag_article_tag_list_article_tag (userAnalysisFavorTagId, articleTagId) VALUES ('77xreQyZSafZkTAtBtmGAE', 'sw3kASgddqbPVc3EgEZ9fE'); +INSERT INTO user_analysis_favor_tag_article_tag_list_article_tag (userAnalysisFavorTagId, articleTagId) VALUES ('77xreQyZSafZkTAtBtmGAE', 'tmdwkv3EcvwzqEdy84FkG3'); +INSERT INTO user_analysis_favor_tag_article_tag_list_article_tag (userAnalysisFavorTagId, articleTagId) VALUES ('6KD3Qemnpqek3DZRTjqHMy', '8LonhL4vQAyaWU4yAC3QEm'); +INSERT INTO user_analysis_favor_tag_article_tag_list_article_tag (userAnalysisFavorTagId, articleTagId) VALUES ('oXdLoR1p879BxFyNHeFeUM', '6czEzqptzyvB2NUoCqCYhj'); +INSERT INTO user_analysis_favor_tag_article_tag_list_article_tag (userAnalysisFavorTagId, articleTagId) VALUES ('sRoXWJUYsRZbpbyTN3YFTR', 'pTARgrRWK3yHs17qf5sfmj'); diff --git a/src/server/test/storage.zip b/src/server/test/storage.zip new file mode 100755 index 0000000..3fecf4a Binary files /dev/null and b/src/server/test/storage.zip differ diff --git a/src/shared/advertisements/model/advertisements.model.ts b/src/shared/advertisements/model/advertisements.model.ts new file mode 100755 index 0000000..7f907f2 --- /dev/null +++ b/src/shared/advertisements/model/advertisements.model.ts @@ -0,0 +1,19 @@ +/** + * 파 일 명: advertisements.model.ts + * 작성일자: 2019-01-20 + * 작 성 자: 박병준 + * 설 명: Advertisements model을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Base } from '../../common/model/base.model'; + +export interface Advertisements extends Base { + title?: string; + contents?: string; + startDate?: Date; + endDate?: Date; + url?: string; +} diff --git a/src/shared/article/model/article-attachments.model.ts b/src/shared/article/model/article-attachments.model.ts new file mode 100755 index 0000000..94edc71 --- /dev/null +++ b/src/shared/article/model/article-attachments.model.ts @@ -0,0 +1,16 @@ +/** + * 파 일 명: article-attachments.model.ts + * 작성일자: 2018-12-13 + * 작 성 자: 박병준 + * 설 명: Article Attachments model을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { AttachmentsRelation } from '../../attachments/model/attachments-relation.model'; +import { ArticleImage } from '../type/article-image.type'; + +export interface ArticleAttachments extends AttachmentsRelation { + imageType?: ArticleImage; +} diff --git a/src/shared/article/model/article-best-comments-tag.model.ts b/src/shared/article/model/article-best-comments-tag.model.ts new file mode 100755 index 0000000..a3a44cc --- /dev/null +++ b/src/shared/article/model/article-best-comments-tag.model.ts @@ -0,0 +1,20 @@ + +/** + * 파 일 명: article-attachments.model.ts + * 작성일자: 2018-12-13 + * 작 성 자: 박병준 + * 설 명: Article Attachments model을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Base } from '../../common/model/base.model'; +import { Article } from './article.model'; +import { MetaCommentsTag } from '../../meta/model/meta-comments-tag.model'; + +export interface ArticleBestCommentsTag extends Base { + article?: Article; + metaCommentsTag?: MetaCommentsTag; + count?: number; +} diff --git a/src/shared/article/model/article-bookmarks.model.ts b/src/shared/article/model/article-bookmarks.model.ts new file mode 100755 index 0000000..4e4a296 --- /dev/null +++ b/src/shared/article/model/article-bookmarks.model.ts @@ -0,0 +1,21 @@ +/** + * 파 일 명: article-bookmarks.model.ts + * 작성일자: 2019-01-18 + * 작 성 자: 윤대훈 + * 설 명: ArticleBookmarks model을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Base } from '../../common/model/base.model'; +import { Article } from './article.model'; +import { User } from 'src/shared/user/model/user.model'; + + + + +export interface ArticleBookmarks extends Base { + user?: User; + articleId?: string; +} diff --git a/src/shared/article/model/article-comments-tag.model.ts b/src/shared/article/model/article-comments-tag.model.ts new file mode 100755 index 0000000..dd7290b --- /dev/null +++ b/src/shared/article/model/article-comments-tag.model.ts @@ -0,0 +1,21 @@ + +/** + * 파 일 명: article-attachments.model.ts + * 작성일자: 2018-12-13 + * 작 성 자: 박병준 + * 설 명: Article Attachments model을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Base } from '../../common/model/base.model'; +import { Article } from './article.model'; +import { MetaCommentsTag } from '../../meta/model/meta-comments-tag.model'; +import { User } from '../../user/model/user.model'; + +export interface ArticleCommentsTag extends Base { + article?: Article; + metaCommentsTag?: MetaCommentsTag; + user?: User; +} diff --git a/src/shared/article/model/article-like.model.ts b/src/shared/article/model/article-like.model.ts new file mode 100755 index 0000000..9098854 --- /dev/null +++ b/src/shared/article/model/article-like.model.ts @@ -0,0 +1,20 @@ +/** + * 파 일 명: article-series.model.ts + * 작성일자: 2018-12-28 + * 작 성 자: 박병준 + * 설 명: Article series model을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Base } from '../../common/model/base.model'; + +import { User } from '../../user/model/user.model'; +import { Article } from './article.model'; + +export interface ArticleLike extends Base { + article?: Article; + user?: User; + count?: number; +} diff --git a/src/shared/article/model/article-series.model.ts b/src/shared/article/model/article-series.model.ts new file mode 100755 index 0000000..fdd82f3 --- /dev/null +++ b/src/shared/article/model/article-series.model.ts @@ -0,0 +1,21 @@ +/** + * 파 일 명: article-series.model.ts + * 작성일자: 2018-12-28 + * 작 성 자: 박병준 + * 설 명: Article series model을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Base } from '../../common/model/base.model'; + +import { User } from '../../user/model/user.model'; +import { Article } from './article.model'; + +export interface ArticleSeries extends Base { + title?: string; + description?: string; + user?: User; + articleList?: Article[]; +} diff --git a/src/shared/article/model/article-tag.model.ts b/src/shared/article/model/article-tag.model.ts new file mode 100755 index 0000000..c11704e --- /dev/null +++ b/src/shared/article/model/article-tag.model.ts @@ -0,0 +1,16 @@ +/** + * 파 일 명: article-tag.model.ts + * 작성일자: 2018-12-13 + * 작 성 자: 박병준 + * 설 명: Article Tag model을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Article } from './article.model'; +import { Tag } from '../../tag/model/tag.model'; + +export interface ArticleTag extends Tag { + articleList?: Article[]; +} diff --git a/src/shared/article/model/article.model.ts b/src/shared/article/model/article.model.ts new file mode 100755 index 0000000..b11f953 --- /dev/null +++ b/src/shared/article/model/article.model.ts @@ -0,0 +1,46 @@ +/** + * 파 일 명: article.model.ts + * 작성일자: 2018-12-13 + * 작 성 자: 박병준 + * 설 명: Article model을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + * 수정일시: 2019-01-14 + * 수 정 자: 최지련 + * 수정내용: publicYN 필드 추가 + * 수정일시: 2018-12-28 + * 수 정 자: 최지련 + * 수정내용: ageLimit 필드 추가 + * (연령제한 설정값 - 00 : 전연령, 01 : 가벼운 성적 묘사, 10 : 폭력, 잔인, 크로테스한 묘사, 11 - 01, 10 동시 선택) + * 수정일시: 2018-12-27 + * 수 정 자: 최지련 + * 수정내용: original 필드 추가 (오리지널 작품 여부) + */ +import { Base } from '../../common/model/base.model'; + +import { User } from '../../user/model/user.model'; +import { ArticleTag } from './article-tag.model'; +import { ArticleSeries } from './article-series.model'; +import { ArticleType } from '../type/article-type.type'; +import { AgeLimitType } from '../../common/type/ageLimit.type'; +import { ArticleCommentsTag } from './article-comments-tag.model'; +import { ArticleLike } from './article-like.model'; + +export interface Article extends Base { + type?: ArticleType; + title?: string; + description?: string; + original?: boolean; + ageLimit?: AgeLimitType; + publicYN?: boolean; + user?: User; + articleSeries?: ArticleSeries; + originalTag?: ArticleTag; + tagList?: ArticleTag[]; + commentsTagList?: ArticleCommentsTag[]; + commentsCount?: number; + viewCount?: number; + likeList?: ArticleLike[]; +} diff --git a/src/shared/article/model/article.mongodb.model.ts b/src/shared/article/model/article.mongodb.model.ts new file mode 100755 index 0000000..27dfa73 --- /dev/null +++ b/src/shared/article/model/article.mongodb.model.ts @@ -0,0 +1,21 @@ +/** + * 파 일 명: article.mongodb.model.ts + * 작성일자: 2018-12-13 + * 작 성 자: 박병준 + * 설 명: Article mongodb model을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Base } from '../../common/model/base.model'; + +import { Cartoons } from '../../cartoons/model/cartoons.model'; +import { Illustrations } from '../../illustrations/model/illustrations.model'; +import { Novel } from '../../novel/model/novel.model'; + +export interface ArticleMongodb extends Base { + type?: string; + userId: string; + article: Cartoons | Illustrations | Novel; +} diff --git a/src/shared/article/type/article-display.type.ts b/src/shared/article/type/article-display.type.ts new file mode 100755 index 0000000..ec66780 --- /dev/null +++ b/src/shared/article/type/article-display.type.ts @@ -0,0 +1,16 @@ +/** + * 파 일 명: article-display.type.ts + * 작성일자: 2019-01-30 + * 작 성 자: 박병준 + * 설 명: Article의 출력 유형을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +export enum ArticleDisplay { + Card = 'card', + Tile2 = 'tile2', + Tile3 = 'tile3', + Viewer = 'viewer', +} diff --git a/src/shared/article/type/article-image.type.ts b/src/shared/article/type/article-image.type.ts new file mode 100755 index 0000000..c2a5b97 --- /dev/null +++ b/src/shared/article/type/article-image.type.ts @@ -0,0 +1,14 @@ +/** + * 파 일 명: article-image.type.ts + * 작성일자: 2018-12-24 + * 작 성 자: 박병준 + * 설 명: Article의 이미지 유형을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +export enum ArticleImage { + Cover = 'Cover', + Contents = 'Contents', +} diff --git a/src/shared/article/type/article-share.type.ts b/src/shared/article/type/article-share.type.ts new file mode 100755 index 0000000..33e1e6d --- /dev/null +++ b/src/shared/article/type/article-share.type.ts @@ -0,0 +1,17 @@ +/** + * 파 일 명: article-share.type.ts + * 작성일자: 2019-01-16 + * 작 성 자: 최지련 + * 설 명: Article Share 유형을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +export enum ArticleShare { + Facebook = 'Facebook', + Twitter = 'Twitter', + GooglePlus = 'GooglePlus', + Email = 'Email', + CopyLink = 'CopyLink', +} diff --git a/src/shared/article/type/article-type.type.ts b/src/shared/article/type/article-type.type.ts new file mode 100755 index 0000000..b6078d7 --- /dev/null +++ b/src/shared/article/type/article-type.type.ts @@ -0,0 +1,16 @@ +/** + * 파 일 명: article-type.type.ts + * 작성일자: 2018-12-24 + * 작 성 자: 박병준 + * 설 명: article 유형을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +export enum ArticleType { + NONE = 'NONE', + Illustrations = 'Illustrations', + Cartoons = 'Cartoons', + Novel = 'Novel', +} diff --git a/src/shared/attachments/model/attachments-relation.model.ts b/src/shared/attachments/model/attachments-relation.model.ts new file mode 100755 index 0000000..842765e --- /dev/null +++ b/src/shared/attachments/model/attachments-relation.model.ts @@ -0,0 +1,16 @@ +/** + * 파 일 명: attachments-relation.model.ts + * 작성일자: 2018-12-10 + * 작 성 자: 박병준 + * 설 명: Attachments 관계 model을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Base } from '../../common/model/base.model'; +import { Attachments } from './attachments.model'; + +export interface AttachmentsRelation extends Base { + attachments?: Attachments; +} diff --git a/src/shared/attachments/model/attachments.model.ts b/src/shared/attachments/model/attachments.model.ts new file mode 100755 index 0000000..5584066 --- /dev/null +++ b/src/shared/attachments/model/attachments.model.ts @@ -0,0 +1,19 @@ +/** + * 파 일 명: attachments.model.ts + * 작성일자: 2018-12-23 + * 작 성 자: 박병준 + * 설 명: Attachments model을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Base } from '../../common/model/base.model'; +import { User } from '../../user/model/user.model'; + +export interface Attachments extends Base { + name?: string; + mimeType?: string; + size?: number; + // user?: User; +} diff --git a/src/shared/attachments/type/image-config.type.ts b/src/shared/attachments/type/image-config.type.ts new file mode 100755 index 0000000..111efb7 --- /dev/null +++ b/src/shared/attachments/type/image-config.type.ts @@ -0,0 +1,6 @@ +import { ImageValidation, ImageThumbnails } from './image.type'; + +export interface ImageConfig { + validation: ImageValidation; + thumbnails: Map; +} diff --git a/src/shared/attachments/type/image.type.ts b/src/shared/attachments/type/image.type.ts new file mode 100755 index 0000000..1f40f57 --- /dev/null +++ b/src/shared/attachments/type/image.type.ts @@ -0,0 +1,12 @@ +export interface ImageDemetion { + width: number; + height: number; +} + +// tslint:disable-next-line:no-empty-interface +export interface ImageValidation extends ImageDemetion { +} + +// tslint:disable-next-line:no-empty-interface +export interface ImageThumbnails extends ImageDemetion { +} diff --git a/src/shared/cartoons/model/cartoons-attachments.model.ts b/src/shared/cartoons/model/cartoons-attachments.model.ts new file mode 100755 index 0000000..f3c9faf --- /dev/null +++ b/src/shared/cartoons/model/cartoons-attachments.model.ts @@ -0,0 +1,17 @@ +/** + * 파 일 명: cartoons-attachments.model.ts + * 작성일자: 2018-12-26 + * 작 성 자: 박병준 + * 설 명: Cartoons Attachments model을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { ArticleAttachments } from '../../article/model/article-attachments.model'; +import { Cartoons } from './cartoons.model'; + +export interface CartoonsAttachments extends ArticleAttachments { + article?: Cartoons; + sequence?: number; +} diff --git a/src/shared/cartoons/model/cartoons-series.model.ts b/src/shared/cartoons/model/cartoons-series.model.ts new file mode 100755 index 0000000..d3793c5 --- /dev/null +++ b/src/shared/cartoons/model/cartoons-series.model.ts @@ -0,0 +1,15 @@ +/** + * 파 일 명: cartoons-series.model.ts + * 작성일자: 2018-12-31 + * 작 성 자: 박병준 + * 설 명: Cartoons series model을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { ArticleSeries } from '../../article/model/article-series.model'; + +// tslint:disable-next-line:no-empty-interface +export interface CartoonsSeries extends ArticleSeries { +} diff --git a/src/shared/cartoons/model/cartoons.model.ts b/src/shared/cartoons/model/cartoons.model.ts new file mode 100755 index 0000000..1b85a76 --- /dev/null +++ b/src/shared/cartoons/model/cartoons.model.ts @@ -0,0 +1,16 @@ +/** + * 파 일 명: cartoons.model.ts + * 작성일자: 2018-12-13 + * 작 성 자: 박병준 + * 설 명: Cartoons model을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Article } from '../../article/model/article.model'; +import { CartoonsAttachments } from './cartoons-attachments.model'; + +export interface Cartoons extends Article { + attachmentsList?: CartoonsAttachments[]; +} diff --git a/src/shared/common/model/base.model.ts b/src/shared/common/model/base.model.ts new file mode 100755 index 0000000..0794b8a --- /dev/null +++ b/src/shared/common/model/base.model.ts @@ -0,0 +1,17 @@ +/** + * 파 일 명: base.model.ts + * 작성일자: 2018-12-26 + * 작 성 자: 박병준 + * 설 명: Base model을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +export interface Base { + id?: string; + + createDate?: Date; + updateDate?: Date; + deleteDate?: Date; +} diff --git a/src/shared/common/type/ageLimit.type.ts b/src/shared/common/type/ageLimit.type.ts new file mode 100755 index 0000000..9597f9c --- /dev/null +++ b/src/shared/common/type/ageLimit.type.ts @@ -0,0 +1,15 @@ +/** + * 파 일 명: ageLimit.type.ts + * 작성일자: 2019-01-15 + * 작 성 자: 최지련 + * 설 명: 연령제한 유형을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +export enum AgeLimitType { + ALL = 0, + SOFT = 1, + HARD = 2, +} diff --git a/src/shared/common/type/lang.type.ts b/src/shared/common/type/lang.type.ts new file mode 100755 index 0000000..c2d897e --- /dev/null +++ b/src/shared/common/type/lang.type.ts @@ -0,0 +1,15 @@ +/** + * 파 일 명: lang.type.ts + * 작성일자: 2018-12-28 + * 작 성 자: 박병준 + * 설 명: 언어 유형을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +export enum LangType { + KO = 'KO', + JP = 'JP', + EN = 'EN', +} diff --git a/src/shared/common/util/date.util.ts b/src/shared/common/util/date.util.ts new file mode 100755 index 0000000..975e3f1 --- /dev/null +++ b/src/shared/common/util/date.util.ts @@ -0,0 +1,5 @@ +export class DateUtil { + public static nowUTC(): Date { + return new Date((new Date()).toUTCString()); + } +} diff --git a/src/shared/contents/model/contents-display-option.model.ts b/src/shared/contents/model/contents-display-option.model.ts new file mode 100755 index 0000000..4ef4e4f --- /dev/null +++ b/src/shared/contents/model/contents-display-option.model.ts @@ -0,0 +1,28 @@ +/** + * 파 일 명: contents-display-option.model.ts + * 작성일자: 2018-12-26 + * 작 성 자: 박병준 + * 설 명: Contents model을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { ContentsDisplay } from '../type/contents-display.type'; + +export interface ContentsDisplayOption { + type?: ContentsDisplay; + option?: ContentsDisplayCardOption | ContentsDisplayGridOption | ContentsDisplayListOption; +} + +// tslint:disable-next-line:no-empty-interface +export interface ContentsDisplayCardOption { +} + +export interface ContentsDisplayGridOption { + colSize: number; +} + +// tslint:disable-next-line:no-empty-interface +export interface ContentsDisplayListOption { +} diff --git a/src/shared/contents/model/contents-size.model.ts b/src/shared/contents/model/contents-size.model.ts new file mode 100755 index 0000000..13f722a --- /dev/null +++ b/src/shared/contents/model/contents-size.model.ts @@ -0,0 +1,18 @@ +/** + * 파 일 명: contents-size.model.ts + * 작성일자: 2018-12-26 + * 작 성 자: 박병준 + * 설 명: Contents size model을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { ContentsType } from '../type/contents-type.type'; +import { ContentsDisplayOption } from './contents-display-option.model'; + +export interface ContentsSize { + type: ContentsType; + displayOption?: ContentsDisplayOption; + size: number; +} diff --git a/src/shared/contents/model/contents.model.ts b/src/shared/contents/model/contents.model.ts new file mode 100755 index 0000000..fb768c5 --- /dev/null +++ b/src/shared/contents/model/contents.model.ts @@ -0,0 +1,18 @@ +/** + * 파 일 명: contents.model.ts + * 작성일자: 2018-12-26 + * 작 성 자: 박병준 + * 설 명: Contents model을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { ContentsType, ContentsDataType } from '../type/contents-type.type'; +import { Base } from '../../common/model/base.model'; + +export interface Contents extends Base { + type?: ContentsType; + display?: any; + data?: ContentsDataType; +} diff --git a/src/shared/contents/type/contents-display.type.ts b/src/shared/contents/type/contents-display.type.ts new file mode 100755 index 0000000..958ac9f --- /dev/null +++ b/src/shared/contents/type/contents-display.type.ts @@ -0,0 +1,15 @@ +/** + * 파 일 명: contents-display.type.ts + * 작성일자: 2019-01-20 + * 작 성 자: 박병준 + * 설 명: contents display 유형을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +export enum ContentsDisplay { + Card = 'Card', + Grid = 'Grid', + List = 'List', +} diff --git a/src/shared/contents/type/contents-request-user.type.ts b/src/shared/contents/type/contents-request-user.type.ts new file mode 100755 index 0000000..d4dfaca --- /dev/null +++ b/src/shared/contents/type/contents-request-user.type.ts @@ -0,0 +1,14 @@ +/** + * 파 일 명: contents-request-user.type.ts + * 작성일자: 2019-01-20 + * 작 성 자: 박병준 + * 설 명: contents request user 유형을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +export enum ContentsRequestUser { + Guest = 'guest', + User = 'user', +} diff --git a/src/shared/contents/type/contents-request.type.ts b/src/shared/contents/type/contents-request.type.ts new file mode 100755 index 0000000..f40a475 --- /dev/null +++ b/src/shared/contents/type/contents-request.type.ts @@ -0,0 +1,15 @@ +/** + * 파 일 명: contents-request.type.ts + * 작성일자: 2019-01-20 + * 작 성 자: 박병준 + * 설 명: contents request 유형을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +export enum ContentsRequest { + Recommendations = 'recommendations', + Following = 'following', + Search = 'search', +} diff --git a/src/shared/contents/type/contents-search.type.ts b/src/shared/contents/type/contents-search.type.ts new file mode 100755 index 0000000..9b9467d --- /dev/null +++ b/src/shared/contents/type/contents-search.type.ts @@ -0,0 +1,14 @@ +/** + * 파 일 명: contents-search.type.ts + * 작성일자: 2019-01-20 + * 작 성 자: 박병준 + * 설 명: contents search 유형을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +export enum ContentsSearch { + Author = 'author', + Tag = 'tag', +} diff --git a/src/shared/contents/type/contents-type.type.ts b/src/shared/contents/type/contents-type.type.ts new file mode 100755 index 0000000..577d8b4 --- /dev/null +++ b/src/shared/contents/type/contents-type.type.ts @@ -0,0 +1,32 @@ +import { ArticleMongodb } from '../../article/model/article.mongodb.model'; +import { User } from '../../user/model/user.model'; +import { Advertisements } from '../../advertisements/model/advertisements.model'; +import { UserSupportEvent } from '../../user-support/model/user-support-event.model'; + +/** + * 파 일 명: contents-type.type.ts + * 작성일자: 2019-01-20 + * 작 성 자: 박병준 + * 설 명: contents 유형을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +export enum ContentsType { + Article = 'Article', + FeaturedAuthor = 'FeaturedAuthor', + Advertisements = 'Advertisements', + Event = 'Event', +} + +export type ContentsDataType = + | ArticleMongodb + | ArticleMongodb[] + | User + | User[] + | Advertisements + | Advertisements[] + | UserSupportEvent + | UserSupportEvent[] + ; diff --git a/src/shared/contents/util/contents.util.ts b/src/shared/contents/util/contents.util.ts new file mode 100755 index 0000000..3ac5aa4 --- /dev/null +++ b/src/shared/contents/util/contents.util.ts @@ -0,0 +1,53 @@ +import { ContentsType } from '../type/contents-type.type'; +import { ContentsSize } from '../model/contents-size.model'; +import { Contents } from '../model/contents.model'; + +export class ContentsUtil { + public static getSizeByContentsSize(contentsType: ContentsType, contentsSizeList: ContentsSize[]): number { + if (!contentsSizeList || 0 === contentsSizeList.length) { + return 0; + } + let size = 0; + for (const contentsSize of contentsSizeList) { + if (contentsType === contentsSize.type) { + size += contentsSize.size; + } + } + return size; + } + + public static getSizeByContents(contentsType: ContentsType, contentsList: Contents[]): number { + if (!contentsList || 0 === contentsList.length) { + return 0; + } + let size = 0; + for (const contents of contentsList) { + if (contentsType === contents.type) { + if (!contents.data) { + continue; + } + if (Array.isArray(contents.data)) { + size += (contents.data as Array).length; + } else { + size++; + } + } + } + return size; + } + + public static isEnded(contentsType: ContentsType, contentsSizeList: ContentsSize[], contentsList: Contents[]): boolean { + if (!contentsSizeList || 0 === contentsSizeList.length) { + return true; + } + + if (!contentsList || 0 === contentsList.length) { + return true; + } + + const targetSize = ContentsUtil.getSizeByContentsSize(contentsType, contentsSizeList); + const realSize = ContentsUtil.getSizeByContents(contentsType, contentsList); + + return targetSize > realSize; + } +} diff --git a/src/shared/illustrations/model/illustrations-attachments.model.ts b/src/shared/illustrations/model/illustrations-attachments.model.ts new file mode 100755 index 0000000..62baa1b --- /dev/null +++ b/src/shared/illustrations/model/illustrations-attachments.model.ts @@ -0,0 +1,17 @@ +/** + * 파 일 명: cartoons-attachments.model.ts + * 작성일자: 2018-12-26 + * 작 성 자: 박병준 + * 설 명: Illustrations Attachments model을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { ArticleAttachments } from '../../article/model/article-attachments.model'; +import { Illustrations } from './illustrations.model'; + +export interface IllustrationsAttachments extends ArticleAttachments { + article?: Illustrations; + sequence?: number; +} diff --git a/src/shared/illustrations/model/illustrations.model.ts b/src/shared/illustrations/model/illustrations.model.ts new file mode 100755 index 0000000..b467eeb --- /dev/null +++ b/src/shared/illustrations/model/illustrations.model.ts @@ -0,0 +1,17 @@ +/** + * 파 일 명: illustrations.model.ts + * 작성일자: 2018-12-21 + * 작 성 자: 박병준 + * 설 명: Illustrations model을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Article } from '../../article/model/article.model'; +import { IllustrationsAttachments } from './illustrations-attachments.model'; + +export interface Illustrations extends Article { + attachmentsList?: IllustrationsAttachments[]; + +} diff --git a/src/shared/meta/model/meta-comments-tag.model.ts b/src/shared/meta/model/meta-comments-tag.model.ts new file mode 100755 index 0000000..910a659 --- /dev/null +++ b/src/shared/meta/model/meta-comments-tag.model.ts @@ -0,0 +1,19 @@ + +/** + * 파 일 명: meta-comments-tag.model.ts + * 작성일자: 2019-01-23 + * 작 성 자: 박병준 + * 설 명: Meta Comments Tag model을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Base } from '../../common/model/base.model'; +import { MetaCommentsTagTarget } from '../type/meta-comments-tag-target.type'; + +export interface MetaCommentsTag extends Base { + code?: string; + type?: MetaCommentsTagTarget; + styleClass?: string; +} diff --git a/src/shared/meta/type/meta-comments-tag-target.type.ts b/src/shared/meta/type/meta-comments-tag-target.type.ts new file mode 100755 index 0000000..ca40cf6 --- /dev/null +++ b/src/shared/meta/type/meta-comments-tag-target.type.ts @@ -0,0 +1,14 @@ +/** + * 파 일 명: meta-comments-tag-target.type.ts + * 작성일자: 2018-12-24 + * 작 성 자: 박병준 + * 설 명: Meta comments tag target 유형을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +export enum MetaCommentsTagTarget { + Article = 'article', + Author = 'author', +} diff --git a/src/shared/novel/model/novel-attachments.model.ts b/src/shared/novel/model/novel-attachments.model.ts new file mode 100755 index 0000000..29f98b9 --- /dev/null +++ b/src/shared/novel/model/novel-attachments.model.ts @@ -0,0 +1,17 @@ +/** + * 파 일 명: novel-attachments.model.ts + * 작성일자: 2018-12-26 + * 작 성 자: 박병준 + * 설 명: Novel Attachments model을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { ArticleAttachments } from '../../article/model/article-attachments.model'; +import { Novel } from './novel.model'; + +export interface NovelAttachments extends ArticleAttachments { + article?: Novel; + placeholder?: string; +} diff --git a/src/shared/novel/model/novel.model.ts b/src/shared/novel/model/novel.model.ts new file mode 100755 index 0000000..e03a59c --- /dev/null +++ b/src/shared/novel/model/novel.model.ts @@ -0,0 +1,16 @@ +/** + * 파 일 명: novel.model.ts + * 작성일자: 2018-12-21 + * 작 성 자: 박병준 + * 설 명: Novel model을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Article } from '../../article/model/article.model'; +import { NovelAttachments } from './novel-attachments.model'; + +export interface Novel extends Article { + attachmentsList?: NovelAttachments[]; +} diff --git a/src/shared/oauth/model/oauth.model.ts b/src/shared/oauth/model/oauth.model.ts new file mode 100755 index 0000000..70ab1ba --- /dev/null +++ b/src/shared/oauth/model/oauth.model.ts @@ -0,0 +1,24 @@ +/** + * 파 일 명: oneall-user.model.ts + * 작성일자: 2018-12-20 + * 작 성 자: 박병준 + * 설 명: OAuth model을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Base } from '../../common/model/base.model'; +import { OAuthProvider } from '../type/oauth-provider.type'; +import { OAuthUse } from '../type/oauth-use.type'; +import { User } from '../../user/model/user.model'; + +export interface OAuth extends Base { + oauthId?: string; + accessToken?: string; + refreshToken?: string; + oauthProvider?: OAuthProvider; + oauthUse?: OAuthUse; + profile?: any; + uid?: User; +} diff --git a/src/shared/oauth/type/oauth-provider.type.ts b/src/shared/oauth/type/oauth-provider.type.ts new file mode 100755 index 0000000..1b12f05 --- /dev/null +++ b/src/shared/oauth/type/oauth-provider.type.ts @@ -0,0 +1,15 @@ +/** + * 파 일 명: oauth-provider.type.ts + * 작성일자: 2018-12-20 + * 작 성 자: 박병준 + * 설 명: OAuth 제공자 유형을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +export enum OAuthProvider { + ONEALL = 'ONEALL', + NAVER = 'NAVER', + KAKAO = 'KAKAO', +} diff --git a/src/shared/oauth/type/oauth-result.type.ts b/src/shared/oauth/type/oauth-result.type.ts new file mode 100755 index 0000000..8385d0c --- /dev/null +++ b/src/shared/oauth/type/oauth-result.type.ts @@ -0,0 +1,14 @@ +/** + * 파 일 명: oauth-result.type.ts + * 작성일자: 2018-12-20 + * 작 성 자: 박병준 + * 설 명: OAuth 인증 결과 유형을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +export enum OAuthResult { + REGIST = 'REGIST', + LOGIN = 'LOGIN', +} diff --git a/src/shared/oauth/type/oauth-use.type.ts b/src/shared/oauth/type/oauth-use.type.ts new file mode 100755 index 0000000..c9d3d63 --- /dev/null +++ b/src/shared/oauth/type/oauth-use.type.ts @@ -0,0 +1,14 @@ +/** + * 파 일 명: oauth-use.type.ts + * 작성일자: 2018-12-20 + * 작 성 자: 박병준 + * 설 명: OAuth 계정의 사용 유형을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +export enum OAuthUse { + REGI = 'REGI', + LINK = 'LINK', +} diff --git a/src/shared/tag/model/tag.model.ts b/src/shared/tag/model/tag.model.ts new file mode 100755 index 0000000..8377098 --- /dev/null +++ b/src/shared/tag/model/tag.model.ts @@ -0,0 +1,21 @@ +/** + * 파 일 명: tag.model.ts + * 작성일자: 2018-12-13 + * 작 성 자: 박병준 + * 설 명: Tag model을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Base } from '../../common/model/base.model'; + +export interface Tag extends Base { + tagName?: string; + + tagCount?: number; + + // 20181228 + color?: string; + backgroundColor?: string; +} diff --git a/src/shared/user-analysis/model/user-analysis-article.model.ts b/src/shared/user-analysis/model/user-analysis-article.model.ts new file mode 100755 index 0000000..8c7577f --- /dev/null +++ b/src/shared/user-analysis/model/user-analysis-article.model.ts @@ -0,0 +1,19 @@ +/** + * 파 일 명: user-analysis-article.model.ts + * 작성일자: 2018-12-27 + * 작 성 자: 이숙영 + * 설 명: User contents 게시물 수 + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Base } from '../../common/model/base.model'; + +export interface UserAnalysisArticle extends Base { + uid: string; + cartoonCount: number; + illustCount: number; + novelCount: number; + totalCount: number; +} diff --git a/src/shared/user-analysis/model/user-analysis-favor-tag.model.ts b/src/shared/user-analysis/model/user-analysis-favor-tag.model.ts new file mode 100755 index 0000000..02008f6 --- /dev/null +++ b/src/shared/user-analysis/model/user-analysis-favor-tag.model.ts @@ -0,0 +1,18 @@ +/** + * 파 일 명: user-analysis-favor-tag.model.ts + * 작성일자: 2018-12-13 + * 작 성 자: 박병준 + * 설 명: User 분석용 favor tag model을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Base } from '../../common/model/base.model'; +import { ArticleTag } from '../../article/model/article-tag.model'; + +export interface UserAnalysisFavorTag extends Base { + articleTagList?: ArticleTag[]; + imageName?: string; + show?: boolean; +} diff --git a/src/shared/user-support/model/user-support-event.model.ts b/src/shared/user-support/model/user-support-event.model.ts new file mode 100755 index 0000000..dd1864e --- /dev/null +++ b/src/shared/user-support/model/user-support-event.model.ts @@ -0,0 +1,20 @@ +/** + * 파 일 명: user-support-event.model.ts + * 작성일자: 2018-12-13 + * 작 성 자: 박병준 + * 설 명: User 지원용 Event model을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Base } from '../../common/model/base.model'; +import { Attachments } from '../../attachments/model/attachments.model'; + +export interface UserSupportEvent extends Base { + title?: string; + contents?: string; + startDate?: Date; + endDate?: Date; + imageAttachements?: Attachments; +} diff --git a/src/shared/user-support/model/user-support-tutorial.model.ts b/src/shared/user-support/model/user-support-tutorial.model.ts new file mode 100755 index 0000000..0b3824b --- /dev/null +++ b/src/shared/user-support/model/user-support-tutorial.model.ts @@ -0,0 +1,16 @@ +/** + * 파 일 명: user-support-tutorial.model.ts + * 작성일자: 2018-12-13 + * 작 성 자: 박병준 + * 설 명: User 지원용 Tutorial model을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Base } from '../../common/model/base.model'; + +export interface UserSupportTutorial extends Base { + title?: string; + contents?: string; +} diff --git a/src/shared/user-support/type/user-report.type.ts b/src/shared/user-support/type/user-report.type.ts new file mode 100755 index 0000000..4d61544 --- /dev/null +++ b/src/shared/user-support/type/user-report.type.ts @@ -0,0 +1,14 @@ +/** + * 파 일 명: user-report.type.ts + * 작성일자: 2019-01-10 + * 작 성 자: 박병준 + * 설 명: User Report 유형을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +export enum UserReport { + CopyrightViolation = 'CopyrightViolation', + Inappropriateness = 'Inappropriateness', +} diff --git a/src/shared/user-support/type/user-share.type.ts b/src/shared/user-support/type/user-share.type.ts new file mode 100755 index 0000000..25fccb5 --- /dev/null +++ b/src/shared/user-support/type/user-share.type.ts @@ -0,0 +1,17 @@ +/** + * 파 일 명: user-share.type.ts + * 작성일자: 2019-01-10 + * 작 성 자: 박병준 + * 설 명: User Share 유형을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +export enum UserShare { + Facebook = 'Facebook', + Twitter = 'Twitter', + GooglePlus = 'GooglePlus', + Email = 'Email', + CopyLink = 'CopyLink', +} diff --git a/src/shared/user/model/user-favor-tag.model.ts b/src/shared/user/model/user-favor-tag.model.ts new file mode 100755 index 0000000..d103fc1 --- /dev/null +++ b/src/shared/user/model/user-favor-tag.model.ts @@ -0,0 +1,18 @@ +/** + * 파 일 명: user-favor-tag.model.ts + * 작성일자: 2019-01-19 + * 작 성 자: 박병준 + * 설 명: User Favor Tag model을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Base } from '../../common/model/base.model'; +import { User } from './user.model'; +import { UserAnalysisFavorTag } from '../../user-analysis/model/user-analysis-favor-tag.model'; + +export interface UserFavorTag extends Base { + user?: User; + userAnalysisFavorTag: UserAnalysisFavorTag; +} diff --git a/src/shared/user/model/user-followers.model.ts b/src/shared/user/model/user-followers.model.ts new file mode 100755 index 0000000..02e5b9d --- /dev/null +++ b/src/shared/user/model/user-followers.model.ts @@ -0,0 +1,17 @@ +/** + * 파 일 명: user-followers.model.ts + * 작성일자: 2018-12-27 + * 작 성 자: 이숙영 + * 설 명: User-follow model을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +import { Base } from '../../common/model/base.model'; +import { User } from './user.model'; + +export interface UserFollowers extends Base { + user?: User; + target?: User; +} diff --git a/src/shared/user/model/user.model.ts b/src/shared/user/model/user.model.ts new file mode 100755 index 0000000..c58d39b --- /dev/null +++ b/src/shared/user/model/user.model.ts @@ -0,0 +1,63 @@ +/** + * 파 일 명: user.model.ts + * 작성일자: 2018-12-13 + * 작 성 자: 박병준 + * 설 명: User model을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + * 수정일시: 2019-01-18 + * 수 정 자: 최지련 + * 수정내용: favorShowedYN 필드 추가 + * 수정일시: 2019-01-10 + * 수 정 자: 최지련 + * 수정내용: email 필드 추가 + * 수정일시: 2019-01-03 + * 수 정 자: 최지련 + * 수정내용: displayLanguage, articlePublicYN 필드 추가 + * 수정일시: 2018-12-28 + * 수 정 자: 최지련 + * 수정내용: ageLimit 필드 추가 + * (연령제한 설정값 - 00 : 전연령, 01 : 가벼운 성적 묘사, 10 : 폭력, 잔인, 크로테스한 묘사, 11 - 01, 10 동시 선택) + */ +import { Base } from '../../common/model/base.model'; +import { AgeLimitType } from '../../common/type/ageLimit.type'; +import { LangType } from '../../common/type/lang.type'; +import { UserAnalysisArticle } from '../../user-analysis/model/user-analysis-article.model'; +import { Attachments } from '../../attachments/model/attachments.model'; +import { UserFollowers } from './user-followers.model'; +import { ArticleBookmarks } from '../../article/model/article-bookmarks.model'; +import { ArticleLike } from '../../article/model/article-like.model'; + + +export interface User extends Base { + nickname?: string; + followerCount?: number; + followingCount?: number; + ageLimit?: AgeLimitType; + displayLanguage?: LangType; + articlePublicYN?: boolean; + email?: string; + favorShowedYN?: boolean; + + // 2018.12.27 - 이숙영 추가 + profileImageAttachments?: Attachments; + backgroundImageAttachments?: Attachments; + workImageAttachments?: Attachments; + introduction?: string; + userAnaliArticle?: UserAnalysisArticle; + + // 2018.01.03 - 김태경 추가 + // 현재 유저 객체에 대한 팔로잉 상태 + isFollowing?: boolean; + + followersList?: UserFollowers[]; + followingList?: UserFollowers[]; + + // 2019.01.18 - 윤대훈 추가 + // bookmark 정보 + articleBookmarksList?: ArticleBookmarks[]; + articleLikeList?: ArticleLike[]; + +} diff --git a/src/shared/user/type/user-display.type.ts b/src/shared/user/type/user-display.type.ts new file mode 100755 index 0000000..c607594 --- /dev/null +++ b/src/shared/user/type/user-display.type.ts @@ -0,0 +1,15 @@ +/** + * 파 일 명: user-display.type.ts + * 작성일자: 2019-01-30 + * 작 성 자: 박병준 + * 설 명: User의 출력 유형을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +export enum UserDisplay { + Article = 'article', + Profile = 'profile', + Recommendations = 'recommendations', +} diff --git a/src/shared/user/type/user-image.type.ts b/src/shared/user/type/user-image.type.ts new file mode 100755 index 0000000..00f5e9e --- /dev/null +++ b/src/shared/user/type/user-image.type.ts @@ -0,0 +1,14 @@ +/** + * 파 일 명: user-image.type.ts + * 작성일자: 2019-01-30 + * 작 성 자: 박병준 + * 설 명: User의 이미지 유형을 정의한다. + * 수정일시: + * 수 정 자: + * 수정내용: + * 참고사항: + */ +export enum UserImage { + Profile = 'profile', + Background = 'background', +} diff --git a/src/styles.scss b/src/styles.scss new file mode 100755 index 0000000..a42a7e0 --- /dev/null +++ b/src/styles.scss @@ -0,0 +1,36 @@ +/* You can add global styles to this file, and also import other style files */ +@import '~@angular/material/theming'; +@import './assets/app-theme'; +@import './assets/styles/api'; +@import './assets/styles/markdown'; +@import './assets/styles/tables'; +@import './assets/styles/general'; +// custom theme SCSS +@import './custom.scss'; +// custom layout style SCSS +@import './customlayout.scss'; +// custom page style SCSS +@import './customcomponent.scss'; +// custom button color SCSS +@import './customcolor2.scss'; +// custom for login layout +@import './customlogin.scss'; +// custom for Dialog SCSS +@import './customDialog.scss'; +// custom for Upload SCSS +@import './customUpload.scss'; + + +// Include material core styles. +@include mat-core(); + + +// Define the light theme. +// $primary: mat-palette($mat-custom-orange); +$primary: mat-palette($mat-custom); +$primary2: mat-palette($mat-custom); +$accent: mat-palette($mat-indigo, A200, A100, A400); + +$theme: mat-light-theme($primary2, $accent); +@include angular-material-theme($theme); +@include app-root-theme($theme); diff --git a/src/test.ts b/src/test.ts new file mode 100755 index 0000000..26cfdde --- /dev/null +++ b/src/test.ts @@ -0,0 +1,20 @@ +// This file is required by karma.conf.js and loads recursively all the .spec and framework files + +import 'zone.js/dist/zone-testing'; +import { getTestBed } from '@angular/core/testing'; +import { + BrowserDynamicTestingModule, + platformBrowserDynamicTesting +} from '@angular/platform-browser-dynamic/testing'; + +declare const require: any; + +// First, initialize the Angular testing environment. +getTestBed().initTestEnvironment( + BrowserDynamicTestingModule, + platformBrowserDynamicTesting() +); +// Then we find all the tests. +const context = require.context('./', true, /\.spec\.ts$/); +// And load the modules. +context.keys().map(context); diff --git a/src/tsconfig.app.json b/src/tsconfig.app.json new file mode 100755 index 0000000..b4555ce --- /dev/null +++ b/src/tsconfig.app.json @@ -0,0 +1,13 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/app", + "types": [ + "node" + ] + }, + "exclude": [ + "test.ts", + "**/*.spec.ts" + ] +} diff --git a/src/tsconfig.server.json b/src/tsconfig.server.json new file mode 100755 index 0000000..29e84a2 --- /dev/null +++ b/src/tsconfig.server.json @@ -0,0 +1,16 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/app", + "module": "commonjs", + "target": "es6", + "types": [ + "node", + "reflect-metadata" + ] + }, + "exclude": [ + "test.ts", + "**/*.spec.ts" + ] +} diff --git a/src/tsconfig.spec.json b/src/tsconfig.spec.json new file mode 100755 index 0000000..903f6d4 --- /dev/null +++ b/src/tsconfig.spec.json @@ -0,0 +1,18 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/spec", + "types": [ + "jasmine", + "node" + ] + }, + "files": [ + "test.ts", + "polyfills.ts" + ], + "include": [ + "**/*.spec.ts", + "**/*.d.ts" + ] +} diff --git a/src/tslint.json b/src/tslint.json new file mode 100755 index 0000000..9d3bffb --- /dev/null +++ b/src/tslint.json @@ -0,0 +1,17 @@ +{ + "extends": "../tslint.json", + "rules": { + "directive-selector": [ + true, + "attribute", + "app", + "camelCase" + ], + "component-selector": [ + true, + "element", + "app", + "kebab-case" + ] + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100755 index 0000000..71f40a6 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "baseUrl": "./", + "outDir": "./dist/out-tsc", + "sourceMap": true, + "declaration": false, + "module": "es2015", + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "target": "es5", + "typeRoots": [ + "node_modules/@types" + ], + "lib": [ + "es2018", + "dom" + ] + } +} diff --git a/tslint.json b/tslint.json new file mode 100755 index 0000000..532e2cc --- /dev/null +++ b/tslint.json @@ -0,0 +1,131 @@ +{ + "rulesDirectory": [ + "node_modules/codelyzer" + ], + "rules": { + "arrow-return-shorthand": true, + "callable-types": true, + "class-name": true, + "comment-format": [ + true, + "check-space" + ], + "curly": true, + "deprecation": { + "severity": "warn" + }, + "eofline": true, + "forin": true, + "import-blacklist": [ + true, + "rxjs/Rx" + ], + "import-spacing": true, + "indent": [ + true, + "spaces" + ], + "interface-over-type-literal": true, + "label-position": true, + "max-line-length": [ + true, + 140 + ], + "member-access": false, + "member-ordering": [ + true, + { + "order": [ + "static-field", + "instance-field", + "static-method", + "instance-method" + ] + } + ], + "no-arg": true, + "no-bitwise": true, + "no-console": [ + true, + "debug", + "info", + "time", + "timeEnd", + "trace" + ], + "no-construct": true, + "no-debugger": true, + "no-duplicate-super": true, + "no-empty": false, + "no-empty-interface": true, + "no-eval": true, + "no-inferrable-types": [ + true, + "ignore-params" + ], + "no-misused-new": true, + "no-non-null-assertion": true, + "no-redundant-jsdoc": true, + "no-shadowed-variable": true, + "no-string-literal": false, + "no-string-throw": true, + "no-switch-case-fall-through": true, + "no-trailing-whitespace": true, + "no-unnecessary-initializer": true, + "no-unused-expression": true, + "no-use-before-declare": true, + "no-var-keyword": true, + "object-literal-sort-keys": false, + "one-line": [ + true, + "check-open-brace", + "check-catch", + "check-else", + "check-whitespace" + ], + "prefer-const": true, + "quotemark": [ + true, + "single" + ], + "radix": true, + "semicolon": [ + true, + "always" + ], + "triple-equals": [ + true, + "allow-null-check" + ], + "typedef-whitespace": [ + true, + { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + } + ], + "unified-signatures": true, + "variable-name": false, + "whitespace": [ + true, + "check-branch", + "check-decl", + "check-operator", + "check-separator", + "check-type" + ], + "no-output-on-prefix": true, + "use-input-property-decorator": true, + "use-output-property-decorator": true, + "use-host-property-decorator": true, + "no-input-rename": true, + "no-output-rename": true, + "use-life-cycle-interface": true, + "use-pipe-transform-interface": true, + "component-class-suffix": true, + "directive-class-suffix": true + } +} diff --git a/webpack.server.config.js b/webpack.server.config.js new file mode 100755 index 0000000..7f6e23c --- /dev/null +++ b/webpack.server.config.js @@ -0,0 +1,54 @@ +// Work around for https://github.com/angular/angular-cli/issues/7200 + +const path = require('path'); +const webpack = require('webpack'); +const { + TsConfigPathsPlugin +} = require('awesome-typescript-loader'); + +module.exports = { + mode: 'none', + entry: { + server: [ + './src/server/main.ts', + + ] + }, + target: 'node', + resolve: { + extensions: ['.ts', '.js', '.json'], + plugins: [ + new TsConfigPathsPlugin({ + configFileName: './src/tsconfig.server.json' + }) + ] + }, + optimization: { + minimize: false + }, + output: { + // Puts the output at the root of the dist folder + path: path.join(__dirname, 'dist', 'server'), + filename: '[name].js' + }, + module: { + rules: [{ + test: /\.json$/, + exclude: /node_modules/, + loader: 'json-loader' + }, + { + test: /\.ts$/, + loader: 'awesome-typescript-loader', + options: { + configFileName: './src/tsconfig.server.json' + } + } + ] + }, + plugins: [ + new webpack.DefinePlugin({ + "global.GENTLY": false + }), + ] +};