Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e413163993 | ||
|
|
4b8bb47800 | ||
|
|
ece8ccf27a | ||
|
|
6cf4d1b748 | ||
|
|
6f6018501b | ||
|
|
6de2f85b24 | ||
|
|
d0f6b8f9a6 | ||
|
|
51d5ac6ee6 | ||
|
|
76862b738d | ||
|
|
2b7e344931 | ||
|
|
c4a0f3e405 | ||
|
|
7700100a7d | ||
|
|
9120332051 | ||
|
|
fba867e0b5 |
@@ -0,0 +1,25 @@
|
||||
module.exports = {
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
project: 'tsconfig.json',
|
||||
sourceType: 'module',
|
||||
},
|
||||
plugins: ['@typescript-eslint/eslint-plugin'],
|
||||
extends: [
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'prettier/@typescript-eslint',
|
||||
'plugin:prettier/recommended',
|
||||
],
|
||||
root: true,
|
||||
env: {
|
||||
node: true,
|
||||
jest: true,
|
||||
},
|
||||
ignorePatterns: ['.eslintrc.js'],
|
||||
rules: {
|
||||
'@typescript-eslint/interface-name-prefix': 'off',
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
},
|
||||
};
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
# compiled output
|
||||
/dist
|
||||
/node_modules
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
|
||||
# Tests
|
||||
/coverage
|
||||
/.nyc_output
|
||||
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
|
||||
# IDE - VSCode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
|
||||
/config.yml
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all"
|
||||
}
|
||||
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"cSpell.words": [
|
||||
"boardcat",
|
||||
"execa",
|
||||
"gitea",
|
||||
"lpush",
|
||||
"lrange",
|
||||
"metatype",
|
||||
"pmessage",
|
||||
"psubscribe",
|
||||
"QLJSON",
|
||||
"Repos",
|
||||
"rpop",
|
||||
"rpush"
|
||||
]
|
||||
}
|
||||
+3
-2
@@ -14,5 +14,6 @@ db:
|
||||
port: 6379
|
||||
password:
|
||||
prefix: blog
|
||||
workspaces:
|
||||
root: '/Users/ivanli/Projects/fennec/workspaces'
|
||||
etcd:
|
||||
hosts:
|
||||
- 'http://192.168.31.194:2379'
|
||||
@@ -0,0 +1,20 @@
|
||||
version: "3.9"
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: 'postgres:14'
|
||||
restart: always
|
||||
environment:
|
||||
- POSTGRES_HOST_AUTH_METHOD=trust
|
||||
ports:
|
||||
- '${PG_PORT}:5432'
|
||||
redis:
|
||||
image: 'redis:6'
|
||||
restart: always
|
||||
ports:
|
||||
- '${REDIS_PORT}:6379'
|
||||
|
||||
networks:
|
||||
default:
|
||||
name: 'blog-dev'
|
||||
driver: bridge
|
||||
@@ -0,0 +1,61 @@
|
||||
import execa from 'execa';
|
||||
import { URL } from 'url';
|
||||
import { findFreePorts } from 'find-free-ports';
|
||||
import YAML from 'js-yaml';
|
||||
import { readFile, writeFile } from 'fs/promises';
|
||||
|
||||
const [PG_PORT, REDIS_PORT] = await findFreePorts(2);
|
||||
|
||||
await execa(
|
||||
'docker-compose',
|
||||
[
|
||||
'-f',
|
||||
new URL('./docker-compose.dev.yml', import.meta.url).pathname,
|
||||
'up',
|
||||
'-d',
|
||||
],
|
||||
{
|
||||
env: {
|
||||
PG_PORT,
|
||||
REDIS_PORT,
|
||||
},
|
||||
stdout: process.stdout,
|
||||
stderr: process.stderr,
|
||||
},
|
||||
);
|
||||
|
||||
console.log(`✅ Postgres is running on port ${PG_PORT}`);
|
||||
console.log(`✅ Redis is running on port ${REDIS_PORT}`);
|
||||
|
||||
const config = await YAML.load(
|
||||
await readFile(
|
||||
new URL('../config.yml.example', import.meta.url).pathname,
|
||||
'utf-8',
|
||||
),
|
||||
);
|
||||
config.db.postgres = {
|
||||
host: 'localhost',
|
||||
port: PG_PORT,
|
||||
database: 'postgres',
|
||||
username: 'postgres',
|
||||
password: '',
|
||||
};
|
||||
|
||||
config.db.redis = {
|
||||
host: 'localhost',
|
||||
port: REDIS_PORT,
|
||||
};
|
||||
|
||||
const configOutputPath = new URL('../config.yml', import.meta.url).pathname;
|
||||
|
||||
await writeFile(configOutputPath, YAML.dump(config), 'utf-8');
|
||||
|
||||
console.log(`✅ Config file is written to ${configOutputPath}`);
|
||||
|
||||
await execa.command('npm run typeorm -- migration:run', {
|
||||
cwd: new URL('../', import.meta.url).pathname,
|
||||
stdout: process.stdout,
|
||||
stderr: process.stderr,
|
||||
});
|
||||
|
||||
console.log(`✅ Database Initiated!`);
|
||||
@@ -0,0 +1,14 @@
|
||||
module.exports = {
|
||||
apps: [
|
||||
{
|
||||
name: 'blog-be',
|
||||
script: 'npm',
|
||||
args: 'run start:prod',
|
||||
watch: false,
|
||||
ignore_watch: ['node_modules'],
|
||||
log_date_format: 'MM-DD HH:mm:ss.SSS Z',
|
||||
env: {},
|
||||
max_restarts: 5,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
import {MigrationInterface, QueryRunner} from "typeorm";
|
||||
|
||||
export class articleAndTagMigration1635661602570 implements MigrationInterface {
|
||||
name = 'articleAndTagMigration1635661602570'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TABLE "article" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "title" character varying NOT NULL, "content" text NOT NULL, "publishedAt" TIMESTAMP, "tags" character varying array NOT NULL, CONSTRAINT "PK_40808690eb7b915046558c0f81b" PRIMARY KEY ("id"))`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_f330baf6be412e8dd60ff7f78e" ON "article" ("publishedAt") `);
|
||||
await queryRunner.query(`CREATE TABLE "tag" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "name" character varying(100) NOT NULL, CONSTRAINT "PK_8e4052373c579afc1471f526760" PRIMARY KEY ("id"))`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_6a9775008add570dc3e5a0bab7" ON "tag" ("name") `);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_6a9775008add570dc3e5a0bab7"`);
|
||||
await queryRunner.query(`DROP TABLE "tag"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_f330baf6be412e8dd60ff7f78e"`);
|
||||
await queryRunner.query(`DROP TABLE "article"`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const yaml = require('js-yaml');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const config = yaml.load(
|
||||
fs.readFileSync(path.join(__dirname, './config.yml'), 'utf8'),
|
||||
);
|
||||
|
||||
module.exports = {
|
||||
type: 'postgres',
|
||||
host: config.db.postgres.host,
|
||||
port: config.db.postgres.port,
|
||||
username: config.db.postgres.username,
|
||||
password: config.db.postgres.password,
|
||||
database: config.db.postgres.database,
|
||||
migrations: ['migrations/**/*.ts'],
|
||||
entities: ['src/**/*.entity.ts'],
|
||||
cli: {
|
||||
migrationsDir: 'migrations',
|
||||
},
|
||||
};
|
||||
Generated
+17670
-8190
File diff suppressed because it is too large
Load Diff
+59
-49
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "fennec-be",
|
||||
"version": "0.0.1",
|
||||
"name": "blog-be",
|
||||
"version": "0.1.0",
|
||||
"description": "",
|
||||
"author": "",
|
||||
"private": true,
|
||||
@@ -13,67 +13,74 @@
|
||||
"start:dev": "DEBUG=fennec:* nest start --watch",
|
||||
"start:debug": "DEBUG=fennec:* nest start --debug --watch",
|
||||
"start:prod": "node dist/main",
|
||||
"init:dev": "node docker/init-dev.mjs",
|
||||
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch",
|
||||
"test:cov": "jest --coverage",
|
||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
||||
"test:e2e": "jest --config ./test/jest-e2e.json"
|
||||
"test:e2e": "jest --config ./test/jest-e2e.json",
|
||||
"typeorm": "node --require ts-node/register ./node_modules/typeorm/cli.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs/bull": "^0.3.1",
|
||||
"@nestjs/common": "^7.5.1",
|
||||
"@nestjs/config": "^0.6.2",
|
||||
"@nestjs/core": "^7.5.1",
|
||||
"@nestjs/graphql": "^7.9.8",
|
||||
"@nestjs/platform-express": "^7.5.1",
|
||||
"@nestjs/typeorm": "^7.1.5",
|
||||
"@types/bull": "^3.15.0",
|
||||
"apollo-server-express": "^2.19.2",
|
||||
"bcrypt": "^5.0.0",
|
||||
"@fennec/configuration": "^0.0.2",
|
||||
"@nestjs-lib/auth": "^0.2.3",
|
||||
"@nestjs-lib/etcd3": "^0.0.1",
|
||||
"@nestjs/common": "^8.1.1",
|
||||
"@nestjs/config": "^1.0.3",
|
||||
"@nestjs/core": "^8.1.1",
|
||||
"@nestjs/graphql": "^9.1.1",
|
||||
"@nestjs/platform-express": "^8.1.1",
|
||||
"@nestjs/typeorm": "^8.0.2",
|
||||
"apollo-server-express": "^3.4.0",
|
||||
"bcrypt": "^5.0.1",
|
||||
"body-parser": "^1.19.0",
|
||||
"bull": "^3.20.1",
|
||||
"class-transformer": "^0.3.2",
|
||||
"class-transformer": "^0.4.0",
|
||||
"class-validator": "^0.13.1",
|
||||
"debug": "^4.3.1",
|
||||
"graphql": "^15.5.0",
|
||||
"graphql-tools": "^7.0.2",
|
||||
"ioredis": "^4.25.0",
|
||||
"js-yaml": "^4.0.0",
|
||||
"nestjs-redis": "^1.2.8",
|
||||
"observable-to-async-generator": "^1.0.1-rc",
|
||||
"pg": "^8.5.1",
|
||||
"debug": "^4.3.2",
|
||||
"graphql": "^15.6.1",
|
||||
"graphql-tools": "^8.2.0",
|
||||
"graphql-type-json": "^0.3.2",
|
||||
"highlight.js": "^11.3.1",
|
||||
"ioredis": "^4.28.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"marked": "^3.0.7",
|
||||
"nestjs-redis": "^1.3.3",
|
||||
"observable-to-async-generator": "^1.0.2",
|
||||
"pg": "^8.7.1",
|
||||
"ramda": "^0.27.1",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rimraf": "^3.0.2",
|
||||
"rxjs": "^6.6.3",
|
||||
"simple-git": "^2.35.0",
|
||||
"typeorm": "^0.2.30"
|
||||
"rxjs": "^7.4.0",
|
||||
"simple-git": "^2.47.0",
|
||||
"typeorm": "^0.2.38"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "^7.5.7",
|
||||
"@nestjs/schematics": "^7.1.3",
|
||||
"@nestjs/testing": "^7.5.1",
|
||||
"@types/body-parser": "^1.19.0",
|
||||
"@types/debug": "^4.1.5",
|
||||
"@types/express": "^4.17.8",
|
||||
"@types/ioredis": "^4.22.3",
|
||||
"@types/jest": "^26.0.15",
|
||||
"@types/node": "^14.14.6",
|
||||
"@types/supertest": "^2.0.10",
|
||||
"@typescript-eslint/eslint-plugin": "^4.6.1",
|
||||
"@typescript-eslint/parser": "^4.6.1",
|
||||
"eslint": "^7.12.1",
|
||||
"eslint-config-prettier": "7.2.0",
|
||||
"eslint-plugin-prettier": "^3.1.4",
|
||||
"jest": "^26.6.3",
|
||||
"prettier": "^2.1.2",
|
||||
"supertest": "^6.0.0",
|
||||
"ts-jest": "^26.4.3",
|
||||
"ts-loader": "^8.0.8",
|
||||
"ts-node": "^9.0.0",
|
||||
"tsconfig-paths": "^3.9.0",
|
||||
"typescript": "^4.0.5"
|
||||
"@nestjs/cli": "^8.1.4",
|
||||
"@nestjs/schematics": "^8.0.4",
|
||||
"@nestjs/testing": "^8.1.1",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/highlight.js": "^10.1.0",
|
||||
"@types/jest": "^27.0.2",
|
||||
"@types/marked": "^3.0.2",
|
||||
"@types/node": "^16.11.2",
|
||||
"@types/supertest": "^2.0.11",
|
||||
"@typescript-eslint/eslint-plugin": "^5.1.0",
|
||||
"@typescript-eslint/parser": "^5.1.0",
|
||||
"apollo-server-testing": "^2.23.0",
|
||||
"eslint": "^8.0.1",
|
||||
"eslint-config-prettier": "8.3.0",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"execa": "^5.1.1",
|
||||
"find-free-ports": "^3.0.0",
|
||||
"jest": "^27.3.1",
|
||||
"prettier": "^2.4.1",
|
||||
"supertest": "^6.1.6",
|
||||
"ts-jest": "^27.0.7",
|
||||
"ts-loader": "^9.2.6",
|
||||
"ts-node": "^10.3.0",
|
||||
"tsconfig-paths": "^3.11.0",
|
||||
"typescript": "^4.4.4"
|
||||
},
|
||||
"jest": {
|
||||
"moduleFileExtensions": [
|
||||
@@ -89,6 +96,9 @@
|
||||
"collectCoverageFrom": [
|
||||
"**/*.(t|j)s"
|
||||
],
|
||||
"moduleNameMapper": {
|
||||
"^jose/(.*)$": "<rootDir>/../node_modules/jose/dist/node/cjs/$1"
|
||||
},
|
||||
"coverageDirectory": "../coverage",
|
||||
"testEnvironment": "node"
|
||||
}
|
||||
|
||||
+11
-34
@@ -6,18 +6,19 @@ import { AppController } from './app.controller';
|
||||
import { AppResolver } from './app.resolver';
|
||||
import { AppService } from './app.service';
|
||||
import configuration from './commons/config/configuration';
|
||||
import { RedisModule } from 'nestjs-redis';
|
||||
import { ParseBodyMiddleware } from './commons/middleware/parse-body.middleware';
|
||||
import { BullModule } from '@nestjs/bull';
|
||||
import { PubSubModule } from './commons/pub-sub/pub-sub.module';
|
||||
import { ArticlesModule } from './articles/articles.module';
|
||||
import { EtcdModule } from '@nestjs-lib/etcd3';
|
||||
import { CommonsModule } from './commons/commons.module';
|
||||
import { TagsModule } from './tags/tags.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
load: [configuration],
|
||||
isGlobal: true,
|
||||
}),
|
||||
TypeOrmModule.forRootAsync({
|
||||
imports: [ConfigModule],
|
||||
useFactory: (configService: ConfigService) => ({
|
||||
type: 'postgres',
|
||||
host: configService.get<string>('db.postgres.host'),
|
||||
@@ -25,13 +26,12 @@ import { PubSubModule } from './commons/pub-sub/pub-sub.module';
|
||||
username: configService.get<string>('db.postgres.username'),
|
||||
password: configService.get<string>('db.postgres.password'),
|
||||
database: configService.get<string>('db.postgres.database'),
|
||||
synchronize: true,
|
||||
synchronize: false,
|
||||
autoLoadEntities: true,
|
||||
}),
|
||||
inject: [ConfigService],
|
||||
}),
|
||||
GraphQLModule.forRootAsync({
|
||||
imports: [ConfigModule],
|
||||
useFactory: (configService: ConfigService) => ({
|
||||
debug: configService.get<string>('env') !== 'prod',
|
||||
playground: true,
|
||||
@@ -40,38 +40,15 @@ import { PubSubModule } from './commons/pub-sub/pub-sub.module';
|
||||
}),
|
||||
inject: [ConfigService],
|
||||
}),
|
||||
BullModule.forRootAsync({
|
||||
imports: [ConfigModule],
|
||||
EtcdModule.forRootAsync({
|
||||
useFactory: (configService: ConfigService) => ({
|
||||
redis: {
|
||||
host: configService.get<string>('db.redis.host', 'localhost'),
|
||||
port: configService.get<number>('db.redis.port', undefined),
|
||||
password: configService.get<string>('db.redis.password', undefined),
|
||||
},
|
||||
}),
|
||||
inject: [ConfigService],
|
||||
}),
|
||||
PubSubModule.forRootAsync({
|
||||
imports: [ConfigModule],
|
||||
useFactory: (configService: ConfigService) => ({
|
||||
redis: {
|
||||
host: configService.get<string>('db.redis.host', 'localhost'),
|
||||
port: configService.get<number>('db.redis.port', undefined),
|
||||
password: configService.get<string>('db.redis.password', undefined),
|
||||
},
|
||||
}),
|
||||
inject: [ConfigService],
|
||||
}),
|
||||
RedisModule.forRootAsync({
|
||||
imports: [ConfigModule],
|
||||
useFactory: (configService: ConfigService) => ({
|
||||
host: configService.get<string>('db.redis.host', 'localhost'),
|
||||
port: configService.get<number>('db.redis.port', 6379),
|
||||
password: configService.get<string>('db.redis.password', ''),
|
||||
keyPrefix: configService.get<string>('db.redis.prefix', 'blog') + ':',
|
||||
hosts: configService.get<string>('db.etcd.hosts', 'localhost:2379'),
|
||||
}),
|
||||
inject: [ConfigService],
|
||||
}),
|
||||
CommonsModule,
|
||||
ArticlesModule,
|
||||
TagsModule,
|
||||
],
|
||||
controllers: [AppController],
|
||||
providers: [AppService, AppResolver],
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { CommonsModule } from './../commons/commons.module';
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ArticlesService } from './articles.service';
|
||||
import { ArticlesResolver } from './articles.resolver';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Article } from './entities/article.entity';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Article]), CommonsModule],
|
||||
providers: [ArticlesResolver, ArticlesService],
|
||||
})
|
||||
export class ArticlesModule {}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { JwtService } from '@nestjs-lib/auth';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { ArticlesResolver } from './articles.resolver';
|
||||
import { ArticlesService } from './articles.service';
|
||||
|
||||
describe('ArticlesResolver', () => {
|
||||
let resolver: ArticlesResolver;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
ArticlesResolver,
|
||||
{
|
||||
provide: ArticlesService,
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: JwtService,
|
||||
useValue: {},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
resolver = module.get<ArticlesResolver>(ArticlesResolver);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(resolver).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,90 @@
|
||||
import {
|
||||
Resolver,
|
||||
Query,
|
||||
Mutation,
|
||||
Args,
|
||||
Int,
|
||||
ResolveField,
|
||||
Parent,
|
||||
} from '@nestjs/graphql';
|
||||
import { ArticlesService } from './articles.service';
|
||||
import { Article } from './entities/article.entity';
|
||||
import { CreateArticleInput } from './dto/create-article.input';
|
||||
import { UpdateArticleInput } from './dto/update-article.input';
|
||||
import * as marked from 'marked';
|
||||
import highlight from 'highlight.js';
|
||||
import { AccountRole, Roles } from '@nestjs-lib/auth';
|
||||
import { ArticleHistory } from './models/article-history.model';
|
||||
|
||||
@Resolver(() => Article)
|
||||
export class ArticlesResolver {
|
||||
constructor(private readonly articlesService: ArticlesService) {}
|
||||
|
||||
@Roles(AccountRole.admin, AccountRole.super)
|
||||
@Mutation(() => Article)
|
||||
createArticle(
|
||||
@Args('createArticleInput') createArticleInput: CreateArticleInput,
|
||||
) {
|
||||
return this.articlesService.create(createArticleInput);
|
||||
}
|
||||
|
||||
@Query(() => [Article], { name: 'articles' })
|
||||
async findAll() {
|
||||
return await this.articlesService.findAll();
|
||||
}
|
||||
|
||||
@Query(() => Article, { name: 'article' })
|
||||
findOne(@Args('id', { type: () => String }) id: string) {
|
||||
return this.articlesService.findOne(id);
|
||||
}
|
||||
|
||||
@Roles(AccountRole.admin, AccountRole.super)
|
||||
@Mutation(() => Article)
|
||||
async updateArticle(
|
||||
@Args('updateArticleInput') updateArticleInput: UpdateArticleInput,
|
||||
) {
|
||||
const article = await this.articlesService.findOne(updateArticleInput.id);
|
||||
return this.articlesService.update(article, updateArticleInput);
|
||||
}
|
||||
|
||||
@Roles(AccountRole.admin, AccountRole.super)
|
||||
@Mutation(() => Int)
|
||||
removeArticle(@Args('id', { type: () => String }) id: string) {
|
||||
return this.articlesService.remove(id);
|
||||
}
|
||||
|
||||
@ResolveField(() => String)
|
||||
async html(@Parent() article: Article) {
|
||||
const tokens = marked.lexer(article.content);
|
||||
const index = tokens.findIndex((token) => ['heading'].includes(token.type));
|
||||
if (index !== -1) {
|
||||
tokens.splice(index, 1);
|
||||
}
|
||||
return marked.parser(tokens, {
|
||||
gfm: true,
|
||||
smartLists: true,
|
||||
smartypants: true,
|
||||
langPrefix: 'hljs language-',
|
||||
highlight: (code, language) => {
|
||||
return highlight.highlight(code, {
|
||||
language: highlight.getLanguage(language) ? language : 'plaintext',
|
||||
}).value;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@ResolveField(() => String, { nullable: true })
|
||||
async description(@Parent() article: Article) {
|
||||
const tokens = marked.lexer(article.content);
|
||||
const token = tokens.find((token) =>
|
||||
['blockquote', 'paragraph'].includes(token.type),
|
||||
) as { text: string };
|
||||
|
||||
return token?.text;
|
||||
}
|
||||
|
||||
@ResolveField(() => [ArticleHistory])
|
||||
async histories(@Parent() article: Article) {
|
||||
return article.histories;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { ArticlesService } from './articles.service';
|
||||
import { Article } from './entities/article.entity';
|
||||
|
||||
describe('ArticlesService', () => {
|
||||
let service: ArticlesService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
ArticlesService,
|
||||
{
|
||||
provide: getRepositoryToken(Article),
|
||||
useValue: new Repository(),
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<ArticlesService>(ArticlesService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { BaseDbService } from '../commons/services/base-db.service';
|
||||
import { CreateArticleInput } from './dto/create-article.input';
|
||||
import { UpdateArticleInput } from './dto/update-article.input';
|
||||
import { Article } from './entities/article.entity';
|
||||
|
||||
@Injectable()
|
||||
export class ArticlesService extends BaseDbService<Article> {
|
||||
readonly uniqueFields: Array<keyof Article> = ['title'];
|
||||
constructor(
|
||||
@InjectRepository(Article)
|
||||
readonly repository: Repository<Article>,
|
||||
) {
|
||||
super(repository);
|
||||
}
|
||||
async create(createArticleInput: CreateArticleInput) {
|
||||
await this.isDuplicateEntity(createArticleInput);
|
||||
return await this.repository.save(
|
||||
this.repository.create(createArticleInput),
|
||||
);
|
||||
}
|
||||
|
||||
async findAll() {
|
||||
return await this.repository.find({
|
||||
order: { createdAt: 'DESC' },
|
||||
});
|
||||
}
|
||||
|
||||
async update(article: Article, updateArticleInput: UpdateArticleInput) {
|
||||
await this.isDuplicateEntityForUpdate(article.id, updateArticleInput);
|
||||
return await this.repository.save(
|
||||
this.repository.merge(article, updateArticleInput),
|
||||
);
|
||||
}
|
||||
|
||||
async remove(id: string) {
|
||||
await this.canRemove([id]);
|
||||
return await this.repository.softDelete({ id }).then((d) => d.affected);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { ObjectType, OmitType } from '@nestjs/graphql';
|
||||
import { Article } from '../entities/article.entity';
|
||||
|
||||
@ObjectType()
|
||||
export class ArticleListItemDto extends OmitType(Article, [
|
||||
'content',
|
||||
] as const) {}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { InputType } from '@nestjs/graphql';
|
||||
import { IsDate, IsOptional, IsString, Length } from 'class-validator';
|
||||
|
||||
@InputType()
|
||||
export class CreateArticleInput {
|
||||
@IsString()
|
||||
@Length(1, 100)
|
||||
title: string;
|
||||
|
||||
@IsString()
|
||||
@Length(2, 100000)
|
||||
content: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsDate()
|
||||
publishedAt?: Date;
|
||||
|
||||
@IsString({ each: true })
|
||||
tags: string[];
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { CreateArticleInput } from './create-article.input';
|
||||
import { InputType, Field, PartialType } from '@nestjs/graphql';
|
||||
|
||||
@InputType()
|
||||
export class UpdateArticleInput extends PartialType(CreateArticleInput) {
|
||||
@Field(() => String)
|
||||
id: string;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ArticleHistory } from './../models/article-history.model';
|
||||
import { HideField, ObjectType } from '@nestjs/graphql';
|
||||
import { Column, Entity, Index } from 'typeorm';
|
||||
import { AppBaseEntity } from '../../commons/entities/app-base-entity';
|
||||
|
||||
@Entity()
|
||||
@ObjectType()
|
||||
export class Article extends AppBaseEntity {
|
||||
@Column()
|
||||
title: string;
|
||||
|
||||
@Column({ type: 'text' })
|
||||
content: string;
|
||||
|
||||
@Index()
|
||||
@Column({ nullable: true })
|
||||
publishedAt?: Date;
|
||||
|
||||
@Column({ type: 'varchar', array: true })
|
||||
tags: string[];
|
||||
|
||||
@HideField()
|
||||
@Column({ type: 'jsonb', default: [] })
|
||||
histories: ArticleHistory[];
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Column } from 'typeorm';
|
||||
import { ObjectType, Field } from '@nestjs/graphql';
|
||||
import { Article } from '../entities/article.entity';
|
||||
|
||||
@ObjectType()
|
||||
export class ArticleHistory {
|
||||
@Field(() => Object)
|
||||
payload: Partial<Article>;
|
||||
|
||||
updatedAt: Date;
|
||||
|
||||
automatic: boolean;
|
||||
|
||||
published: boolean;
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
import { ObjectScalar } from './scalars/object.scalar';
|
||||
import { Module } from '@nestjs/common';
|
||||
import { PasswordConverter } from './services/password-converter';
|
||||
import { PubSubModule } from './pub-sub/pub-sub.module';
|
||||
import { AuthModule } from '@nestjs-lib/auth';
|
||||
|
||||
@Module({
|
||||
providers: [PasswordConverter],
|
||||
exports: [PasswordConverter],
|
||||
imports: [PubSubModule],
|
||||
imports: [PubSubModule, AuthModule],
|
||||
providers: [PasswordConverter, ObjectScalar],
|
||||
exports: [PasswordConverter, AuthModule],
|
||||
})
|
||||
export class CommonsModule {}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { readFileSync } from 'fs';
|
||||
import * as yaml from 'js-yaml';
|
||||
import { join } from 'path';
|
||||
import { readConfiguration } from '@fennec/configuration';
|
||||
|
||||
export default () => {
|
||||
return yaml.load(
|
||||
readFileSync(join(__dirname, '../../../config.yml'), 'utf8'),
|
||||
) as unknown;
|
||||
return readConfiguration({
|
||||
etcd: {
|
||||
hosts: '192.168.31.2:2379',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -12,7 +12,7 @@ export class AppBaseEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@CreateDateColumn()
|
||||
@CreateDateColumn({ select: false })
|
||||
createdAt: Date;
|
||||
|
||||
@UpdateDateColumn({ select: false })
|
||||
|
||||
@@ -4,7 +4,7 @@ import { PubSub } from './pub-sub';
|
||||
|
||||
debug.enable('app:pubsub:*');
|
||||
|
||||
describe('PubSub', () => {
|
||||
describe.skip('PubSub', () => {
|
||||
let instance: PubSub;
|
||||
|
||||
beforeEach(async () => {
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { Scalar, CustomScalar } from '@nestjs/graphql';
|
||||
import { GraphQLJSONObject } from 'graphql-type-json';
|
||||
|
||||
@Scalar('Object', (type) => Object)
|
||||
export class ObjectScalar implements CustomScalar<number, Object> {
|
||||
description = GraphQLJSONObject.description;
|
||||
|
||||
parseValue = GraphQLJSONObject.parseValue;
|
||||
|
||||
serialize = GraphQLJSONObject.serialize;
|
||||
|
||||
parseLiteral = GraphQLJSONObject.parseLiteral;
|
||||
}
|
||||
@@ -112,7 +112,7 @@ export class BaseDbService<Entity extends AppBaseEntity> extends TypeormHelper {
|
||||
}
|
||||
}
|
||||
|
||||
async canYouRemoveWithIds(ids: string[]): Promise<void> {
|
||||
async canRemove(ids: string[]): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+7
-1
@@ -1,3 +1,4 @@
|
||||
import { ServiceRegister } from '@fennec/configuration';
|
||||
import { ValidationPipe } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
@@ -15,6 +16,11 @@ async function bootstrap() {
|
||||
}),
|
||||
);
|
||||
app.useGlobalFilters(new HttpExceptionFilter());
|
||||
await app.listen(configService.get<number>('http.port'));
|
||||
const server = await app.listen(configService.get<number>('http.port', 0));
|
||||
const port = server.address().port;
|
||||
const register = new ServiceRegister({ etcd: { hosts: 'http://rpi:2379' } });
|
||||
register.register('blog/api', `http://localhost:${port}`);
|
||||
register.register('admin.blog/api', `http://localhost:${port}`);
|
||||
register.register('api.blog', `http://localhost:${port}`);
|
||||
}
|
||||
bootstrap();
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { InputType } from '@nestjs/graphql';
|
||||
import { IsString, Length } from 'class-validator';
|
||||
|
||||
@InputType()
|
||||
export class CreateTagInput {
|
||||
@IsString()
|
||||
@Length(1, 100)
|
||||
name: string;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { CreateTagInput } from './create-tag.input';
|
||||
import { InputType, PartialType } from '@nestjs/graphql';
|
||||
import { IsUUID } from 'class-validator';
|
||||
|
||||
@InputType()
|
||||
export class UpdateTagInput extends PartialType(CreateTagInput) {
|
||||
@IsUUID()
|
||||
id: string;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { AppBaseEntity } from './../../commons/entities/app-base-entity';
|
||||
import { ObjectType } from '@nestjs/graphql';
|
||||
import { Column, Index, Entity } from 'typeorm';
|
||||
|
||||
@Entity()
|
||||
@ObjectType()
|
||||
export class Tag extends AppBaseEntity {
|
||||
@Index({ unique: true })
|
||||
@Column({ length: 100 })
|
||||
name: string;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Tag } from './entities/tag.entity';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { CommonsModule } from './../commons/commons.module';
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TagsService } from './tags.service';
|
||||
import { TagsResolver } from './tags.resolver';
|
||||
|
||||
@Module({
|
||||
imports: [CommonsModule, TypeOrmModule.forFeature([Tag])],
|
||||
providers: [TagsResolver, TagsService],
|
||||
})
|
||||
export class TagsModule {}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { TagsResolver } from './tags.resolver';
|
||||
import { TagsService } from './tags.service';
|
||||
|
||||
describe('TagsResolver', () => {
|
||||
let resolver: TagsResolver;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
TagsResolver,
|
||||
{
|
||||
provide: TagsService,
|
||||
useValue: {},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
resolver = module.get<TagsResolver>(TagsResolver);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(resolver).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Resolver, Query, Mutation, Args } from '@nestjs/graphql';
|
||||
import { TagsService } from './tags.service';
|
||||
import { Tag } from './entities/tag.entity';
|
||||
import { CreateTagInput } from './dto/create-tag.input';
|
||||
import { UpdateTagInput } from './dto/update-tag.input';
|
||||
|
||||
@Resolver(() => Tag)
|
||||
export class TagsResolver {
|
||||
constructor(private readonly tagsService: TagsService) {}
|
||||
|
||||
@Mutation(() => Tag)
|
||||
createTag(@Args('createTagInput') createTagInput: CreateTagInput) {
|
||||
return this.tagsService.create(createTagInput);
|
||||
}
|
||||
|
||||
@Query(() => [Tag], { name: 'tags' })
|
||||
findAll() {
|
||||
return this.tagsService.findAll();
|
||||
}
|
||||
|
||||
@Query(() => Tag, { name: 'tag' })
|
||||
findOne(@Args('id', { type: () => String }) id: string) {
|
||||
return this.tagsService.findOne(id);
|
||||
}
|
||||
|
||||
@Mutation(() => Tag)
|
||||
async updateTag(@Args('updateTagInput') updateTagInput: UpdateTagInput) {
|
||||
const tag = await this.tagsService.findOne(updateTagInput.id);
|
||||
return this.tagsService.update(tag, updateTagInput);
|
||||
}
|
||||
|
||||
@Mutation(() => Tag)
|
||||
removeTag(@Args('id', { type: () => String }) id: string) {
|
||||
return this.tagsService.remove(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Tag } from './entities/tag.entity';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { TagsService } from './tags.service';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
describe('TagsService', () => {
|
||||
let service: TagsService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
TagsService,
|
||||
{
|
||||
provide: getRepositoryToken(Tag),
|
||||
useValue: new Repository(),
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<TagsService>(TagsService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,45 @@
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { BaseDbService } from './../commons/services/base-db.service';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { CreateTagInput } from './dto/create-tag.input';
|
||||
import { UpdateTagInput } from './dto/update-tag.input';
|
||||
import { Tag } from './entities/tag.entity';
|
||||
|
||||
@Injectable()
|
||||
export class TagsService extends BaseDbService<Tag> {
|
||||
readonly uniqueFields: Array<keyof Tag> = ['name'];
|
||||
constructor(@InjectRepository(Tag) readonly repository: Repository<Tag>) {
|
||||
super(repository);
|
||||
}
|
||||
|
||||
/**
|
||||
* create or recover a tag
|
||||
*/
|
||||
async create(createTagInput: CreateTagInput): Promise<Tag> {
|
||||
const old = await this.repository.findOne({ name: createTagInput.name });
|
||||
return this.repository.save(
|
||||
old
|
||||
? this.repository.merge(old, createTagInput)
|
||||
: this.repository.create(createTagInput),
|
||||
);
|
||||
}
|
||||
|
||||
async findAll() {
|
||||
return await this.repository.find({
|
||||
order: { createdAt: 'DESC' },
|
||||
});
|
||||
}
|
||||
|
||||
async update(tag: Tag, updateTagInput: UpdateTagInput) {
|
||||
await this.isDuplicateEntityForUpdate(tag.id, updateTagInput);
|
||||
return await this.repository.save(
|
||||
this.repository.merge(tag, updateTagInput),
|
||||
);
|
||||
}
|
||||
|
||||
async remove(id: string) {
|
||||
await this.canRemove([id]);
|
||||
return await this.repository.softDelete({ id }).then((d) => d.affected);
|
||||
}
|
||||
}
|
||||
+33
-8
@@ -1,24 +1,49 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { INestApplication } from '@nestjs/common';
|
||||
import * as request from 'supertest';
|
||||
import { AppModule } from './../src/app.module';
|
||||
import { GraphQLModule } from '@nestjs/graphql';
|
||||
import {
|
||||
ApolloServerTestClient,
|
||||
createTestClient,
|
||||
} from 'apollo-server-testing';
|
||||
import { gql } from 'apollo-server-express';
|
||||
|
||||
describe('AppController (e2e)', () => {
|
||||
describe('ArticleResolver (e2e)', () => {
|
||||
let app: INestApplication;
|
||||
let apolloClient: ApolloServerTestClient;
|
||||
|
||||
beforeEach(async () => {
|
||||
beforeAll(async () => {
|
||||
const moduleFixture: TestingModule = await Test.createTestingModule({
|
||||
imports: [AppModule],
|
||||
}).compile();
|
||||
|
||||
app = moduleFixture.createNestApplication();
|
||||
await app.init();
|
||||
const module: GraphQLModule = moduleFixture.get<GraphQLModule>(
|
||||
GraphQLModule,
|
||||
);
|
||||
// apolloServer is protected, we need to cast module to any to get it
|
||||
apolloClient = createTestClient((module as any).apolloServer);
|
||||
});
|
||||
it('QUERY hello', async () => {
|
||||
const res = await apolloClient.query({
|
||||
query: gql`
|
||||
query {
|
||||
hello {
|
||||
message
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {},
|
||||
});
|
||||
expect(res.data).toEqual({
|
||||
hello: {
|
||||
message: 'Hello, World!',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('/ (GET)', () => {
|
||||
return request(app.getHttpServer())
|
||||
.get('/')
|
||||
.expect(200)
|
||||
.expect('Hello World!');
|
||||
afterAll(async () => {
|
||||
await app?.close();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { INestApplication } from '@nestjs/common';
|
||||
import * as request from 'supertest';
|
||||
import { AppModule } from '../src/app.module';
|
||||
import { GraphQLModule } from '@nestjs/graphql';
|
||||
import {
|
||||
ApolloServerTestClient,
|
||||
createTestClient,
|
||||
} from 'apollo-server-testing';
|
||||
import { gql } from 'apollo-server-express';
|
||||
|
||||
describe('AppController (e2e)', () => {
|
||||
let app: INestApplication;
|
||||
let apolloClient: ApolloServerTestClient;
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleFixture: TestingModule = await Test.createTestingModule({
|
||||
imports: [AppModule],
|
||||
}).compile();
|
||||
|
||||
app = moduleFixture.createNestApplication();
|
||||
await app.init();
|
||||
const module: GraphQLModule = moduleFixture.get<GraphQLModule>(
|
||||
GraphQLModule,
|
||||
);
|
||||
// apolloServer is protected, we need to cast module to any to get it
|
||||
apolloClient = createTestClient((module as any).apolloServer);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await app?.close();
|
||||
});
|
||||
});
|
||||
@@ -1,8 +0,0 @@
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
console.log(i * 10);
|
||||
}
|
||||
console.error('Error Message');
|
||||
console.error('Error Message 2');
|
||||
console.log('Bye-bye');
|
||||
|
||||
process.exit(1);
|
||||
@@ -1,115 +0,0 @@
|
||||
{
|
||||
"secret": "boardcat",
|
||||
"ref": "refs/heads/master",
|
||||
"before": "429de1eaedf1da83f1e0e3ac3d8b20e771b7051c",
|
||||
"after": "429de1eaedf1da83f1e0e3ac3d8b20e771b7051c",
|
||||
"compare_url": "",
|
||||
"commits": [
|
||||
{
|
||||
"id": "429de1eaedf1da83f1e0e3ac3d8b20e771b7051c",
|
||||
"message": "test(pipeline-tasks): pass test cases.\n",
|
||||
"url": "https://git.ivanli.cc/Fennec/fennec-be/commit/429de1eaedf1da83f1e0e3ac3d8b20e771b7051c",
|
||||
"author": {
|
||||
"name": "Ivan",
|
||||
"email": "ivanli@live.cn",
|
||||
"username": ""
|
||||
},
|
||||
"committer": {
|
||||
"name": "Ivan",
|
||||
"email": "ivanli@live.cn",
|
||||
"username": ""
|
||||
},
|
||||
"verification": null,
|
||||
"timestamp": "0001-01-01T00:00:00Z",
|
||||
"added": null,
|
||||
"removed": null,
|
||||
"modified": null
|
||||
}
|
||||
],
|
||||
"head_commit": null,
|
||||
"repository": {
|
||||
"id": 3,
|
||||
"owner": {
|
||||
"id": 3,
|
||||
"login": "Fennec",
|
||||
"full_name": "",
|
||||
"email": "",
|
||||
"avatar_url": "https://git.ivanli.cc/user/avatar/Fennec/-1",
|
||||
"language": "",
|
||||
"is_admin": false,
|
||||
"last_login": "1970-01-01T08:00:00+08:00",
|
||||
"created": "2021-01-30T16:46:11+08:00",
|
||||
"username": "Fennec"
|
||||
},
|
||||
"name": "fennec-be",
|
||||
"full_name": "Fennec/fennec-be",
|
||||
"description": "Fennec CI/CD Back-End",
|
||||
"empty": false,
|
||||
"private": false,
|
||||
"fork": false,
|
||||
"template": false,
|
||||
"parent": null,
|
||||
"mirror": false,
|
||||
"size": 1897,
|
||||
"html_url": "https://git.ivanli.cc/Fennec/fennec-be",
|
||||
"ssh_url": "ssh://gitea@git.ivanli.cc:7018/Fennec/fennec-be.git",
|
||||
"clone_url": "https://git.ivanli.cc/Fennec/fennec-be.git",
|
||||
"original_url": "",
|
||||
"website": "",
|
||||
"stars_count": 1,
|
||||
"forks_count": 0,
|
||||
"watchers_count": 1,
|
||||
"open_issues_count": 0,
|
||||
"open_pr_counter": 0,
|
||||
"release_counter": 0,
|
||||
"default_branch": "master",
|
||||
"archived": false,
|
||||
"created_at": "2021-01-31T09:58:38+08:00",
|
||||
"updated_at": "2021-03-27T15:57:00+08:00",
|
||||
"permissions": {
|
||||
"admin": false,
|
||||
"push": false,
|
||||
"pull": false
|
||||
},
|
||||
"has_issues": true,
|
||||
"internal_tracker": {
|
||||
"enable_time_tracker": true,
|
||||
"allow_only_contributors_to_track_time": true,
|
||||
"enable_issue_dependencies": true
|
||||
},
|
||||
"has_wiki": true,
|
||||
"has_pull_requests": true,
|
||||
"has_projects": true,
|
||||
"ignore_whitespace_conflicts": false,
|
||||
"allow_merge_commits": true,
|
||||
"allow_rebase": true,
|
||||
"allow_rebase_explicit": true,
|
||||
"allow_squash_merge": true,
|
||||
"avatar_url": "",
|
||||
"internal": false
|
||||
},
|
||||
"pusher": {
|
||||
"id": 1,
|
||||
"login": "Ivan",
|
||||
"full_name": "Ivan Li",
|
||||
"email": "ivan@noreply.%(DOMAIN)s",
|
||||
"avatar_url": "https://git.ivanli.cc/user/avatar/Ivan/-1",
|
||||
"language": "zh-CN",
|
||||
"is_admin": true,
|
||||
"last_login": "2021-03-26T22:28:05+08:00",
|
||||
"created": "2021-01-23T18:15:30+08:00",
|
||||
"username": "Ivan"
|
||||
},
|
||||
"sender": {
|
||||
"id": 1,
|
||||
"login": "Ivan",
|
||||
"full_name": "Ivan Li",
|
||||
"email": "ivan@noreply.%(DOMAIN)s",
|
||||
"avatar_url": "https://git.ivanli.cc/user/avatar/Ivan/-1",
|
||||
"language": "zh-CN",
|
||||
"is_admin": true,
|
||||
"last_login": "2021-03-26T22:28:05+08:00",
|
||||
"created": "2021-01-23T18:15:30+08:00",
|
||||
"username": "Ivan"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
let timer;
|
||||
let count = 0;
|
||||
setTimeout(() => clearInterval(timer), 1_000);
|
||||
|
||||
timer = setInterval(() => {
|
||||
console.log(++count * 10);
|
||||
}, 95);
|
||||
@@ -0,0 +1,15 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const transformer = require('@nestjs/graphql/plugin');
|
||||
|
||||
module.exports.name = 'nestjs-graphql-transformer';
|
||||
// you should change the version number anytime you change the configuration below - otherwise, jest will not detect changes
|
||||
module.exports.version = 1;
|
||||
|
||||
module.exports.factory = (cs) => {
|
||||
return transformer.before(
|
||||
{
|
||||
// @nestjs/graphql/plugin options (can be empty)
|
||||
},
|
||||
cs.tsCompiler.program,
|
||||
);
|
||||
};
|
||||
@@ -5,5 +5,12 @@
|
||||
"testRegex": ".e2e-spec.ts$",
|
||||
"transform": {
|
||||
"^.+\\.(t|j)s$": "ts-jest"
|
||||
},
|
||||
"globals": {
|
||||
"ts-jest": {
|
||||
"astTransformers": {
|
||||
"before": ["<rootDir>/graphql-e2e.ts"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -6,10 +6,12 @@
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"lib": ["es2020"],
|
||||
"target": "es2017",
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
"baseUrl": "./",
|
||||
"incremental": true
|
||||
"incremental": true,
|
||||
"skipLibCheck": true
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user