mirror of
https://github.com/zen-browser/rices.git
synced 2025-07-07 17:05:40 +02:00
- Changed create and update method. Headers+body instead of DTO
- Added token validation for `update` and `remove` operations to ensure it matches the record in the database. - Ensured `name` and `author` from headers in `update` are validated against the existing record. - Improved error handling for mismatched or missing tokens, returning appropriate HTTP status codes.
This commit is contained in:
parent
c8ce6e7637
commit
121ccadf57
8 changed files with 328 additions and 151 deletions
|
@ -2,10 +2,11 @@
|
||||||
|
|
||||||
CREATE TABLE rices (
|
CREATE TABLE rices (
|
||||||
id UUID NOT NULL, -- Unique identifier
|
id UUID NOT NULL, -- Unique identifier
|
||||||
|
slug VARCHAR(75) NOT NULL, -- Unique user-friendly identifier
|
||||||
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
|
||||||
slug VARCHAR(75) NOT NULL, -- Unique user-friendly identifier
|
|
||||||
name VARCHAR(75) NOT NULL, -- Name of the rice
|
name VARCHAR(75) NOT NULL, -- Name of the rice
|
||||||
|
author VARCHAR(100) NOT NULL, -- Name of the rice
|
||||||
token UUID NOT NULL, -- Unique authorization token
|
token UUID NOT NULL, -- Unique authorization token
|
||||||
visits INTEGER DEFAULT 0 NOT NULL, -- Visit counter, initialized to 0
|
visits INTEGER DEFAULT 0 NOT NULL, -- Visit counter, initialized to 0
|
||||||
level INTEGER DEFAULT 0 NOT NULL, -- Level: 0 (Public), 1 (Verified)
|
level INTEGER DEFAULT 0 NOT NULL, -- Level: 0 (Public), 1 (Verified)
|
||||||
|
|
|
@ -6,24 +6,14 @@ import {
|
||||||
Delete,
|
Delete,
|
||||||
Param,
|
Param,
|
||||||
Body,
|
Body,
|
||||||
UseInterceptors,
|
|
||||||
Headers,
|
Headers,
|
||||||
HttpCode,
|
HttpCode,
|
||||||
HttpStatus,
|
HttpStatus,
|
||||||
UnauthorizedException,
|
UnauthorizedException,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { FileInterceptor } from '@nestjs/platform-express';
|
|
||||||
import { RicesService } from './rices.service';
|
import { RicesService } from './rices.service';
|
||||||
import { CreateRiceDto } from './dto/create-rice.dto';
|
|
||||||
import { UpdateRiceDto } from './dto/update-rice.dto';
|
|
||||||
|
|
||||||
import {
|
import { ApiTags, ApiOperation, ApiResponse, ApiHeader } from '@nestjs/swagger';
|
||||||
ApiTags,
|
|
||||||
ApiOperation,
|
|
||||||
ApiResponse,
|
|
||||||
ApiConsumes,
|
|
||||||
ApiBody,
|
|
||||||
} from '@nestjs/swagger';
|
|
||||||
|
|
||||||
@ApiTags('rices')
|
@ApiTags('rices')
|
||||||
@Controller('rices')
|
@Controller('rices')
|
||||||
|
@ -32,47 +22,72 @@ export class RicesController {
|
||||||
|
|
||||||
@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.' })
|
||||||
@ApiConsumes('multipart/form-data')
|
@ApiHeader({
|
||||||
@ApiBody({
|
name: 'X-Zen-Rice-Name',
|
||||||
description: 'Data required to create a rice',
|
|
||||||
schema: {
|
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
name: {
|
|
||||||
type: 'string',
|
|
||||||
description: 'Name of the rice',
|
description: 'Name of the rice',
|
||||||
example: 'My First Rice',
|
required: true,
|
||||||
},
|
})
|
||||||
content: {
|
@ApiHeader({
|
||||||
type: 'string',
|
name: 'X-Zen-Rice-Author',
|
||||||
description: 'The JSON content to upload',
|
description: 'Author of the rice',
|
||||||
},
|
required: true,
|
||||||
},
|
})
|
||||||
},
|
@ApiHeader({
|
||||||
|
name: 'User-Agent',
|
||||||
|
description: 'User-Agent in the format ZenBrowser/<version> (<OS>)',
|
||||||
|
required: true,
|
||||||
})
|
})
|
||||||
@Post()
|
@Post()
|
||||||
async createRice(@Body() createRiceDto: CreateRiceDto) {
|
async createRice(
|
||||||
return this.ricesService.create(createRiceDto);
|
@Body() content: string,
|
||||||
|
@Headers() headers: Record<string, string>,
|
||||||
|
) {
|
||||||
|
const contentString =
|
||||||
|
typeof content === 'string' ? content : JSON.stringify(content);
|
||||||
|
return this.ricesService.create(contentString, headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiOperation({ summary: 'Get information about a Rice' })
|
@ApiOperation({ summary: 'Get information about a Rice' })
|
||||||
@ApiResponse({ status: 200, description: 'Returns metadata of the Rice.' })
|
@ApiResponse({ status: 200, description: 'Returns metadata of the Rice.' })
|
||||||
@Get(':slug')
|
@Get(':slug')
|
||||||
|
/************* ✨ Codeium Command ⭐ *************/
|
||||||
|
/**
|
||||||
|
* Retrieve metadata of a rice with the given slug.
|
||||||
|
* @param slug Slug of the rice.
|
||||||
|
* @returns Metadata of the rice if found, otherwise throws a NotFoundException.
|
||||||
|
*/
|
||||||
|
/****** c6f70808-e78d-4b17-a285-d2fd79527659 *******/
|
||||||
async getRice(@Param('slug') slug: string) {
|
async getRice(@Param('slug') slug: string) {
|
||||||
return this.ricesService.findOne(slug);
|
return this.ricesService.findOne(slug);
|
||||||
}
|
}
|
||||||
|
|
||||||
@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.' })
|
||||||
@ApiConsumes('multipart/form-data')
|
@ApiHeader({
|
||||||
|
name: 'X-Zen-Rice-Name',
|
||||||
|
description: 'Name of the rice',
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
@ApiHeader({
|
||||||
|
name: 'X-Zen-Rice-Author',
|
||||||
|
description: 'Author of the rice',
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
@ApiHeader({
|
||||||
|
name: 'User-Agent',
|
||||||
|
description: 'User-Agent in the format ZenBrowser/<version> (<OS>)',
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
@Put(':slug')
|
@Put(':slug')
|
||||||
@UseInterceptors(FileInterceptor('file'))
|
|
||||||
async updateRice(
|
async updateRice(
|
||||||
@Param('slug') slug: string,
|
@Param('slug') slug: string,
|
||||||
@Headers('x-rices-token') token: string,
|
@Body() content: string,
|
||||||
@Body() updateRiceDto: UpdateRiceDto,
|
@Headers() headers: Record<string, string>,
|
||||||
|
@Headers('x-zen-rices-token') token: string,
|
||||||
) {
|
) {
|
||||||
return this.ricesService.update(slug, token, updateRiceDto);
|
const contentString =
|
||||||
|
typeof content === 'string' ? content : JSON.stringify(content);
|
||||||
|
return this.ricesService.update(slug, token, contentString, headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiOperation({ summary: 'Delete an existing Rice' })
|
@ApiOperation({ summary: 'Delete an existing Rice' })
|
||||||
|
@ -81,15 +96,12 @@ export class RicesController {
|
||||||
@Delete(':slug')
|
@Delete(':slug')
|
||||||
async removeRice(
|
async removeRice(
|
||||||
@Param('slug') slug: string,
|
@Param('slug') slug: string,
|
||||||
@Headers('x-rices-token') token: string,
|
@Headers('x-zen-rices-token') token: string,
|
||||||
) {
|
) {
|
||||||
await this.ricesService.remove(slug, token);
|
await this.ricesService.remove(slug, token);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================================
|
|
||||||
// NEW ENDPOINT FOR MODERATION DELETION
|
|
||||||
// =========================================
|
|
||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
summary: 'Forcefully delete a Rice (moderation)',
|
summary: 'Forcefully delete a Rice (moderation)',
|
||||||
description:
|
description:
|
||||||
|
@ -102,12 +114,9 @@ export class RicesController {
|
||||||
@Param('slug') slug: string,
|
@Param('slug') slug: string,
|
||||||
@Headers('x-moderation-secret') moderationSecret: string,
|
@Headers('x-moderation-secret') moderationSecret: string,
|
||||||
) {
|
) {
|
||||||
// Verify the secret
|
|
||||||
if (moderationSecret !== process.env.MODERATION_SECRET) {
|
if (moderationSecret !== process.env.MODERATION_SECRET) {
|
||||||
throw new UnauthorizedException('Invalid moderation secret');
|
throw new UnauthorizedException('Invalid moderation secret');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call the service to delete without a token
|
|
||||||
await this.ricesService.moderateRemove(slug);
|
await this.ricesService.moderateRemove(slug);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,6 @@ import {
|
||||||
ConflictException,
|
ConflictException,
|
||||||
BadRequestException,
|
BadRequestException,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { CreateRiceDto } from './dto/create-rice.dto';
|
|
||||||
import { UpdateRiceDto } from './dto/update-rice.dto';
|
|
||||||
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 { GitHubService } from '../github/github.service';
|
import { GitHubService } from '../github/github.service';
|
||||||
|
@ -19,42 +17,82 @@ export class RicesService {
|
||||||
private readonly supabaseService: SupabaseService,
|
private readonly supabaseService: SupabaseService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async create(createRiceDto: CreateRiceDto) {
|
async create(content: string, headers: Record<string, string>) {
|
||||||
try {
|
try {
|
||||||
// Ensure required fields are present
|
// Validate headers
|
||||||
if (!createRiceDto.name || !createRiceDto.version || !createRiceDto.os) {
|
const name = headers['x-zen-rice-name'];
|
||||||
|
const author = headers['x-zen-rice-author'];
|
||||||
|
const userAgent = headers['user-agent'];
|
||||||
|
|
||||||
|
if (!name || !author || !userAgent) {
|
||||||
throw new BadRequestException(
|
throw new BadRequestException(
|
||||||
'Missing required fields: name, version, and os are mandatory.',
|
'Missing required headers: X-Zen-Rice-Name, X-Zen-Rice-Author, and User-Agent are mandatory.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate content
|
||||||
|
if (typeof content !== 'string') {
|
||||||
|
throw new BadRequestException('The request body must be a string.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate lengths
|
||||||
|
if (name.length > 75) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
`The value of X-Zen-Rice-Name exceeds the maximum allowed length of 75 characters.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (author.length > 100) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
`The value of X-Zen-Rice-Author exceeds the maximum allowed length of 100 characters.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse version and OS from User-Agent
|
||||||
|
const userAgentRegex = /ZenBrowser\/(\d+\.\d+\.\d+) \((.+)\)/;
|
||||||
|
const match = userAgent.match(userAgentRegex);
|
||||||
|
|
||||||
|
if (!match) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
'Invalid User-Agent format. Expected format: ZenBrowser/<version> (<OS>).',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [, version, os] = match;
|
||||||
|
// Validate version and OS lengths
|
||||||
|
if (version.length > 10) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
`The version in User-Agent exceeds the maximum allowed length of 10 characters.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (os.length > 30) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
`The operating system in User-Agent exceeds the maximum allowed length of 30 characters.`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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(
|
const existingRice = await this.supabaseService.getRiceByName(name);
|
||||||
createRiceDto.name,
|
|
||||||
);
|
|
||||||
if (existingRice) {
|
if (existingRice) {
|
||||||
throw new ConflictException(
|
throw new ConflictException(
|
||||||
`A rice with the name '${createRiceDto.name}' already exists.`,
|
`A rice with the name '${name}' already exists.`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const slug = createRiceDto.name
|
const slug = `${generateSlug(name)}-${uuidv4()}`;
|
||||||
? `${generateSlug(createRiceDto.name)}-${uuidv4()}`
|
|
||||||
: uuidv4();
|
|
||||||
|
|
||||||
const token = uuidv4();
|
const token = uuidv4();
|
||||||
|
|
||||||
const encodedContent = Buffer.from(
|
const encodedContent = Buffer.from(content).toString('base64');
|
||||||
JSON.stringify(createRiceDto.content),
|
|
||||||
).toString('base64');
|
|
||||||
|
|
||||||
const metadata = {
|
const metadata = {
|
||||||
id: uuidv4(),
|
id: uuidv4(),
|
||||||
token,
|
token,
|
||||||
name: createRiceDto.name,
|
name,
|
||||||
version: createRiceDto.version,
|
author,
|
||||||
os: createRiceDto.os,
|
version,
|
||||||
slug: slug,
|
os,
|
||||||
|
slug,
|
||||||
visits: 0,
|
visits: 0,
|
||||||
level: 0,
|
level: 0,
|
||||||
created_at: new Date().toISOString(),
|
created_at: new Date().toISOString(),
|
||||||
|
@ -63,32 +101,17 @@ export class RicesService {
|
||||||
// Insert metadata into Supabase
|
// Insert metadata into Supabase
|
||||||
await this.supabaseService.insertRice(metadata);
|
await this.supabaseService.insertRice(metadata);
|
||||||
|
|
||||||
if (createRiceDto.content) {
|
|
||||||
const uploadedFilePath = `rices/${slug}/data.zenrice`;
|
const uploadedFilePath = `rices/${slug}/data.zenrice`;
|
||||||
await this.gitHubService.createOrUpdateFile(
|
await this.gitHubService.createOrUpdateFile(
|
||||||
uploadedFilePath,
|
uploadedFilePath,
|
||||||
encodedContent,
|
encodedContent,
|
||||||
`Add file createRiceDto.content to rice ${slug}`,
|
`Add content to rice ${slug}`,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
return { slug, token };
|
return { slug, token };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Log the error for debugging
|
|
||||||
console.error('Error in create method:', error);
|
console.error('Error in create method:', error);
|
||||||
|
throw error;
|
||||||
if (
|
|
||||||
error instanceof ConflictException ||
|
|
||||||
error instanceof BadRequestException
|
|
||||||
) {
|
|
||||||
throw error; // Or create a custom response
|
|
||||||
} else if (error instanceof Error) {
|
|
||||||
// Extract a user-friendly message if possible, or log details
|
|
||||||
throw new Error(`Rice creation failed: ${error.message}`); // More informative error message
|
|
||||||
} else {
|
|
||||||
// Catch unexpected errors
|
|
||||||
throw new Error('Internal Server Error'); // Only for truly unexpected issues
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,51 +137,76 @@ export class RicesService {
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(slug: string, token: string, updateRiceDto: UpdateRiceDto) {
|
async update(
|
||||||
/************* ✨ Codeium Command ⭐ *************/
|
slug: string,
|
||||||
/**
|
token: string,
|
||||||
* Updates the metadata and content of a rice entry identified by its slug.
|
content: string,
|
||||||
*
|
headers: Record<string, string>,
|
||||||
* @param slug - The unique identifier for the rice entry.
|
) {
|
||||||
* @param token - The authorization token to verify the request.
|
try {
|
||||||
* @param updateRiceDto - Data Transfer Object containing fields to update.
|
// Extract fields from headers
|
||||||
*
|
const name = headers['x-zen-rice-name'];
|
||||||
* @returns A confirmation message indicating successful update.
|
const author = headers['x-zen-rice-author'];
|
||||||
*
|
const userAgent = headers['user-agent'];
|
||||||
* @throws NotFoundException - If the rice entry does not exist.
|
|
||||||
* @throws UnauthorizedException - If the provided token is invalid.
|
if (!name || !author || !userAgent) {
|
||||||
*/
|
|
||||||
/****** bf5f61f3-c1dc-40a0-85e6-288824144ead *******/ const rice =
|
|
||||||
await this.supabaseService.getRiceBySlug(slug);
|
|
||||||
if (!rice) throw new NotFoundException('Rice not found');
|
|
||||||
if (rice.token !== token) throw new UnauthorizedException('Invalid token');
|
|
||||||
if (!updateRiceDto.content) {
|
|
||||||
throw new BadRequestException(
|
throw new BadRequestException(
|
||||||
'Missing required fields: content is mandatory.',
|
'Missing required headers: X-Zen-Rice-Name, X-Zen-Rice-Author, and User-Agent are mandatory.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse version and OS from User-Agent
|
||||||
|
const userAgentRegex = /ZenBrowser\/(\d+\.\d+\.\d+) \((.+)\)/;
|
||||||
|
const match = userAgent.match(userAgentRegex);
|
||||||
|
|
||||||
|
if (!match) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
'Invalid User-Agent format. Expected format: ZenBrowser/<version> (<OS>).',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [, version, os] = match;
|
||||||
|
|
||||||
|
// Check if the rice exists
|
||||||
|
const rice = await this.supabaseService.getRiceBySlug(slug);
|
||||||
|
if (!rice) {
|
||||||
|
throw new NotFoundException('Rice not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate token, name, and author match the existing record
|
||||||
|
if (rice.token !== token) {
|
||||||
|
throw new UnauthorizedException('Invalid token.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate name and author match the existing record
|
||||||
|
if (rice.name !== name || rice.author !== author) {
|
||||||
|
throw new UnauthorizedException(
|
||||||
|
'Provided name and author do not match the existing record.',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedMetadata = {
|
const updatedMetadata = {
|
||||||
...rice,
|
...rice,
|
||||||
|
version,
|
||||||
|
os,
|
||||||
updated_at: new Date().toISOString(),
|
updated_at: new Date().toISOString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.supabaseService.updateRice(slug, updatedMetadata);
|
await this.supabaseService.updateRice(slug, updatedMetadata);
|
||||||
|
|
||||||
if (updateRiceDto.content) {
|
const encodedContent = Buffer.from(content).toString('base64');
|
||||||
const encodedContent = Buffer.from(
|
|
||||||
JSON.stringify(updateRiceDto.content),
|
|
||||||
).toString('base64');
|
|
||||||
|
|
||||||
const uploadedFilePath = `rices/${slug}/data.zenrice`;
|
const uploadedFilePath = `rices/${slug}/data.zenrice`;
|
||||||
await this.gitHubService.createOrUpdateFile(
|
await this.gitHubService.createOrUpdateFile(
|
||||||
uploadedFilePath,
|
uploadedFilePath,
|
||||||
encodedContent,
|
encodedContent,
|
||||||
`Update file updateRiceDto.content in rice ${slug}`,
|
`Update content in rice ${slug}`,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
return { message: `ok` };
|
return { message: `Rice ${slug} updated successfully.` };
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in update method:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async remove(slug: string, token: string): Promise<void> {
|
async remove(slug: string, token: string): Promise<void> {
|
||||||
|
@ -166,6 +214,11 @@ export class RicesService {
|
||||||
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');
|
||||||
|
|
||||||
|
// Validate token, name, and author match the existing record
|
||||||
|
if (rice.token !== token) {
|
||||||
|
throw new UnauthorizedException('Invalid token.');
|
||||||
|
}
|
||||||
|
|
||||||
await this.supabaseService.deleteRice(slug);
|
await this.supabaseService.deleteRice(slug);
|
||||||
|
|
||||||
const folderPath = `rices/${slug}`;
|
const folderPath = `rices/${slug}`;
|
||||||
|
|
|
@ -87,7 +87,7 @@ describe('Rices API E2E', () => {
|
||||||
|
|
||||||
const updateResponse = await request(app.getHttpServer())
|
const updateResponse = await request(app.getHttpServer())
|
||||||
.put(`/rices/${slug}`)
|
.put(`/rices/${slug}`)
|
||||||
.set('x-rices-token', token)
|
.set('x-zen-rices-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);
|
||||||
|
@ -116,7 +116,7 @@ describe('Rices API E2E', () => {
|
||||||
|
|
||||||
await request(app.getHttpServer())
|
await request(app.getHttpServer())
|
||||||
.delete(`/rices/${slug}`)
|
.delete(`/rices/${slug}`)
|
||||||
.set('x-rices-token', token)
|
.set('x-zen-rices-token', token)
|
||||||
.expect(204);
|
.expect(204);
|
||||||
|
|
||||||
const riceInDatabase = await supabaseService.getRiceBySlug(slug);
|
const riceInDatabase = await supabaseService.getRiceBySlug(slug);
|
||||||
|
|
|
@ -1,19 +1,74 @@
|
||||||
@baseUrl = http://localhost:3000
|
@baseUrl = http://localhost:3000
|
||||||
|
|
||||||
# {
|
|
||||||
# "key": "value",
|
|
||||||
# "description": "Example content"
|
|
||||||
# }
|
|
||||||
|
|
||||||
# {'key':'value','description':'Example content'}
|
|
||||||
|
|
||||||
POST {{baseUrl}}/rices
|
POST {{baseUrl}}/rices
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
X-Zen-Rice-Name: cool-zenrice-aurora
|
||||||
|
X-Zen-Rice-Author: jhon@doe.com
|
||||||
|
User-Agent: ZenBrowser/1.0.0 (EndeavourOS x86_64)
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "cool-zenrice-aurora2",
|
"userChrome": "",
|
||||||
"version": "1.0.0",
|
"userContent": null,
|
||||||
"os": "EndeavourOS x86_64",
|
"enabledMods": [
|
||||||
"content": "{'key':'value','description':'Example content'}"
|
"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
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
@baseUrl = http://localhost:3000
|
@baseUrl = http://localhost:3000
|
||||||
@previous_slug = cool-zenrice-aurora-e99096ae-00da-4d54-9a47-53b20eb57647
|
@previous_slug = cool-zenrice-aurora-249dd7f2-d669-4d1f-892c-df4caa6fcbfe
|
||||||
|
|
||||||
|
|
||||||
GET {{baseUrl}}/rices/{{previous_slug}}
|
GET {{baseUrl}}/rices/{{previous_slug}}
|
|
@ -1,19 +1,78 @@
|
||||||
@baseUrl = http://localhost:3000
|
@baseUrl = http://localhost:3000
|
||||||
@previous_slug = cool-zenrice-aurora-ef732cbc-fdde-4f76-b4e3-cff0ec8b6f39
|
@previous_slug = cool-zenrice-aurora-249dd7f2-d669-4d1f-892c-df4caa6fcbfe
|
||||||
@previous_token = b406f962-5c51-43a9-8382-40e0983a46e7
|
@previous_token = 528bb297-274f-4fe1-87f1-956b9b26e0df
|
||||||
|
|
||||||
# {
|
|
||||||
# "key": "value",
|
|
||||||
# "description": "Example updated content"
|
|
||||||
# }
|
|
||||||
|
|
||||||
# {'key':'value','description':'Example updated content'}
|
|
||||||
|
|
||||||
PUT {{baseUrl}}/rices/{{previous_slug}}
|
PUT {{baseUrl}}/rices/{{previous_slug}}
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
x-rices-token: {{previous_token}}
|
x-zen-rices-token: {{previous_token}}
|
||||||
|
X-Zen-Rice-Name: cool-zenrice-aurora
|
||||||
|
X-Zen-Rice-Author: jhon@doe.com
|
||||||
|
User-Agent: ZenBrowser/1.0.0 (EndeavourOS x86_64)
|
||||||
|
|
||||||
{
|
{
|
||||||
"content": "{'key':'value','description':'Example updated content'}"
|
"userChrome": "",
|
||||||
|
"userContent": null,
|
||||||
|
"enabledMods": [
|
||||||
|
"5bb07b6e-c89f-4f4a-a0ed-e483cc535594",
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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-rices-token: {{previous_token}}
|
x-zen-rices-token: {{previous_token}}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue