Merge pull request #6 from MrSedan/dev

Dev docker and proxy update
This commit is contained in:
2023-11-30 19:33:48 +03:00
committed by GitHub
20 changed files with 416 additions and 7 deletions

2
.gitmodules vendored
View File

@@ -1,4 +1,4 @@
[submodule "neuro-reply-bot-reworked"] [submodule "neuro-reply-bot-reworked"]
path = neuro-reply-bot-reworked path = neuro-reply-bot-reworked
url = git@github.com:MrSedan/neuro-reply-bot-reworked.git url = https://github.com/MrSedan/neuro-reply-bot-reworked.git
branch = dev branch = dev

2
backend/.dockerignore Normal file
View File

@@ -0,0 +1,2 @@
node_modules
dockerfile

9
backend/Dockerfile Normal file
View File

@@ -0,0 +1,9 @@
FROM node:lts-alpine
WORKDIR /app
RUN corepack enable && corepack prepare pnpm@latest --activate
COPY package.json pnpm-*.yaml ./
RUN pnpm install --frozen-lockfile
COPY . .
RUN pnpm build
EXPOSE 3000
CMD [ "pnpm", "start:prod" ]

8
backend/Dockerfile.dev Normal file
View File

@@ -0,0 +1,8 @@
FROM node:lts-alpine
WORKDIR /app
RUN corepack enable && corepack prepare pnpm@latest --activate
COPY package.json pnpm-*.yaml ./
RUN pnpm install --frozen-lockfile
COPY . .
EXPOSE 3000
CMD [ "pnpm", "start:dev" ]

View File

@@ -0,0 +1,22 @@
import { Column, Entity, JoinColumn, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
import { ProxyUser } from './proxy_user.entity';
@Entity()
export class Payment {
constructor(props?: Partial<Payment>) {
Object.assign(this, props);
}
@PrimaryGeneratedColumn('increment')
public id!: number;
@Column()
public user_uuid!: string;
@Column({ type: 'timestamptz' })
public payTime!: Date;
@ManyToOne(() => ProxyUser, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'user_uuid' })
user: ProxyUser;
}

View File

@@ -22,6 +22,9 @@ export class Post {
@Column({ type: 'timestamptz' }) @Column({ type: 'timestamptz' })
public timestamp!: Date; public timestamp!: Date;
@Column({ type: 'timestamptz', nullable: true })
public edit_timestamp?: Date;
@Column({ nullable: false }) @Column({ nullable: false })
public from_user_id!: string; public from_user_id!: string;

View File

@@ -0,0 +1,35 @@
import { Column, Entity, JoinColumn, OneToMany, OneToOne, PrimaryGeneratedColumn } from 'typeorm';
import { Payment } from './payment.entity';
import { User } from './user.entity';
@Entity()
export class ProxyUser {
constructor(props?: Partial<ProxyUser>) {
Object.assign(this, props);
}
@PrimaryGeneratedColumn('uuid')
public uuid!: string;
@Column({ nullable: false, unique: true })
public userName!: string;
@Column({ nullable: true })
public description?: string;
@Column({ nullable: false })
public link!: string;
@Column({ nullable: false, type: 'timestamptz' })
public connectDate!: Date;
@Column({ nullable: true })
public user_id!: string;
@OneToOne(() => User, (user) => user.id, { onDelete: 'CASCADE', onUpdate: 'CASCADE', nullable: true })
@JoinColumn({ name: 'user_id' })
public user?: User;
@OneToMany(() => Payment, (payment) => payment.user)
public payments: Payment[];
}

View File

@@ -4,8 +4,10 @@ import { User } from './database/user.entity';
import { Admin } from './database/admin.entity'; import { Admin } from './database/admin.entity';
import { Post } from './database/post.entity'; import { Post } from './database/post.entity';
import { Image } from './database/image.entity'; import { Image } from './database/image.entity';
import { Payment } from './database/payment.entity';
import { ProxyUser } from './database/proxy_user.entity';
@Module({ @Module({
imports: [TypeOrmModule.forFeature([User, Admin, Post, Image])], imports: [TypeOrmModule.forFeature([User, Admin, Post, Image, Payment, ProxyUser])],
exports: [TypeOrmModule], exports: [TypeOrmModule],
}) })
export class LibsModule {} export class LibsModule {}

View File

@@ -11,7 +11,7 @@
"start": "nest start", "start": "nest start",
"start:dev": "nest start --watch", "start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch", "start:debug": "nest start --debug --watch",
"start:prod": "node dist/main", "start:prod": "node dist/src/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest", "test": "jest",
"test:watch": "jest --watch", "test:watch": "jest --watch",

View File

@@ -0,0 +1,12 @@
import { Controller, Get } from '@nestjs/common';
import { ApiOperation, ApiTags } from '@nestjs/swagger';
@ApiTags('App')
@Controller()
export class AppController {
@ApiOperation({ description: 'check site availability' })
@Get('ping')
pingpong() {
return 'pong';
}
}

View File

@@ -2,15 +2,25 @@ import { Module } from '@nestjs/common';
import { TypeOrmModule, TypeOrmModuleOptions } from '@nestjs/typeorm'; import { TypeOrmModule, TypeOrmModuleOptions } from '@nestjs/typeorm';
import { config } from 'config'; import { config } from 'config';
import { LibsModule } from 'libs/libs.module'; import { LibsModule } from 'libs/libs.module';
import { AppController } from './app.controller';
import { AdminModule } from './modules/admin/admin.module'; import { AdminModule } from './modules/admin/admin.module';
import { ImageModule } from './modules/image/image.module'; import { ImageModule } from './modules/image/image.module';
import { AppInitService } from './modules/initialization/app.init.service'; import { AppInitService } from './modules/initialization/app.init.service';
import { PostModule } from './modules/post/post.module'; import { PostModule } from './modules/post/post.module';
import { ProxyModule } from './modules/proxy/proxy.module';
import { UserModule } from './modules/user/user.module'; import { UserModule } from './modules/user/user.module';
@Module({ @Module({
imports: [LibsModule, PostModule, AdminModule, UserModule, ImageModule, TypeOrmModule.forRoot(<TypeOrmModuleOptions>config.database)], imports: [
controllers: [], LibsModule,
PostModule,
AdminModule,
UserModule,
ImageModule,
ProxyModule,
TypeOrmModule.forRoot(<TypeOrmModuleOptions>config.database),
],
controllers: [AppController],
providers: [AppInitService], providers: [AppInitService],
}) })
export class AppModule {} export class AppModule {}

View File

@@ -39,4 +39,10 @@ export class PostController {
async editPost(@Param('postId') postId: string, @Body() data: IEditPost) { async editPost(@Param('postId') postId: string, @Body() data: IEditPost) {
return await this.postService.editPost(postId, data); return await this.postService.editPost(postId, data);
} }
@ApiOperation({ description: 'Get post to post' })
@Get('post')
async post() {
return await this.postService.post();
}
} }

View File

@@ -41,7 +41,7 @@ export class PostService {
} }
if (post.text !== data.text) { if (post.text !== data.text) {
post.text = data.text; post.text = data.text;
post.timestamp = new Date(); post.edit_timestamp = new Date();
await this.postRepository.save(post); await this.postRepository.save(post);
} }
return post; return post;
@@ -95,4 +95,23 @@ export class PostService {
throw new HttpException("Can't find post with this media group id", HttpStatus.BAD_REQUEST); throw new HttpException("Can't find post with this media group id", HttpStatus.BAD_REQUEST);
} }
} }
async post() {
try {
const posts = await this.postRepository.find({ order: { timestamp: 'ASC' }, where: { posted: false }, relations: { images: true } });
if (!posts.length) throw new HttpException('Nothing to post', HttpStatus.NOT_FOUND);
const post = posts[0];
post.posted = true;
this.logger.log(`[post.post] Post ${post.uuid} is posted`);
await this.postRepository.save(post);
return post;
} catch (error) {
if (error instanceof HttpException) {
this.logger.debug('[post.post] Not found');
throw error;
}
this.logger.debug(`[post.post] error: ${JSON.stringify(error)}`);
throw new HttpException('Bad data', HttpStatus.BAD_REQUEST);
}
}
} }

View File

@@ -0,0 +1,46 @@
import { Body, Controller, Get, Param, Post } from '@nestjs/common';
import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { IOperation, IProxyUser } from './proxy.dto';
import { ProxyService } from './proxy.service';
@ApiTags('Proxy')
@Controller('proxy')
export class ProxyController {
constructor(private proxyService: ProxyService) {}
@ApiOperation({ description: 'Method to create a new proxy user' })
@Post('new-user')
async newUser(@Body() data: IProxyUser) {
return await this.proxyService.newUser(data);
}
@ApiOperation({ description: 'get user by its username' })
@Get('get-user/:userName')
async getUser(@Param('userName') userName: string) {
return await this.proxyService.getUser(userName);
}
@ApiOperation({ description: 'get all users of proxy' })
@Get('get-all-users')
async getAllUsers() {
return await this.proxyService.getAllUsers();
}
@ApiOperation({ description: 'adding an operation to user' })
@Post('operation/add')
async addOperation(@Body() data: IOperation) {
return await this.proxyService.addOperation(data);
}
@ApiOperation({ description: 'get user payments' })
@Get('operation/get/:userName')
async getOperations(@Param('userName') userName: string) {
return this.proxyService.getOperations(userName);
}
@ApiOperation({ description: 'get all payments' })
@Get('operation/get-all')
async getAllOperations() {
return this.proxyService.getAllOperations();
}
}

View File

@@ -0,0 +1,12 @@
import { ApiProperty } from '@nestjs/swagger';
export class IProxyUser {
@ApiProperty({ description: 'user name of user to identify them', example: 'username' }) readonly userName!: string;
@ApiProperty({ description: 'some user description if you want', example: 'Description of user' }) readonly description?: string;
@ApiProperty({ description: 'user link to connect to the proxy', example: 'vless://....' }) readonly link!: string;
@ApiProperty({ description: 'telegram user id to connect to user entity', example: '187564' }) readonly user_id?: string;
}
export class IOperation {
@ApiProperty({ description: 'user name of user, that made new operation', example: 'username' }) readonly userName!: string;
}

View File

@@ -0,0 +1,11 @@
import { Module } from '@nestjs/common';
import { LibsModule } from 'libs/libs.module';
import { ProxyController } from './proxy.controller';
import { ProxyService } from './proxy.service';
@Module({
imports: [LibsModule],
controllers: [ProxyController],
providers: [ProxyService],
})
export class ProxyModule {}

View File

@@ -0,0 +1,106 @@
import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Payment } from 'libs/database/payment.entity';
import { ProxyUser } from 'libs/database/proxy_user.entity';
import { User } from 'libs/database/user.entity';
import { Repository } from 'typeorm';
import { IOperation, IProxyUser } from './proxy.dto';
@Injectable()
export class ProxyService {
private readonly logger: Logger = new Logger(ProxyService.name);
constructor(
@InjectRepository(ProxyUser) private proxyUserRepository: Repository<ProxyUser>,
@InjectRepository(Payment) private paymentRepository: Repository<Payment>,
@InjectRepository(User) private userRepository: Repository<User>,
) {}
async newUser(data: IProxyUser) {
try {
if (
(await this.proxyUserRepository.findOne({ where: { userName: data.userName } })) || data.user_id
? await this.proxyUserRepository.findOne({ where: { user_id: data.user_id } })
: false
)
throw new HttpException('User already exists', HttpStatus.FOUND);
const proxyUser = new ProxyUser();
const user = data.user_id ? await this.userRepository.findOne({ where: { id: data.user_id } }) : null;
if (user) proxyUser.user = user;
proxyUser.description = data.description;
proxyUser.connectDate = new Date();
proxyUser.userName = data.userName;
proxyUser.link = data.link;
return await this.proxyUserRepository.save(proxyUser);
} catch (error) {
if (error instanceof HttpException) {
this.logger.debug(`[proxy.newUser] error: user already created`);
throw error;
}
this.logger.debug(`[proxy.newUser] error: ${JSON.stringify(error)}`);
throw new HttpException('Bad data', HttpStatus.BAD_REQUEST);
}
}
async getUser(userName: string) {
try {
const user = await this.proxyUserRepository.findOne({ where: { userName: userName } });
if (!user) throw new HttpException('Not found', HttpStatus.NOT_FOUND);
return user;
} catch (error) {
if (error instanceof HttpException) {
this.logger.debug(`[proxy.getUser] error: not found`);
throw error;
}
this.logger.debug(`[proxy.getUser] error: ${JSON.stringify(error)}`);
throw new HttpException('Bad data', HttpStatus.BAD_REQUEST);
}
}
async getAllUsers() {
try {
return await this.proxyUserRepository.find();
} catch (error) {
this.logger.debug(`[proxy.getAllUsers] error: ${JSON.stringify(error)}`);
throw new HttpException('Bad data', HttpStatus.BAD_REQUEST);
}
}
async addOperation(data: IOperation) {
try {
const user = await this.proxyUserRepository.findOne({ where: { userName: data.userName } });
if (!user) throw new HttpException('Not found', HttpStatus.NOT_FOUND);
return await this.paymentRepository.save({ payTime: new Date(), user: user });
} catch (error) {
if (error instanceof HttpException) {
this.logger.debug(`[proxy.addOperation] error: not found`);
throw error;
}
this.logger.debug(`[proxy.addOperation] error: ${JSON.stringify(error)}`);
throw new HttpException('Bad data', HttpStatus.BAD_REQUEST);
}
}
async getOperations(userName: string) {
try {
const user = await this.proxyUserRepository.findOne({ where: { userName: userName }, relations: { payments: true } });
if (!user) throw new HttpException('Not found', HttpStatus.NOT_FOUND);
return user.payments;
} catch (error) {
if (error instanceof HttpException) {
this.logger.debug(`[proxy.addOperation] error: not found`);
throw error;
}
this.logger.debug(`[proxy.addOperation] error: ${error}`);
throw new HttpException('Bad data', HttpStatus.BAD_REQUEST);
}
}
async getAllOperations() {
try {
return await this.paymentRepository.find();
} catch (error) {
this.logger.debug(`[proxy.addOperation] error: ${error}`);
throw new HttpException('Bad data', HttpStatus.BAD_REQUEST);
}
}
}

74
docker-compose.dev.yml Normal file
View File

@@ -0,0 +1,74 @@
version: '3.9'
services:
db:
container_name: neuro_db_dev
image: postgres:alpine
environment:
- POSTGRES_USER=${DATABASE_USER}
- POSTGRES_PASSWORD=${DATABASE_PASSWORD}
- POSTGRES_DB=${DATABASE_NAME}
- PGDATA=/var/lib/postgresql/data/pgdata
ports:
- "${DATABASE_PORT}:5432"
env_file:
- .env
networks:
- labnet
volumes:
- neuro_postgres_db:/var/lib/postgresql/data
restart: always
backend:
container_name: neuro_backend_dev
build:
context: ./backend
dockerfile: Dockerfile.dev
develop:
watch:
- action: sync
path: ./backend/src
target: ./app/src
ignore:
- node_modules/
- action: rebuild
path: package.json
- action: rebuild
path: ./libs
environment:
- DATABASE_PORT=5432
- DATABASE_HOST=db
env_file:
- .env
networks:
- labnet
ports:
- 3000:3000
depends_on:
- db
restart: always
bot:
container_name: neuro_bot_dev
build:
context: ./neuro-reply-bot-reworked
dockerfile: Dockerfile.dev
environment:
- API_URL=http://backend:3000
networks:
- labnet
depends_on:
- backend
restart: always
develop:
watch:
- action: sync
path: ./neuro-reply-bot-reworked/
target: ./app/
volumes:
neuro_postgres_db:
driver: local
networks:
labnet:

View File

@@ -13,9 +13,41 @@ services:
- "${DATABASE_PORT}:5432" - "${DATABASE_PORT}:5432"
env_file: env_file:
- .env - .env
networks:
- labnet
volumes: volumes:
- neuro_postgres_db:/var/lib/postgresql/data - neuro_postgres_db:/var/lib/postgresql/data
restart: always restart: always
backend:
container_name: neuro_backend
build: ./backend
environment:
- DATABASE_PORT=5432
- DATABASE_HOST=db
env_file:
- .env
networks:
- labnet
ports:
- 3000:3000
depends_on:
- db
restart: always
bot:
container_name: neuro_bot
build: ./neuro-reply-bot-reworked
environment:
- API_URL=http://backend:3000
networks:
- labnet
depends_on:
- backend
restart: always
volumes: volumes:
neuro_postgres_db: neuro_postgres_db:
driver: local driver: local
networks:
labnet: