mirror of
https://github.com/zen-browser/rices.git
synced 2025-07-07 17:05:40 +02:00
feat(rices): mejorar reglas de publicación de rices
- Permitir publicar un rice con un token existente si no excede el límite. - Configurar límite de 5 rices por token a través de variable de entorno (env.MAX_RICES_BY_TOKEN). - Validar duplicidad de nombres al crear un nuevo rice.
This commit is contained in:
parent
612e27a55c
commit
9bf1fe8e0d
7 changed files with 122 additions and 9 deletions
|
@ -5,4 +5,6 @@ GITHUB_REPO_NAME=rices-store
|
||||||
SUPABASE_URL=https://xxxxxxxxxxxxx.supabase.co
|
SUPABASE_URL=https://xxxxxxxxxxxxx.supabase.co
|
||||||
SUPABASE_KEY=XXXXXXXXXXXXXXXXXXX
|
SUPABASE_KEY=XXXXXXXXXXXXXXXXXXX
|
||||||
|
|
||||||
|
MAX_RICES_BY_TOKEN=5
|
||||||
|
|
||||||
MODERATION_SECRET=superSecret123
|
MODERATION_SECRET=superSecret123
|
||||||
|
|
|
@ -12,9 +12,8 @@ CREATE TABLE rices (
|
||||||
level INTEGER DEFAULT 0 NOT NULL, -- Level: 0 (Public), 1 (Verified)
|
level INTEGER DEFAULT 0 NOT NULL, -- Level: 0 (Public), 1 (Verified)
|
||||||
created_at TIMESTAMP DEFAULT NOW(), -- Creation date
|
created_at TIMESTAMP DEFAULT NOW(), -- Creation date
|
||||||
updated_at TIMESTAMP, -- Last update date
|
updated_at TIMESTAMP, -- Last update date
|
||||||
PRIMARY KEY (id, slug), -- Composite primary key
|
PRIMARY KEY (id), -- Composite primary key
|
||||||
UNIQUE (slug), -- Ensure slug is unique
|
UNIQUE (slug) -- Ensure slug is unique
|
||||||
UNIQUE (name) -- Ensure name is unique
|
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION increment_visits(slug_param TEXT)
|
CREATE OR REPLACE FUNCTION increment_visits(slug_param TEXT)
|
||||||
|
|
|
@ -41,10 +41,11 @@ 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,
|
||||||
) {
|
) {
|
||||||
const contentString =
|
const contentString =
|
||||||
typeof content === 'string' ? content : JSON.stringify(content);
|
typeof content === 'string' ? content : JSON.stringify(content);
|
||||||
return this.ricesService.create(contentString, headers);
|
return this.ricesService.create(contentString, token, headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiOperation({ summary: 'Get information about a Rice' })
|
@ApiOperation({ summary: 'Get information about a Rice' })
|
||||||
|
|
|
@ -7,6 +7,7 @@ import {
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import { generateSlug } from './utils/slug.util';
|
import { generateSlug } from './utils/slug.util';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { GitHubService } from '../github/github.service';
|
import { GitHubService } from '../github/github.service';
|
||||||
import { SupabaseService } from '../supabase/supabase.service';
|
import { SupabaseService } from '../supabase/supabase.service';
|
||||||
|
|
||||||
|
@ -17,9 +18,14 @@ export class RicesService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly gitHubService: GitHubService,
|
private readonly gitHubService: GitHubService,
|
||||||
private readonly supabaseService: SupabaseService,
|
private readonly supabaseService: SupabaseService,
|
||||||
|
private readonly configService: ConfigService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async create(content: string, headers: Record<string, string>) {
|
async create(
|
||||||
|
content: string,
|
||||||
|
token: string | null,
|
||||||
|
headers: Record<string, string>,
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
// Validate headers
|
// Validate headers
|
||||||
const name = headers['x-zen-rice-name'];
|
const name = headers['x-zen-rice-name'];
|
||||||
|
@ -77,12 +83,12 @@ export class RicesService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if a rice with the same name already exists
|
// Check if a rice with the same name already exists
|
||||||
const existingRice = await this.supabaseService.getRiceByName(name);
|
/*const existingRice = await this.supabaseService.getRiceByName(name);
|
||||||
if (existingRice) {
|
if (existingRice) {
|
||||||
throw new ConflictException(
|
throw new ConflictException(
|
||||||
`A rice with the name '${name}' already exists.`,
|
`A rice with the name '${name}' already exists.`,
|
||||||
);
|
);
|
||||||
}
|
}*/
|
||||||
|
|
||||||
let slug: string;
|
let slug: string;
|
||||||
try {
|
try {
|
||||||
|
@ -93,7 +99,21 @@ export class RicesService {
|
||||||
throw new BadRequestException(`Invalid name provided`);
|
throw new BadRequestException(`Invalid name provided`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = uuidv4();
|
if (!token) {
|
||||||
|
token = uuidv4();
|
||||||
|
} else {
|
||||||
|
const tokenMaxCount = this.configService.get<number>(
|
||||||
|
'MAX_RICES_BY_TOKEN',
|
||||||
|
5,
|
||||||
|
);
|
||||||
|
const tokenCount = await this.supabaseService.countRicesByToken(token);
|
||||||
|
if (tokenCount >= tokenMaxCount) {
|
||||||
|
throw new ConflictException(
|
||||||
|
`The token '${token}' is already associated with 5 or more rices.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const metadata = {
|
const metadata = {
|
||||||
id: uuidv4(),
|
id: uuidv4(),
|
||||||
token,
|
token,
|
||||||
|
|
|
@ -132,4 +132,18 @@ export class SupabaseService {
|
||||||
throw new Error(`Failed to update rice level: ${error.message}`);
|
throw new Error(`Failed to update rice level: ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async countRicesByToken(token: string): Promise<number> {
|
||||||
|
const { data, error, count } = await this.supabase
|
||||||
|
.from('rices') // Nombre de tu tabla en Supabase
|
||||||
|
.select('*', { count: 'exact' })
|
||||||
|
.eq('token', token);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.error('Error counting rices by token:', error);
|
||||||
|
throw new Error('Failed to count rices by token');
|
||||||
|
}
|
||||||
|
|
||||||
|
return count || 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
POST {{baseUrl}}/rices
|
POST {{baseUrl}}/rices
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
X-Zen-Rice-Name: cool-zenrice-test-base64211
|
X-Zen-Rice-Name: cool-zenrice-test-base
|
||||||
X-Zen-Rice-Author: jhon@doe.com
|
X-Zen-Rice-Author: jhon@doe.com
|
||||||
User-Agent: ZenBrowser/1.2b.0 (EndeavourOS x86_64)
|
User-Agent: ZenBrowser/1.2b.0 (EndeavourOS x86_64)
|
||||||
|
|
||||||
|
|
77
test/restclient/01b_create_rice_same_token.http
Normal file
77
test/restclient/01b_create_rice_same_token.http
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
@baseUrl = http://localhost:3000
|
||||||
|
|
||||||
|
@previous_token = 32af2ec5-b7f0-4c61-8c9a-e67edb3faad4
|
||||||
|
|
||||||
|
POST {{baseUrl}}/rices
|
||||||
|
Content-Type: application/json
|
||||||
|
X-Zen-Rice-Name: cool-zenrice-test-base5
|
||||||
|
X-Zen-Rice-Author: jhon@doe.com
|
||||||
|
x-zen-rices-token: {{previous_token}}
|
||||||
|
User-Agent: ZenBrowser/1.2b.0 (EndeavourOS x86_64)
|
||||||
|
|
||||||
|
{
|
||||||
|
"userChrome": "",
|
||||||
|
"userContent": null,
|
||||||
|
"enabledMods": [
|
||||||
|
"5bb07b6e-c89f-4f4a-a0ed-e483cc535594"
|
||||||
|
],
|
||||||
|
"preferences": {
|
||||||
|
"theme.custom_menubutton.default": "Firefox",
|
||||||
|
"theme.custom_menubutton.custom": "url(chrome://branding/content/icon32.png)",
|
||||||
|
"zen.view.use-single-toolbar": true,
|
||||||
|
"zen.view.sidebar-expanded": true,
|
||||||
|
"zen.tabs.vertical.right-side": false,
|
||||||
|
"zen.view.experimental-no-window-controls": false,
|
||||||
|
"zen.view.hide-window-controls": true,
|
||||||
|
"browser.uiCustomization.state": "{\"placements\":{\"widget-overflow-fixed-list\":[],\"unified-extensions-area\":[\"ublock0_raymondhill_net-browser-action\",\"addon_darkreader_org-browser-action\",\"_7a7a4a92-a2a0-41d1-9fd7-1e92480d612d_-browser-action\",\"cookieautodelete_kennydo_com-browser-action\",\"tab-unloader-we_afnankhan-browser-action\"],\"nav-bar\":[\"back-button\",\"forward-button\",\"stop-reload-button\",\"customizableui-special-spring1\",\"urlbar-container\",\"customizableui-special-spring2\",\"wrapper-sidebar-button\",\"unified-extensions-button\"],\"toolbar-menubar\":[\"menubar-items\"],\"TabsToolbar\":[\"tabbrowser-tabs\"],\"vertical-tabs\":[],\"PersonalToolbar\":[\"personal-bookmarks\"],\"zen-sidebar-top-buttons\":[\"zen-sidepanel-button\"],\"zen-sidebar-icons-wrapper\":[\"zen-profile-button\",\"zen-workspaces-button\",\"downloads-button\"]},\"seen\":[\"_7a7a4a92-a2a0-41d1-9fd7-1e92480d612d_-browser-action\",\"developer-button\",\"cookieautodelete_kennydo_com-browser-action\",\"tab-unloader-we_afnankhan-browser-action\",\"addon_darkreader_org-browser-action\",\"ublock0_raymondhill_net-browser-action\"],\"dirtyAreaCache\":[\"unified-extensions-area\",\"nav-bar\",\"toolbar-menubar\",\"TabsToolbar\",\"vertical-tabs\",\"PersonalToolbar\",\"zen-sidebar-top-buttons\",\"zen-sidebar-icons-wrapper\"],\"currentVersion\":20,\"newElementCount\":2}"
|
||||||
|
},
|
||||||
|
"workspaceThemes": [
|
||||||
|
{
|
||||||
|
"type": "gradient",
|
||||||
|
"gradientColors": [
|
||||||
|
{
|
||||||
|
"c": [
|
||||||
|
124,
|
||||||
|
133,
|
||||||
|
255
|
||||||
|
],
|
||||||
|
"isCustom": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"c": [
|
||||||
|
69,
|
||||||
|
255,
|
||||||
|
86
|
||||||
|
],
|
||||||
|
"isCustom": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"opacity": 0.5,
|
||||||
|
"rotation": 45,
|
||||||
|
"texture": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "gradient",
|
||||||
|
"gradientColors": [
|
||||||
|
{
|
||||||
|
"c": [
|
||||||
|
255,
|
||||||
|
133,
|
||||||
|
65
|
||||||
|
],
|
||||||
|
"isCustom": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"opacity": 0.6,
|
||||||
|
"rotation": 45,
|
||||||
|
"texture": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "gradient",
|
||||||
|
"gradientColors": [],
|
||||||
|
"opacity": 0.5,
|
||||||
|
"rotation": 45,
|
||||||
|
"texture": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue