mirror of
https://github.com/zen-browser/rices.git
synced 2025-07-07 08:55:40 +02:00
shared refactor
This commit is contained in:
parent
49b7ba2c78
commit
079cde591e
16 changed files with 6592 additions and 90 deletions
|
@ -66,5 +66,11 @@
|
||||||
"tsconfig-paths": "^4.2.0",
|
"tsconfig-paths": "^4.2.0",
|
||||||
"typescript": "^5.1.3",
|
"typescript": "^5.1.3",
|
||||||
"xss": "^1.0.15"
|
"xss": "^1.0.15"
|
||||||
|
},
|
||||||
|
"pnpm": {
|
||||||
|
"onlyBuiltDependencies": [
|
||||||
|
"@nestjs/core",
|
||||||
|
"@scarf/scarf"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
6495
pnpm-lock.yaml
generated
Normal file
6495
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,8 +1,9 @@
|
||||||
--DROP TABLE IF EXISTS rices;
|
--DROP TABLE IF EXISTS rices;
|
||||||
|
|
||||||
CREATE TABLE rices (
|
CREATE TABLE shared (
|
||||||
id UUID NOT NULL, -- Unique identifier
|
id UUID NOT NULL, -- Unique identifier
|
||||||
slug VARCHAR(75) NOT NULL, -- Unique user-friendly identifier
|
slug VARCHAR(75) NOT NULL, -- Unique user-friendly identifier
|
||||||
|
type INTEGER DEFAULT 0 NOT NULL, -- Type: 1-WORKSPACE 2-RICE
|
||||||
version VARCHAR(10) NOT NULL, -- Data version
|
version VARCHAR(10) NOT NULL, -- Data version
|
||||||
os VARCHAR(30) NOT NULL, -- Operating system
|
os VARCHAR(30) NOT NULL, -- Operating system
|
||||||
name VARCHAR(75) NOT NULL, -- Name of the rice
|
name VARCHAR(75) NOT NULL, -- Name of the rice
|
||||||
|
@ -19,7 +20,7 @@ CREATE TABLE rices (
|
||||||
CREATE OR REPLACE FUNCTION increment_visits(slug_param TEXT)
|
CREATE OR REPLACE FUNCTION increment_visits(slug_param TEXT)
|
||||||
RETURNS VOID AS $$
|
RETURNS VOID AS $$
|
||||||
BEGIN
|
BEGIN
|
||||||
UPDATE rices
|
UPDATE shared
|
||||||
SET visits = visits + 1
|
SET visits = visits + 1
|
||||||
WHERE slug = slug_param;
|
WHERE slug = slug_param;
|
||||||
END;
|
END;
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { ConfigModule } from '@nestjs/config';
|
||||||
import { ThrottlerModule, ThrottlerGuard } from '@nestjs/throttler';
|
import { ThrottlerModule, ThrottlerGuard } from '@nestjs/throttler';
|
||||||
import { APP_GUARD } from '@nestjs/core';
|
import { APP_GUARD } from '@nestjs/core';
|
||||||
import { GitHubModule } from './github/github.module';
|
import { GitHubModule } from './github/github.module';
|
||||||
import { RicesModule } from './rices/rices.module';
|
import { SharedModule } from './shared/shared.module';
|
||||||
import { ThrottlerExceptionFilter } from './common/filters/throttler-exception.filter';
|
import { ThrottlerExceptionFilter } from './common/filters/throttler-exception.filter';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
|
@ -29,7 +29,7 @@ import { ThrottlerExceptionFilter } from './common/filters/throttler-exception.f
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
GitHubModule,
|
GitHubModule,
|
||||||
RicesModule,
|
SharedModule,
|
||||||
],
|
],
|
||||||
controllers: [],
|
controllers: [],
|
||||||
providers: [
|
providers: [
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { IsString } from 'class-validator';
|
import { IsString } from 'class-validator';
|
||||||
|
|
||||||
export class CreateRiceDto {
|
export class CreateSharedDto {
|
||||||
@IsString()
|
@IsString()
|
||||||
name!: string;
|
name!: string;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { IsString } from 'class-validator';
|
import { IsString } from 'class-validator';
|
||||||
|
|
||||||
export class UpdateRiceDto {
|
export class UpdateSharedDto {
|
||||||
@IsString()
|
@IsString()
|
||||||
content!: string;
|
content!: string;
|
||||||
}
|
}
|
|
@ -14,24 +14,24 @@ import {
|
||||||
Res,
|
Res,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { Response } from 'express';
|
import { Response } from 'express';
|
||||||
import { RicesService } from './rices.service';
|
import { SharedService } from './shared.service';
|
||||||
import { ApiTags, ApiOperation, ApiResponse, ApiHeader } from '@nestjs/swagger';
|
import { ApiTags, ApiOperation, ApiResponse, ApiHeader } from '@nestjs/swagger';
|
||||||
|
|
||||||
@ApiTags('rices')
|
@ApiTags('rices')
|
||||||
@Controller('rices')
|
@Controller('rices')
|
||||||
export class RicesController {
|
export class RicesController {
|
||||||
constructor(private readonly ricesService: RicesService) {}
|
constructor(private readonly sharedService: SharedService) {}
|
||||||
|
|
||||||
@ApiOperation({ summary: 'Upload a new Rice' })
|
@ApiOperation({ summary: 'Upload a new Rice' })
|
||||||
@ApiResponse({ status: 201, description: 'Rice successfully created.' })
|
@ApiResponse({ status: 201, description: 'Rice successfully created.' })
|
||||||
@ApiHeader({
|
@ApiHeader({
|
||||||
name: 'X-Zen-Rice-Name',
|
name: 'x-zen-shared-name',
|
||||||
description: 'Name of the rice',
|
description: 'Name of the Shared',
|
||||||
required: true,
|
required: true,
|
||||||
})
|
})
|
||||||
@ApiHeader({
|
@ApiHeader({
|
||||||
name: 'X-Zen-Rice-Author',
|
name: 'x-zen-shared-author',
|
||||||
description: 'Author of the rice',
|
description: 'Author of the Shared',
|
||||||
required: true,
|
required: true,
|
||||||
})
|
})
|
||||||
@ApiHeader({
|
@ApiHeader({
|
||||||
|
@ -43,14 +43,14 @@ export class RicesController {
|
||||||
async createRice(
|
async createRice(
|
||||||
@Body() content: string,
|
@Body() content: string,
|
||||||
@Headers() headers: Record<string, string>,
|
@Headers() headers: Record<string, string>,
|
||||||
@Headers('x-zen-rices-token') token: string,
|
@Headers('x-zen-shared-token') token: string,
|
||||||
) {
|
) {
|
||||||
const contentString =
|
const contentString =
|
||||||
typeof content === 'string' ? content : JSON.stringify(content);
|
typeof content === 'string' ? content : JSON.stringify(content);
|
||||||
|
|
||||||
this.validateFileSize(contentString); // Validate file size
|
this.validateFileSize(contentString); // Validate file size
|
||||||
|
|
||||||
return this.ricesService.create(contentString, token, headers);
|
return this.sharedService.create(contentString, token, headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiOperation({ summary: 'Get information about a Rice' })
|
@ApiOperation({ summary: 'Get information about a Rice' })
|
||||||
|
@ -60,7 +60,7 @@ export class RicesController {
|
||||||
})
|
})
|
||||||
@Get(':slug')
|
@Get(':slug')
|
||||||
async getRice(@Param('slug') slug: string, @Res() res: Response) {
|
async getRice(@Param('slug') slug: string, @Res() res: Response) {
|
||||||
const riceMetadata = await this.ricesService.getRiceMetadata(slug);
|
const riceMetadata = await this.sharedService.getRiceMetadata(slug);
|
||||||
|
|
||||||
const htmlContent = `<!DOCTYPE html>
|
const htmlContent = `<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
@ -94,13 +94,13 @@ export class RicesController {
|
||||||
@ApiOperation({ summary: 'Update an existing Rice' })
|
@ApiOperation({ summary: 'Update an existing Rice' })
|
||||||
@ApiResponse({ status: 200, description: 'Rice successfully updated.' })
|
@ApiResponse({ status: 200, description: 'Rice successfully updated.' })
|
||||||
@ApiHeader({
|
@ApiHeader({
|
||||||
name: 'X-Zen-Rice-Name',
|
name: 'x-zen-shared-name',
|
||||||
description: 'Name of the rice',
|
description: 'Name of the shared',
|
||||||
required: true,
|
required: true,
|
||||||
})
|
})
|
||||||
@ApiHeader({
|
@ApiHeader({
|
||||||
name: 'X-Zen-Rice-Author',
|
name: 'x-zen-shared-author',
|
||||||
description: 'Author of the rice',
|
description: 'Author of the shared',
|
||||||
required: true,
|
required: true,
|
||||||
})
|
})
|
||||||
@ApiHeader({
|
@ApiHeader({
|
||||||
|
@ -113,14 +113,14 @@ export class RicesController {
|
||||||
@Param('slug') slug: string,
|
@Param('slug') slug: string,
|
||||||
@Body() content: string,
|
@Body() content: string,
|
||||||
@Headers() headers: Record<string, string>,
|
@Headers() headers: Record<string, string>,
|
||||||
@Headers('x-zen-rices-token') token: string,
|
@Headers('x-zen-shared-token') token: string,
|
||||||
) {
|
) {
|
||||||
const contentString =
|
const contentString =
|
||||||
typeof content === 'string' ? content : JSON.stringify(content);
|
typeof content === 'string' ? content : JSON.stringify(content);
|
||||||
|
|
||||||
this.validateFileSize(contentString); // Validate file size
|
this.validateFileSize(contentString); // Validate file size
|
||||||
|
|
||||||
return this.ricesService.update(slug, token, contentString, headers);
|
return this.sharedService.update(slug, token, contentString, headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiOperation({ summary: 'Delete an existing Rice' })
|
@ApiOperation({ summary: 'Delete an existing Rice' })
|
||||||
|
@ -129,9 +129,9 @@ export class RicesController {
|
||||||
@Delete(':slug')
|
@Delete(':slug')
|
||||||
async removeRice(
|
async removeRice(
|
||||||
@Param('slug') slug: string,
|
@Param('slug') slug: string,
|
||||||
@Headers('x-zen-rices-token') token: string,
|
@Headers('x-zen-shared-token') token: string,
|
||||||
) {
|
) {
|
||||||
await this.ricesService.remove(slug, token);
|
await this.sharedService.remove(slug, token);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,7 +150,7 @@ export class RicesController {
|
||||||
if (moderationSecret !== process.env.MODERATION_SECRET) {
|
if (moderationSecret !== process.env.MODERATION_SECRET) {
|
||||||
throw new UnauthorizedException('Invalid moderation secret');
|
throw new UnauthorizedException('Invalid moderation secret');
|
||||||
}
|
}
|
||||||
await this.ricesService.moderateRemove(slug);
|
await this.sharedService.moderateRemove(slug);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
// src/rices/rices.module.ts
|
// src/rices/rices.module.ts
|
||||||
|
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { RicesService } from './rices.service';
|
import { SharedService } from './shared.service';
|
||||||
import { GitHubModule } from '../github/github.module';
|
import { GitHubModule } from '../github/github.module';
|
||||||
import { RicesController } from './rices.controller';
|
import { RicesController } from './rices.controller';
|
||||||
import { SupabaseService } from '../supabase/supabase.service';
|
import { SupabaseService } from '../supabase/supabase.service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [GitHubModule],
|
imports: [GitHubModule],
|
||||||
providers: [RicesService, SupabaseService],
|
providers: [SharedService, SupabaseService],
|
||||||
controllers: [RicesController],
|
controllers: [RicesController],
|
||||||
exports: [RicesService],
|
exports: [SharedService],
|
||||||
})
|
})
|
||||||
export class RicesModule {}
|
export class SharedModule {}
|
|
@ -17,7 +17,7 @@ import { SupabaseService } from '../supabase/supabase.service';
|
||||||
const userAgentRegex = /ZenBrowser\/(\d+\.\d\w?\.\d) \((.+)\)/;
|
const userAgentRegex = /ZenBrowser\/(\d+\.\d\w?\.\d) \((.+)\)/;
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RicesService {
|
export class SharedService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly gitHubService: GitHubService,
|
private readonly gitHubService: GitHubService,
|
||||||
private readonly supabaseService: SupabaseService,
|
private readonly supabaseService: SupabaseService,
|
||||||
|
@ -31,8 +31,8 @@ export class RicesService {
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
// Validate headers
|
// Validate headers
|
||||||
const name = headers['x-zen-rice-name'];
|
const name = headers['x-zen-shared-name'];
|
||||||
const author = headers['x-zen-rice-author'];
|
const author = headers['x-zen-shared-author'];
|
||||||
const userAgent = headers['user-agent'];
|
const userAgent = headers['user-agent'];
|
||||||
|
|
||||||
if (!name || !author || !userAgent) {
|
if (!name || !author || !userAgent) {
|
||||||
|
@ -58,13 +58,13 @@ export class RicesService {
|
||||||
// Validate lengths
|
// Validate lengths
|
||||||
if (name.length > 75) {
|
if (name.length > 75) {
|
||||||
throw new BadRequestException(
|
throw new BadRequestException(
|
||||||
`The value of X-Zen-Rice-Name exceeds the maximum allowed length of 75 characters.`,
|
`The value of x-zen-shared-name exceeds the maximum allowed length of 75 characters.`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (author.length > 100) {
|
if (author.length > 100) {
|
||||||
throw new BadRequestException(
|
throw new BadRequestException(
|
||||||
`The value of X-Zen-Rice-Author exceeds the maximum allowed length of 100 characters.`,
|
`The value of x-zen-shared-author exceeds the maximum allowed length of 100 characters.`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,7 +113,7 @@ export class RicesService {
|
||||||
'MAX_RICES_BY_TOKEN',
|
'MAX_RICES_BY_TOKEN',
|
||||||
5,
|
5,
|
||||||
);
|
);
|
||||||
const tokenCount = await this.supabaseService.countRicesByToken(token);
|
const tokenCount = await this.supabaseService.countSharedByToken(token);
|
||||||
if (tokenCount >= tokenMaxCount) {
|
if (tokenCount >= tokenMaxCount) {
|
||||||
throw new ConflictException(
|
throw new ConflictException(
|
||||||
`The token '${token}' is already associated with 5 or more rices.`,
|
`The token '${token}' is already associated with 5 or more rices.`,
|
||||||
|
@ -135,7 +135,7 @@ export class RicesService {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Insert metadata into Supabase
|
// Insert metadata into Supabase
|
||||||
await this.supabaseService.insertRice(metadata);
|
await this.supabaseService.insertShared(metadata);
|
||||||
|
|
||||||
const uploadedFilePath = `rices/${slug}/data.zenrice`;
|
const uploadedFilePath = `rices/${slug}/data.zenrice`;
|
||||||
await this.gitHubService.createOrUpdateFile(
|
await this.gitHubService.createOrUpdateFile(
|
||||||
|
@ -153,7 +153,7 @@ export class RicesService {
|
||||||
|
|
||||||
async findOne(slug: string) {
|
async findOne(slug: string) {
|
||||||
// Check if the rice exists in the database
|
// Check if the rice exists in the database
|
||||||
const rice = await this.supabaseService.getRiceBySlug(slug);
|
const rice = await this.supabaseService.getSharedBySlug(slug);
|
||||||
if (!rice) throw new NotFoundException('Rice not found');
|
if (!rice) throw new NotFoundException('Rice not found');
|
||||||
|
|
||||||
// Fetch the file from GitHub
|
// Fetch the file from GitHub
|
||||||
|
@ -171,7 +171,7 @@ export class RicesService {
|
||||||
}
|
}
|
||||||
|
|
||||||
async getRiceMetadata(slug: string) {
|
async getRiceMetadata(slug: string) {
|
||||||
const rice = await this.supabaseService.getRiceBySlug(slug);
|
const rice = await this.supabaseService.getSharedBySlug(slug);
|
||||||
if (!rice) throw new NotFoundException('Rice not found');
|
if (!rice) throw new NotFoundException('Rice not found');
|
||||||
|
|
||||||
return rice;
|
return rice;
|
||||||
|
@ -216,7 +216,7 @@ export class RicesService {
|
||||||
const [, version, os] = match;
|
const [, version, os] = match;
|
||||||
|
|
||||||
// Check if the rice exists
|
// Check if the rice exists
|
||||||
const rice = await this.supabaseService.getRiceBySlug(slug);
|
const rice = await this.supabaseService.getSharedBySlug(slug);
|
||||||
if (!rice) {
|
if (!rice) {
|
||||||
throw new NotFoundException('Rice not found');
|
throw new NotFoundException('Rice not found');
|
||||||
}
|
}
|
||||||
|
@ -233,7 +233,7 @@ export class RicesService {
|
||||||
updated_at: new Date().toISOString(),
|
updated_at: new Date().toISOString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.supabaseService.updateRice(slug, updatedMetadata);
|
await this.supabaseService.updateShared(slug, updatedMetadata);
|
||||||
|
|
||||||
const uploadedFilePath = `rices/${slug}/data.zenrice`;
|
const uploadedFilePath = `rices/${slug}/data.zenrice`;
|
||||||
await this.gitHubService.createOrUpdateFile(
|
await this.gitHubService.createOrUpdateFile(
|
||||||
|
@ -250,7 +250,7 @@ export class RicesService {
|
||||||
}
|
}
|
||||||
|
|
||||||
async remove(slug: string, token: string): Promise<void> {
|
async remove(slug: string, token: string): Promise<void> {
|
||||||
const rice = await this.supabaseService.getRiceBySlug(slug);
|
const rice = await this.supabaseService.getSharedBySlug(slug);
|
||||||
if (!rice) throw new NotFoundException('Rice not found');
|
if (!rice) throw new NotFoundException('Rice not found');
|
||||||
if (rice.token !== token) throw new UnauthorizedException('Invalid token');
|
if (rice.token !== token) throw new UnauthorizedException('Invalid token');
|
||||||
|
|
||||||
|
@ -259,7 +259,7 @@ export class RicesService {
|
||||||
throw new UnauthorizedException('Invalid token.');
|
throw new UnauthorizedException('Invalid token.');
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.supabaseService.deleteRice(slug);
|
await this.supabaseService.deleteShared(slug);
|
||||||
|
|
||||||
const folderPath = `rices/${slug}`;
|
const folderPath = `rices/${slug}`;
|
||||||
|
|
||||||
|
@ -289,13 +289,13 @@ export class RicesService {
|
||||||
public async moderateRemove(slug: string): Promise<void> {
|
public async moderateRemove(slug: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
// 1. Check if rice exists in Supabase
|
// 1. Check if rice exists in Supabase
|
||||||
const rice = await this.supabaseService.getRiceBySlug(slug);
|
const rice = await this.supabaseService.getSharedBySlug(slug);
|
||||||
if (!rice) {
|
if (!rice) {
|
||||||
throw new NotFoundException('Rice not found');
|
throw new NotFoundException('Rice not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Delete metadata from Supabase
|
// 2. Delete metadata from Supabase
|
||||||
await this.supabaseService.deleteRice(slug);
|
await this.supabaseService.deleteShared(slug);
|
||||||
|
|
||||||
// 3. Delete data.zenrice from GitHub
|
// 3. Delete data.zenrice from GitHub
|
||||||
const riceJsonPath = `rices/${slug}/data.zenrice`;
|
const riceJsonPath = `rices/${slug}/data.zenrice`;
|
|
@ -17,42 +17,42 @@ export class SupabaseService {
|
||||||
this.supabase = createClient(this.supabase_url, this.supabase_key);
|
this.supabase = createClient(this.supabase_url, this.supabase_key);
|
||||||
}
|
}
|
||||||
|
|
||||||
async insertRice(metadata: any) {
|
async insertShared(metadata: any) {
|
||||||
const { error } = await this.supabase.from('rices').insert(metadata);
|
const { error } = await this.supabase.from('shared').insert(metadata);
|
||||||
if (error) {
|
if (error) {
|
||||||
this.logger.error(
|
this.logger.error(
|
||||||
`Failed to insert rice: ${error.message}`,
|
`Failed to insert shared: ${error.message}`,
|
||||||
error.details,
|
error.details,
|
||||||
);
|
);
|
||||||
throw new Error(`Failed to insert rice: ${error.message}`);
|
throw new Error(`Failed to insert shared: ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getRiceById(id: string) {
|
async getSharedById(id: string) {
|
||||||
const { data, error } = await this.supabase
|
const { data, error } = await this.supabase
|
||||||
.from('rices')
|
.from('shared')
|
||||||
.select('*')
|
.select('*')
|
||||||
.eq('id', id)
|
.eq('id', id)
|
||||||
.single();
|
.single();
|
||||||
if (error) {
|
if (error) {
|
||||||
this.logger.error(
|
this.logger.error(
|
||||||
`Failed to fetch rice with ID ${id}: ${error.message}`,
|
`Failed to fetch shared with ID ${id}: ${error.message}`,
|
||||||
error.details,
|
error.details,
|
||||||
);
|
);
|
||||||
throw new Error(`Failed to fetch rice: ${error.message}`);
|
throw new Error(`Failed to fetch shared: ${error.message}`);
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getRiceBySlug(slug: string) {
|
async getSharedBySlug(slug: string) {
|
||||||
const { data, error } = await this.supabase
|
const { data, error } = await this.supabase
|
||||||
.from('rices')
|
.from('shared')
|
||||||
.select('*')
|
.select('*')
|
||||||
.eq('slug', slug)
|
.eq('slug', slug)
|
||||||
.single();
|
.single();
|
||||||
if (error) {
|
if (error) {
|
||||||
this.logger.error(
|
this.logger.error(
|
||||||
`Failed to fetch rice with slug ${slug}: ${error.message}`,
|
`Failed to fetch shared with slug ${slug}: ${error.message}`,
|
||||||
error.details,
|
error.details,
|
||||||
);
|
);
|
||||||
return null;
|
return null;
|
||||||
|
@ -60,48 +60,48 @@ export class SupabaseService {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getRiceByName(name: string) {
|
async getSharedByName(name: string) {
|
||||||
const { data, error } = await this.supabase
|
const { data, error } = await this.supabase
|
||||||
.from('rices')
|
.from('shared')
|
||||||
.select('*')
|
.select('*')
|
||||||
.eq('name', name)
|
.eq('name', name)
|
||||||
.single();
|
.single();
|
||||||
if (error && error.code !== 'PGRST116') {
|
if (error && error.code !== 'PGRST116') {
|
||||||
// Handle "no rows found" separately
|
// Handle "no rows found" separately
|
||||||
this.logger.error(
|
this.logger.error(
|
||||||
`Failed to fetch rice with name ${name}: ${error.message}`,
|
`Failed to fetch shared with name ${name}: ${error.message}`,
|
||||||
error.details,
|
error.details,
|
||||||
);
|
);
|
||||||
throw new Error(`Failed to fetch rice: ${error.message}`);
|
throw new Error(`Failed to fetch shared: ${error.message}`);
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateRice(slug: string, metadata: any) {
|
async updateShared(slug: string, metadata: any) {
|
||||||
const { error } = await this.supabase
|
const { error } = await this.supabase
|
||||||
.from('rices')
|
.from('shared')
|
||||||
.update(metadata)
|
.update(metadata)
|
||||||
.eq('slug', slug);
|
.eq('slug', slug);
|
||||||
if (error) {
|
if (error) {
|
||||||
this.logger.error(
|
this.logger.error(
|
||||||
`Failed to update rice with slug ${slug}: ${error.message}`,
|
`Failed to update shared with slug ${slug}: ${error.message}`,
|
||||||
error.details,
|
error.details,
|
||||||
);
|
);
|
||||||
throw new Error(`Failed to update rice: ${error.message}`);
|
throw new Error(`Failed to update shared: ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteRice(slug: string) {
|
async deleteShared(slug: string) {
|
||||||
const { error } = await this.supabase
|
const { error } = await this.supabase
|
||||||
.from('rices')
|
.from('shared')
|
||||||
.delete()
|
.delete()
|
||||||
.eq('slug', slug);
|
.eq('slug', slug);
|
||||||
if (error) {
|
if (error) {
|
||||||
this.logger.error(
|
this.logger.error(
|
||||||
`Failed to delete rice with slug ${slug}: ${error.message}`,
|
`Failed to delete shared with slug ${slug}: ${error.message}`,
|
||||||
error.details,
|
error.details,
|
||||||
);
|
);
|
||||||
throw new Error(`Failed to delete rice: ${error.message}`);
|
throw new Error(`Failed to delete shared: ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,7 +112,7 @@ export class SupabaseService {
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
this.logger.error(
|
this.logger.error(
|
||||||
`Failed to increment visits for rice with slug ${slug}: ${error.message}`,
|
`Failed to increment visits for shared with slug ${slug}: ${error.message}`,
|
||||||
error.details,
|
error.details,
|
||||||
);
|
);
|
||||||
throw new Error(`Failed to increment visits: ${error.message}`);
|
throw new Error(`Failed to increment visits: ${error.message}`);
|
||||||
|
@ -121,27 +121,27 @@ export class SupabaseService {
|
||||||
|
|
||||||
async updateLevel(slug: string, level: number) {
|
async updateLevel(slug: string, level: number) {
|
||||||
const { error } = await this.supabase
|
const { error } = await this.supabase
|
||||||
.from('rices')
|
.from('shared')
|
||||||
.update({ level })
|
.update({ level })
|
||||||
.eq('slug', slug);
|
.eq('slug', slug);
|
||||||
if (error) {
|
if (error) {
|
||||||
this.logger.error(
|
this.logger.error(
|
||||||
`Failed to update level for rice with slug ${slug}: ${error.message}`,
|
`Failed to update level for shared with slug ${slug}: ${error.message}`,
|
||||||
error.details,
|
error.details,
|
||||||
);
|
);
|
||||||
throw new Error(`Failed to update rice level: ${error.message}`);
|
throw new Error(`Failed to update shared level: ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async countRicesByToken(token: string): Promise<number> {
|
async countSharedByToken(token: string): Promise<number> {
|
||||||
const { data, error, count } = await this.supabase
|
const { data, error, count } = await this.supabase
|
||||||
.from('rices') // Nombre de tu tabla en Supabase
|
.from('shared') // Nombre de tu tabla en Supabase
|
||||||
.select('*', { count: 'exact' })
|
.select('*', { count: 'exact' })
|
||||||
.eq('token', token);
|
.eq('token', token);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error('Error counting rices by token:', error);
|
console.error('Error counting shared by token:', error);
|
||||||
throw new Error('Failed to count rices by token');
|
throw new Error('Failed to count shared by token');
|
||||||
}
|
}
|
||||||
|
|
||||||
return count || 0;
|
return count || 0;
|
||||||
|
|
|
@ -48,7 +48,7 @@ describe('Rices API E2E', () => {
|
||||||
expect(slug).toBeDefined();
|
expect(slug).toBeDefined();
|
||||||
expect(token).toBeDefined();
|
expect(token).toBeDefined();
|
||||||
|
|
||||||
const riceInDatabase = await supabaseService.getRiceBySlug(slug);
|
const riceInDatabase = await supabaseService.getSharedBySlug(slug);
|
||||||
expect(riceInDatabase).not.toBeNull();
|
expect(riceInDatabase).not.toBeNull();
|
||||||
expect(riceInDatabase.name).toBe('Test Rice');
|
expect(riceInDatabase.name).toBe('Test Rice');
|
||||||
|
|
||||||
|
@ -67,12 +67,12 @@ describe('Rices API E2E', () => {
|
||||||
|
|
||||||
const { slug } = createResponse.body;
|
const { slug } = createResponse.body;
|
||||||
|
|
||||||
const initialData = await supabaseService.getRiceBySlug(slug);
|
const initialData = await supabaseService.getSharedBySlug(slug);
|
||||||
expect(initialData.visits).toBe(0);
|
expect(initialData.visits).toBe(0);
|
||||||
|
|
||||||
await request(app.getHttpServer()).get(`/rices/${slug}`).expect(200);
|
await request(app.getHttpServer()).get(`/rices/${slug}`).expect(200);
|
||||||
|
|
||||||
const updatedData = await supabaseService.getRiceBySlug(slug);
|
const updatedData = await supabaseService.getSharedBySlug(slug);
|
||||||
expect(updatedData.visits).toBe(1);
|
expect(updatedData.visits).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -87,14 +87,14 @@ describe('Rices API E2E', () => {
|
||||||
|
|
||||||
const updateResponse = await request(app.getHttpServer())
|
const updateResponse = await request(app.getHttpServer())
|
||||||
.put(`/rices/${slug}`)
|
.put(`/rices/${slug}`)
|
||||||
.set('x-zen-rices-token', token)
|
.set('x-zen-shared-token', token)
|
||||||
.field('name', 'Updated Rice')
|
.field('name', 'Updated Rice')
|
||||||
.attach('file', path.join(__dirname, 'files', 'example_update.zenrice'))
|
.attach('file', path.join(__dirname, 'files', 'example_update.zenrice'))
|
||||||
.expect(200);
|
.expect(200);
|
||||||
|
|
||||||
expect(updateResponse.body.message).toBe(`ok`);
|
expect(updateResponse.body.message).toBe(`ok`);
|
||||||
|
|
||||||
const updatedData = await supabaseService.getRiceBySlug(slug);
|
const updatedData = await supabaseService.getSharedBySlug(slug);
|
||||||
expect(updatedData.name).toBe('Updated Rice');
|
expect(updatedData.name).toBe('Updated Rice');
|
||||||
|
|
||||||
const updatedFile = await gitHubService.getFileContent(
|
const updatedFile = await gitHubService.getFileContent(
|
||||||
|
@ -116,10 +116,10 @@ describe('Rices API E2E', () => {
|
||||||
|
|
||||||
await request(app.getHttpServer())
|
await request(app.getHttpServer())
|
||||||
.delete(`/rices/${slug}`)
|
.delete(`/rices/${slug}`)
|
||||||
.set('x-zen-rices-token', token)
|
.set('x-zen-shared-token', token)
|
||||||
.expect(204);
|
.expect(204);
|
||||||
|
|
||||||
const riceInDatabase = await supabaseService.getRiceBySlug(slug);
|
const riceInDatabase = await supabaseService.getSharedBySlug(slug);
|
||||||
expect(riceInDatabase).toBeNull();
|
expect(riceInDatabase).toBeNull();
|
||||||
|
|
||||||
const fileInGitHub = await gitHubService.getFileContent(
|
const fileInGitHub = await gitHubService.getFileContent(
|
||||||
|
@ -142,7 +142,7 @@ describe('Rices API E2E', () => {
|
||||||
.set('x-moderation-secret', moderationSecret)
|
.set('x-moderation-secret', moderationSecret)
|
||||||
.expect(204);
|
.expect(204);
|
||||||
|
|
||||||
const riceInDatabase = await supabaseService.getRiceBySlug(slug);
|
const riceInDatabase = await supabaseService.getSharedBySlug(slug);
|
||||||
expect(riceInDatabase).toBeNull();
|
expect(riceInDatabase).toBeNull();
|
||||||
|
|
||||||
const fileInGitHub = await gitHubService.getFileContent(
|
const fileInGitHub = await gitHubService.getFileContent(
|
||||||
|
@ -165,7 +165,7 @@ describe('Rices API E2E', () => {
|
||||||
.set('x-moderation-secret', 'wrongSecret')
|
.set('x-moderation-secret', 'wrongSecret')
|
||||||
.expect(401);
|
.expect(401);
|
||||||
|
|
||||||
const riceInDatabase = await supabaseService.getRiceBySlug(slug);
|
const riceInDatabase = await supabaseService.getSharedBySlug(slug);
|
||||||
expect(riceInDatabase).not.toBeNull();
|
expect(riceInDatabase).not.toBeNull();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
|
|
||||||
POST {{baseUrl}}/rices
|
POST {{baseUrl}}/rices
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
X-Zen-Rice-Name: cool-zenrice-test-base
|
x-zen-shared-name: cool-zenrice-test-base
|
||||||
X-Zen-Rice-Author: jhon@doe.com
|
x-zen-shared-author: jhon@doe.com
|
||||||
User-Agent: ZenBrowser/1.2b.0 (EndeavourOS x86_64)
|
User-Agent: ZenBrowser/1.2b.0 (EndeavourOS x86_64)
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
|
|
||||||
POST {{baseUrl}}/rices
|
POST {{baseUrl}}/rices
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
X-Zen-Rice-Name: cool-zenrice-test-base5
|
x-zen-shared-name: cool-zenrice-test-base
|
||||||
X-Zen-Rice-Author: jhon@doe.com
|
x-zen-shared-author: jhon@doe.com
|
||||||
x-zen-rices-token: {{previous_token}}
|
x-zen-shared-token: {{previous_token}}
|
||||||
User-Agent: ZenBrowser/1.2b.0 (EndeavourOS x86_64)
|
User-Agent: ZenBrowser/1.2b.0 (EndeavourOS x86_64)
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
PUT {{baseUrl}}/rices/{{previous_slug}}
|
PUT {{baseUrl}}/rices/{{previous_slug}}
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
x-zen-rices-token: {{previous_token}}
|
x-zen-shared-token: {{previous_token}}
|
||||||
User-Agent: ZenBrowser/1.2b.0 (EndeavourOS x86_64)
|
User-Agent: ZenBrowser/1.2b.0 (EndeavourOS x86_64)
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
|
@ -3,4 +3,4 @@
|
||||||
@previous_token = 03fbfdb4-d3a5-4d64-8740-feac7d32e7a8
|
@previous_token = 03fbfdb4-d3a5-4d64-8740-feac7d32e7a8
|
||||||
|
|
||||||
DELETE {{baseUrl}}/rices/{{previous_slug}}
|
DELETE {{baseUrl}}/rices/{{previous_slug}}
|
||||||
x-zen-rices-token: {{previous_token}}
|
x-zen-shared-token: {{previous_token}}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue