mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-23 18:06:42 +02:00
chore: add techbase to apps-archived
Integrated techbase (software comparison platform) into monorepo structure: - Created NestJS backend with votes and comments modules - Migrated from external Supabase to own PostgreSQL - Set up Drizzle ORM schema for votes and comments - Created API client replacing Supabase in Astro frontend - Added environment configuration (port 3021) Archived immediately as it's not yet ready for active development. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
17313473aa
commit
34c879929b
161 changed files with 12613 additions and 0 deletions
|
|
@ -0,0 +1,57 @@
|
|||
import { Controller, Post, Get, Patch, Delete, Body, Param, Req } from '@nestjs/common';
|
||||
import { Request } from 'express';
|
||||
import { CommentsService } from './comments.service';
|
||||
import { CreateCommentDto } from './dto/create-comment.dto';
|
||||
|
||||
@Controller('comments')
|
||||
export class CommentsController {
|
||||
constructor(private readonly commentsService: CommentsService) {}
|
||||
|
||||
@Post()
|
||||
async createComment(@Body() createCommentDto: CreateCommentDto, @Req() req: Request) {
|
||||
const ipAddress = req.ip || req.socket.remoteAddress || 'unknown';
|
||||
return this.commentsService.createComment(
|
||||
createCommentDto.softwareId,
|
||||
createCommentDto.userName,
|
||||
createCommentDto.comment,
|
||||
ipAddress
|
||||
);
|
||||
}
|
||||
|
||||
@Get(':softwareId')
|
||||
async getComments(@Param('softwareId') softwareId: string) {
|
||||
return this.commentsService.getApprovedComments(softwareId);
|
||||
}
|
||||
}
|
||||
|
||||
@Controller('admin/comments')
|
||||
export class AdminCommentsController {
|
||||
constructor(private readonly commentsService: CommentsService) {}
|
||||
|
||||
@Get()
|
||||
async getAllComments() {
|
||||
return this.commentsService.getAllComments();
|
||||
}
|
||||
|
||||
@Get('pending')
|
||||
async getPendingComments() {
|
||||
return this.commentsService.getPendingComments();
|
||||
}
|
||||
|
||||
@Patch(':id/approve')
|
||||
async approveComment(@Param('id') id: string) {
|
||||
// TODO: Get actual moderator ID from auth
|
||||
return this.commentsService.approveComment(id, 'admin');
|
||||
}
|
||||
|
||||
@Patch(':id/reject')
|
||||
async rejectComment(@Param('id') id: string) {
|
||||
// TODO: Get actual moderator ID from auth
|
||||
return this.commentsService.rejectComment(id, 'admin');
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
async deleteComment(@Param('id') id: string) {
|
||||
return this.commentsService.deleteComment(id);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { CommentsController, AdminCommentsController } from './comments.controller';
|
||||
import { CommentsService } from './comments.service';
|
||||
|
||||
@Module({
|
||||
controllers: [CommentsController, AdminCommentsController],
|
||||
providers: [CommentsService],
|
||||
exports: [CommentsService],
|
||||
})
|
||||
export class CommentsModule {}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
import { Injectable, Inject } from '@nestjs/common';
|
||||
import { eq, and, desc } from 'drizzle-orm';
|
||||
import { DATABASE_CONNECTION } from '../db/database.module';
|
||||
import { type Database } from '../db/connection';
|
||||
import { comments, type Comment, type NewComment } from '../db/schema';
|
||||
import { createHash } from 'crypto';
|
||||
|
||||
@Injectable()
|
||||
export class CommentsService {
|
||||
constructor(@Inject(DATABASE_CONNECTION) private db: Database) {}
|
||||
|
||||
private hashIp(ip: string): string {
|
||||
return createHash('sha256').update(ip).digest('hex').substring(0, 32);
|
||||
}
|
||||
|
||||
async createComment(
|
||||
softwareId: string,
|
||||
userName: string,
|
||||
commentText: string,
|
||||
ipAddress: string
|
||||
): Promise<{ success: boolean; message: string }> {
|
||||
const ipHash = this.hashIp(ipAddress);
|
||||
|
||||
const newComment: NewComment = {
|
||||
softwareId,
|
||||
userName,
|
||||
comment: commentText,
|
||||
ipHash,
|
||||
isApproved: false,
|
||||
isSpam: false,
|
||||
};
|
||||
|
||||
await this.db.insert(comments).values(newComment);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Comment submitted successfully. It will be visible after moderation.',
|
||||
};
|
||||
}
|
||||
|
||||
async getApprovedComments(softwareId: string): Promise<Comment[]> {
|
||||
return this.db
|
||||
.select()
|
||||
.from(comments)
|
||||
.where(and(eq(comments.softwareId, softwareId), eq(comments.isApproved, true)))
|
||||
.orderBy(desc(comments.createdAt));
|
||||
}
|
||||
|
||||
async getAllComments(): Promise<Comment[]> {
|
||||
return this.db.select().from(comments).orderBy(desc(comments.createdAt));
|
||||
}
|
||||
|
||||
async getPendingComments(): Promise<Comment[]> {
|
||||
return this.db
|
||||
.select()
|
||||
.from(comments)
|
||||
.where(and(eq(comments.isApproved, false), eq(comments.isSpam, false)))
|
||||
.orderBy(desc(comments.createdAt));
|
||||
}
|
||||
|
||||
async approveComment(id: string, moderatorId: string): Promise<{ success: boolean }> {
|
||||
await this.db
|
||||
.update(comments)
|
||||
.set({
|
||||
isApproved: true,
|
||||
moderatedAt: new Date(),
|
||||
moderatedBy: moderatorId,
|
||||
})
|
||||
.where(eq(comments.id, id));
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
async rejectComment(id: string, moderatorId: string): Promise<{ success: boolean }> {
|
||||
await this.db
|
||||
.update(comments)
|
||||
.set({
|
||||
isSpam: true,
|
||||
moderatedAt: new Date(),
|
||||
moderatedBy: moderatorId,
|
||||
})
|
||||
.where(eq(comments.id, id));
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
async deleteComment(id: string): Promise<{ success: boolean }> {
|
||||
await this.db.delete(comments).where(eq(comments.id, id));
|
||||
return { success: true };
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import { IsString, IsNotEmpty, MaxLength, MinLength } from 'class-validator';
|
||||
|
||||
export class CreateCommentDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
softwareId: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@MinLength(2)
|
||||
@MaxLength(100)
|
||||
userName: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@MinLength(10)
|
||||
@MaxLength(2000)
|
||||
comment: string;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue