mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-22 02:26:41 +02:00
chore: archive finance, mail, moodlit apps and rename voxel-lava
- Move finance, mail, moodlit to apps-archived for later development - Rename games/voxel-lava to games/voxelava 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
c3c272abc9
commit
ace7fa8f7f
427 changed files with 0 additions and 0 deletions
57
apps-archived/mail/apps/backend/src/folder/dto/folder.dto.ts
Normal file
57
apps-archived/mail/apps/backend/src/folder/dto/folder.dto.ts
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
import { IsString, IsOptional, IsUUID, IsBoolean, MaxLength, IsIn } from 'class-validator';
|
||||
import { Transform } from 'class-transformer';
|
||||
|
||||
export class CreateFolderDto {
|
||||
@IsUUID()
|
||||
accountId: string;
|
||||
|
||||
@IsString()
|
||||
@MaxLength(255)
|
||||
name: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@MaxLength(7)
|
||||
color?: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@MaxLength(50)
|
||||
icon?: string;
|
||||
}
|
||||
|
||||
export class UpdateFolderDto {
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@MaxLength(255)
|
||||
name?: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@MaxLength(7)
|
||||
color?: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@MaxLength(50)
|
||||
icon?: string;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
isHidden?: boolean;
|
||||
}
|
||||
|
||||
export class FolderQueryDto {
|
||||
@IsUUID()
|
||||
@IsOptional()
|
||||
accountId?: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@IsIn(['inbox', 'sent', 'drafts', 'trash', 'spam', 'archive', 'custom'])
|
||||
type?: string;
|
||||
|
||||
@IsOptional()
|
||||
@Transform(({ value }) => value === 'true')
|
||||
includeHidden?: boolean;
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Patch,
|
||||
Delete,
|
||||
Body,
|
||||
Param,
|
||||
Query,
|
||||
UseGuards,
|
||||
ParseUUIDPipe,
|
||||
BadRequestException,
|
||||
} from '@nestjs/common';
|
||||
import { JwtAuthGuard, CurrentUser, CurrentUserData } from '@manacore/shared-nestjs-auth';
|
||||
import { FolderService } from './folder.service';
|
||||
import { CreateFolderDto, UpdateFolderDto, FolderQueryDto } from './dto/folder.dto';
|
||||
|
||||
@Controller('folders')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
export class FolderController {
|
||||
constructor(private readonly folderService: FolderService) {}
|
||||
|
||||
@Get()
|
||||
async findAll(@CurrentUser() user: CurrentUserData, @Query() query: FolderQueryDto) {
|
||||
const folders = await this.folderService.findByUserId(user.userId, query);
|
||||
return { folders };
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
async findOne(@CurrentUser() user: CurrentUserData, @Param('id', ParseUUIDPipe) id: string) {
|
||||
const folder = await this.folderService.findById(id, user.userId);
|
||||
if (!folder) {
|
||||
return { folder: null };
|
||||
}
|
||||
return { folder };
|
||||
}
|
||||
|
||||
@Post()
|
||||
async create(@CurrentUser() user: CurrentUserData, @Body() dto: CreateFolderDto) {
|
||||
const folder = await this.folderService.create({
|
||||
...dto,
|
||||
userId: user.userId,
|
||||
type: 'custom',
|
||||
path: dto.name, // For custom folders, path is the name
|
||||
isSystem: false,
|
||||
});
|
||||
return { folder };
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
async update(
|
||||
@CurrentUser() user: CurrentUserData,
|
||||
@Param('id', ParseUUIDPipe) id: string,
|
||||
@Body() dto: UpdateFolderDto
|
||||
) {
|
||||
const existingFolder = await this.folderService.findById(id, user.userId);
|
||||
if (!existingFolder) {
|
||||
throw new BadRequestException('Folder not found');
|
||||
}
|
||||
|
||||
// Don't allow renaming system folders
|
||||
if (existingFolder.isSystem && dto.name) {
|
||||
throw new BadRequestException('Cannot rename system folders');
|
||||
}
|
||||
|
||||
const folder = await this.folderService.update(id, user.userId, dto);
|
||||
return { folder };
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
async delete(@CurrentUser() user: CurrentUserData, @Param('id', ParseUUIDPipe) id: string) {
|
||||
await this.folderService.delete(id, user.userId);
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
@Post(':id/hide')
|
||||
async toggleHidden(@CurrentUser() user: CurrentUserData, @Param('id', ParseUUIDPipe) id: string) {
|
||||
const folder = await this.folderService.findById(id, user.userId);
|
||||
if (!folder) {
|
||||
throw new BadRequestException('Folder not found');
|
||||
}
|
||||
|
||||
const updatedFolder = await this.folderService.update(id, user.userId, {
|
||||
isHidden: !folder.isHidden,
|
||||
});
|
||||
return { folder: updatedFolder };
|
||||
}
|
||||
}
|
||||
10
apps-archived/mail/apps/backend/src/folder/folder.module.ts
Normal file
10
apps-archived/mail/apps/backend/src/folder/folder.module.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { FolderController } from './folder.controller';
|
||||
import { FolderService } from './folder.service';
|
||||
|
||||
@Module({
|
||||
controllers: [FolderController],
|
||||
providers: [FolderService],
|
||||
exports: [FolderService],
|
||||
})
|
||||
export class FolderModule {}
|
||||
200
apps-archived/mail/apps/backend/src/folder/folder.service.ts
Normal file
200
apps-archived/mail/apps/backend/src/folder/folder.service.ts
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
import { Injectable, Inject, NotFoundException } from '@nestjs/common';
|
||||
import { eq, and, desc, sql } from 'drizzle-orm';
|
||||
import { DATABASE_CONNECTION } from '../db/database.module';
|
||||
import { type Database } from '../db/connection';
|
||||
import { folders, type Folder, type NewFolder } from '../db/schema';
|
||||
|
||||
export interface FolderFilters {
|
||||
accountId?: string;
|
||||
type?: string;
|
||||
includeHidden?: boolean;
|
||||
}
|
||||
|
||||
// Standard folder types that should be created for each account
|
||||
export const SYSTEM_FOLDERS = [
|
||||
{ type: 'inbox', name: 'Inbox', path: 'INBOX', icon: 'inbox' },
|
||||
{ type: 'sent', name: 'Sent', path: 'Sent', icon: 'send' },
|
||||
{ type: 'drafts', name: 'Drafts', path: 'Drafts', icon: 'file-text' },
|
||||
{ type: 'trash', name: 'Trash', path: 'Trash', icon: 'trash' },
|
||||
{ type: 'spam', name: 'Spam', path: 'Spam', icon: 'alert-triangle' },
|
||||
{ type: 'archive', name: 'Archive', path: 'Archive', icon: 'archive' },
|
||||
];
|
||||
|
||||
@Injectable()
|
||||
export class FolderService {
|
||||
constructor(@Inject(DATABASE_CONNECTION) private db: Database) {}
|
||||
|
||||
async findByUserId(userId: string, filters: FolderFilters = {}): Promise<Folder[]> {
|
||||
const { accountId, type, includeHidden = false } = filters;
|
||||
|
||||
let conditions = [eq(folders.userId, userId)];
|
||||
|
||||
if (accountId) {
|
||||
conditions.push(eq(folders.accountId, accountId));
|
||||
}
|
||||
|
||||
if (type) {
|
||||
conditions.push(eq(folders.type, type));
|
||||
}
|
||||
|
||||
if (!includeHidden) {
|
||||
conditions.push(eq(folders.isHidden, false));
|
||||
}
|
||||
|
||||
return this.db
|
||||
.select()
|
||||
.from(folders)
|
||||
.where(and(...conditions))
|
||||
.orderBy(desc(folders.isSystem), folders.name);
|
||||
}
|
||||
|
||||
async findById(id: string, userId: string): Promise<Folder | null> {
|
||||
const [folder] = await this.db
|
||||
.select()
|
||||
.from(folders)
|
||||
.where(and(eq(folders.id, id), eq(folders.userId, userId)));
|
||||
return folder || null;
|
||||
}
|
||||
|
||||
async findByAccountId(accountId: string, userId: string): Promise<Folder[]> {
|
||||
return this.db
|
||||
.select()
|
||||
.from(folders)
|
||||
.where(and(eq(folders.accountId, accountId), eq(folders.userId, userId)))
|
||||
.orderBy(desc(folders.isSystem), folders.name);
|
||||
}
|
||||
|
||||
async findByType(accountId: string, userId: string, type: string): Promise<Folder | null> {
|
||||
const [folder] = await this.db
|
||||
.select()
|
||||
.from(folders)
|
||||
.where(
|
||||
and(eq(folders.accountId, accountId), eq(folders.userId, userId), eq(folders.type, type))
|
||||
);
|
||||
return folder || null;
|
||||
}
|
||||
|
||||
async create(data: NewFolder): Promise<Folder> {
|
||||
const [folder] = await this.db.insert(folders).values(data).returning();
|
||||
return folder;
|
||||
}
|
||||
|
||||
async update(id: string, userId: string, data: Partial<NewFolder>): Promise<Folder> {
|
||||
const [folder] = await this.db
|
||||
.update(folders)
|
||||
.set({ ...data, updatedAt: new Date() })
|
||||
.where(and(eq(folders.id, id), eq(folders.userId, userId)))
|
||||
.returning();
|
||||
|
||||
if (!folder) {
|
||||
throw new NotFoundException('Folder not found');
|
||||
}
|
||||
|
||||
return folder;
|
||||
}
|
||||
|
||||
async delete(id: string, userId: string): Promise<void> {
|
||||
const folder = await this.findById(id, userId);
|
||||
if (!folder) {
|
||||
throw new NotFoundException('Folder not found');
|
||||
}
|
||||
|
||||
// Prevent deletion of system folders
|
||||
if (folder.isSystem) {
|
||||
throw new NotFoundException('Cannot delete system folder');
|
||||
}
|
||||
|
||||
await this.db.delete(folders).where(and(eq(folders.id, id), eq(folders.userId, userId)));
|
||||
}
|
||||
|
||||
// Create system folders for a new account
|
||||
async createSystemFolders(accountId: string, userId: string): Promise<Folder[]> {
|
||||
const createdFolders: Folder[] = [];
|
||||
|
||||
for (const systemFolder of SYSTEM_FOLDERS) {
|
||||
const folder = await this.create({
|
||||
accountId,
|
||||
userId,
|
||||
name: systemFolder.name,
|
||||
type: systemFolder.type,
|
||||
path: systemFolder.path,
|
||||
icon: systemFolder.icon,
|
||||
isSystem: true,
|
||||
isHidden: false,
|
||||
});
|
||||
createdFolders.push(folder);
|
||||
}
|
||||
|
||||
return createdFolders;
|
||||
}
|
||||
|
||||
// Update folder counts
|
||||
async updateCounts(id: string, totalCount: number, unreadCount: number): Promise<void> {
|
||||
await this.db
|
||||
.update(folders)
|
||||
.set({ totalCount, unreadCount, updatedAt: new Date() })
|
||||
.where(eq(folders.id, id));
|
||||
}
|
||||
|
||||
// Increment/decrement counts
|
||||
async incrementUnreadCount(id: string, delta: number): Promise<void> {
|
||||
await this.db
|
||||
.update(folders)
|
||||
.set({
|
||||
unreadCount: sql`GREATEST(0, ${folders.unreadCount} + ${delta})`,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(eq(folders.id, id));
|
||||
}
|
||||
|
||||
async incrementTotalCount(id: string, delta: number): Promise<void> {
|
||||
await this.db
|
||||
.update(folders)
|
||||
.set({
|
||||
totalCount: sql`GREATEST(0, ${folders.totalCount} + ${delta})`,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(eq(folders.id, id));
|
||||
}
|
||||
|
||||
// Sync folders from external provider
|
||||
async syncFromProvider(
|
||||
accountId: string,
|
||||
userId: string,
|
||||
providerFolders: Array<{ name: string; path: string; type?: string; externalId?: string }>
|
||||
): Promise<Folder[]> {
|
||||
const existingFolders = await this.findByAccountId(accountId, userId);
|
||||
const existingPaths = new Set(existingFolders.map((f) => f.path));
|
||||
const newFolders: Folder[] = [];
|
||||
|
||||
for (const pf of providerFolders) {
|
||||
if (!existingPaths.has(pf.path)) {
|
||||
// Determine folder type
|
||||
let type = pf.type || 'custom';
|
||||
const lowerPath = pf.path.toLowerCase();
|
||||
|
||||
if (!pf.type) {
|
||||
if (lowerPath === 'inbox') type = 'inbox';
|
||||
else if (lowerPath.includes('sent')) type = 'sent';
|
||||
else if (lowerPath.includes('draft')) type = 'drafts';
|
||||
else if (lowerPath.includes('trash') || lowerPath.includes('deleted')) type = 'trash';
|
||||
else if (lowerPath.includes('spam') || lowerPath.includes('junk')) type = 'spam';
|
||||
else if (lowerPath.includes('archive')) type = 'archive';
|
||||
}
|
||||
|
||||
const folder = await this.create({
|
||||
accountId,
|
||||
userId,
|
||||
name: pf.name,
|
||||
path: pf.path,
|
||||
type,
|
||||
externalId: pf.externalId,
|
||||
isSystem: ['inbox', 'sent', 'drafts', 'trash', 'spam', 'archive'].includes(type),
|
||||
});
|
||||
newFolders.push(folder);
|
||||
}
|
||||
}
|
||||
|
||||
return newFolders;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue