mirror of
https://github.com/zen-browser/rices.git
synced 2025-07-07 08:55: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 (
|
||||
id UUID NOT NULL, -- Unique identifier
|
||||
slug VARCHAR(75) NOT NULL, -- Unique user-friendly identifier
|
||||
version VARCHAR(10) NOT NULL, -- Data version
|
||||
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
|
||||
author VARCHAR(100) NOT NULL, -- Name of the rice
|
||||
token UUID NOT NULL, -- Unique authorization token
|
||||
visits INTEGER DEFAULT 0 NOT NULL, -- Visit counter, initialized to 0
|
||||
level INTEGER DEFAULT 0 NOT NULL, -- Level: 0 (Public), 1 (Verified)
|
||||
|
|
|
@ -6,24 +6,14 @@ import {
|
|||
Delete,
|
||||
Param,
|
||||
Body,
|
||||
UseInterceptors,
|
||||
Headers,
|
||||
HttpCode,
|
||||
HttpStatus,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
import { FileInterceptor } from '@nestjs/platform-express';
|
||||
import { RicesService } from './rices.service';
|
||||
import { CreateRiceDto } from './dto/create-rice.dto';
|
||||
import { UpdateRiceDto } from './dto/update-rice.dto';
|
||||
|
||||
import {
|
||||
ApiTags,
|
||||
ApiOperation,
|
||||
ApiResponse,
|
||||
ApiConsumes,
|
||||
ApiBody,
|
||||
} from '@nestjs/swagger';
|
||||
import { ApiTags, ApiOperation, ApiResponse, ApiHeader } from '@nestjs/swagger';
|
||||
|
||||
@ApiTags('rices')
|
||||
@Controller('rices')
|
||||
|
@ -32,47 +22,72 @@ export class RicesController {
|
|||
|
||||
@ApiOperation({ summary: 'Upload a new Rice' })
|
||||
@ApiResponse({ status: 201, description: 'Rice successfully created.' })
|
||||
@ApiConsumes('multipart/form-data')
|
||||
@ApiBody({
|
||||
description: 'Data required to create a rice',
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: {
|
||||
type: 'string',
|
||||
@ApiHeader({
|
||||
name: 'X-Zen-Rice-Name',
|
||||
description: 'Name of the rice',
|
||||
example: 'My First Rice',
|
||||
},
|
||||
content: {
|
||||
type: 'string',
|
||||
description: 'The JSON content to upload',
|
||||
},
|
||||
},
|
||||
},
|
||||
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,
|
||||
})
|
||||
@Post()
|
||||
async createRice(@Body() createRiceDto: CreateRiceDto) {
|
||||
return this.ricesService.create(createRiceDto);
|
||||
async createRice(
|
||||
@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' })
|
||||
@ApiResponse({ status: 200, description: 'Returns metadata of the Rice.' })
|
||||
@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) {
|
||||
return this.ricesService.findOne(slug);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: 'Update an existing Rice' })
|
||||
@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')
|
||||
@UseInterceptors(FileInterceptor('file'))
|
||||
async updateRice(
|
||||
@Param('slug') slug: string,
|
||||
@Headers('x-rices-token') token: string,
|
||||
@Body() updateRiceDto: UpdateRiceDto,
|
||||
@Body() content: string,
|
||||
@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' })
|
||||
|
@ -81,15 +96,12 @@ export class RicesController {
|
|||
@Delete(':slug')
|
||||
async removeRice(
|
||||
@Param('slug') slug: string,
|
||||
@Headers('x-rices-token') token: string,
|
||||
@Headers('x-zen-rices-token') token: string,
|
||||
) {
|
||||
await this.ricesService.remove(slug, token);
|
||||
return;
|
||||
}
|
||||
|
||||
// =========================================
|
||||
// NEW ENDPOINT FOR MODERATION DELETION
|
||||
// =========================================
|
||||
@ApiOperation({
|
||||
summary: 'Forcefully delete a Rice (moderation)',
|
||||
description:
|
||||
|
@ -102,12 +114,9 @@ export class RicesController {
|
|||
@Param('slug') slug: string,
|
||||
@Headers('x-moderation-secret') moderationSecret: string,
|
||||
) {
|
||||
// Verify the secret
|
||||
if (moderationSecret !== process.env.MODERATION_SECRET) {
|
||||
throw new UnauthorizedException('Invalid moderation secret');
|
||||
}
|
||||
|
||||
// Call the service to delete without a token
|
||||
await this.ricesService.moderateRemove(slug);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -5,8 +5,6 @@ import {
|
|||
ConflictException,
|
||||
BadRequestException,
|
||||
} from '@nestjs/common';
|
||||
import { CreateRiceDto } from './dto/create-rice.dto';
|
||||
import { UpdateRiceDto } from './dto/update-rice.dto';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { generateSlug } from './utils/slug.util';
|
||||
import { GitHubService } from '../github/github.service';
|
||||
|
@ -19,42 +17,82 @@ export class RicesService {
|
|||
private readonly supabaseService: SupabaseService,
|
||||
) {}
|
||||
|
||||
async create(createRiceDto: CreateRiceDto) {
|
||||
async create(content: string, headers: Record<string, string>) {
|
||||
try {
|
||||
// Ensure required fields are present
|
||||
if (!createRiceDto.name || !createRiceDto.version || !createRiceDto.os) {
|
||||
// Validate headers
|
||||
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(
|
||||
'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
|
||||
const existingRice = await this.supabaseService.getRiceByName(
|
||||
createRiceDto.name,
|
||||
);
|
||||
const existingRice = await this.supabaseService.getRiceByName(name);
|
||||
if (existingRice) {
|
||||
throw new ConflictException(
|
||||
`A rice with the name '${createRiceDto.name}' already exists.`,
|
||||
`A rice with the name '${name}' already exists.`,
|
||||
);
|
||||
}
|
||||
|
||||
const slug = createRiceDto.name
|
||||
? `${generateSlug(createRiceDto.name)}-${uuidv4()}`
|
||||
: uuidv4();
|
||||
|
||||
const slug = `${generateSlug(name)}-${uuidv4()}`;
|
||||
const token = uuidv4();
|
||||
|
||||
const encodedContent = Buffer.from(
|
||||
JSON.stringify(createRiceDto.content),
|
||||
).toString('base64');
|
||||
const encodedContent = Buffer.from(content).toString('base64');
|
||||
|
||||
const metadata = {
|
||||
id: uuidv4(),
|
||||
token,
|
||||
name: createRiceDto.name,
|
||||
version: createRiceDto.version,
|
||||
os: createRiceDto.os,
|
||||
slug: slug,
|
||||
name,
|
||||
author,
|
||||
version,
|
||||
os,
|
||||
slug,
|
||||
visits: 0,
|
||||
level: 0,
|
||||
created_at: new Date().toISOString(),
|
||||
|
@ -63,32 +101,17 @@ export class RicesService {
|
|||
// Insert metadata into Supabase
|
||||
await this.supabaseService.insertRice(metadata);
|
||||
|
||||
if (createRiceDto.content) {
|
||||
const uploadedFilePath = `rices/${slug}/data.zenrice`;
|
||||
await this.gitHubService.createOrUpdateFile(
|
||||
uploadedFilePath,
|
||||
encodedContent,
|
||||
`Add file createRiceDto.content to rice ${slug}`,
|
||||
`Add content to rice ${slug}`,
|
||||
);
|
||||
}
|
||||
|
||||
return { slug, token };
|
||||
} catch (error) {
|
||||
// Log the error for debugging
|
||||
console.error('Error in create method:', 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
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -114,51 +137,76 @@ export class RicesService {
|
|||
return content;
|
||||
}
|
||||
|
||||
async update(slug: string, token: string, updateRiceDto: UpdateRiceDto) {
|
||||
/************* ✨ Codeium Command ⭐ *************/
|
||||
/**
|
||||
* Updates the metadata and content of a rice entry identified by its slug.
|
||||
*
|
||||
* @param slug - The unique identifier for the rice entry.
|
||||
* @param token - The authorization token to verify the request.
|
||||
* @param updateRiceDto - Data Transfer Object containing fields to update.
|
||||
*
|
||||
* @returns A confirmation message indicating successful update.
|
||||
*
|
||||
* @throws NotFoundException - If the rice entry does not exist.
|
||||
* @throws UnauthorizedException - If the provided token is invalid.
|
||||
*/
|
||||
/****** 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) {
|
||||
async update(
|
||||
slug: string,
|
||||
token: string,
|
||||
content: string,
|
||||
headers: Record<string, string>,
|
||||
) {
|
||||
try {
|
||||
// Extract fields from headers
|
||||
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(
|
||||
'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 = {
|
||||
...rice,
|
||||
version,
|
||||
os,
|
||||
updated_at: new Date().toISOString(),
|
||||
};
|
||||
|
||||
await this.supabaseService.updateRice(slug, updatedMetadata);
|
||||
|
||||
if (updateRiceDto.content) {
|
||||
const encodedContent = Buffer.from(
|
||||
JSON.stringify(updateRiceDto.content),
|
||||
).toString('base64');
|
||||
|
||||
const encodedContent = Buffer.from(content).toString('base64');
|
||||
const uploadedFilePath = `rices/${slug}/data.zenrice`;
|
||||
await this.gitHubService.createOrUpdateFile(
|
||||
uploadedFilePath,
|
||||
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> {
|
||||
|
@ -166,6 +214,11 @@ export class RicesService {
|
|||
if (!rice) throw new NotFoundException('Rice not found');
|
||||
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);
|
||||
|
||||
const folderPath = `rices/${slug}`;
|
||||
|
|
|
@ -87,7 +87,7 @@ describe('Rices API E2E', () => {
|
|||
|
||||
const updateResponse = await request(app.getHttpServer())
|
||||
.put(`/rices/${slug}`)
|
||||
.set('x-rices-token', token)
|
||||
.set('x-zen-rices-token', token)
|
||||
.field('name', 'Updated Rice')
|
||||
.attach('file', path.join(__dirname, 'files', 'example_update.zenrice'))
|
||||
.expect(200);
|
||||
|
@ -116,7 +116,7 @@ describe('Rices API E2E', () => {
|
|||
|
||||
await request(app.getHttpServer())
|
||||
.delete(`/rices/${slug}`)
|
||||
.set('x-rices-token', token)
|
||||
.set('x-zen-rices-token', token)
|
||||
.expect(204);
|
||||
|
||||
const riceInDatabase = await supabaseService.getRiceBySlug(slug);
|
||||
|
|
|
@ -1,19 +1,74 @@
|
|||
@baseUrl = http://localhost:3000
|
||||
|
||||
# {
|
||||
# "key": "value",
|
||||
# "description": "Example content"
|
||||
# }
|
||||
|
||||
# {'key':'value','description':'Example content'}
|
||||
|
||||
POST {{baseUrl}}/rices
|
||||
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",
|
||||
"version": "1.0.0",
|
||||
"os": "EndeavourOS x86_64",
|
||||
"content": "{'key':'value','description':'Example content'}"
|
||||
"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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
@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}}
|
|
@ -1,19 +1,78 @@
|
|||
@baseUrl = http://localhost:3000
|
||||
@previous_slug = cool-zenrice-aurora-ef732cbc-fdde-4f76-b4e3-cff0ec8b6f39
|
||||
@previous_token = b406f962-5c51-43a9-8382-40e0983a46e7
|
||||
|
||||
# {
|
||||
# "key": "value",
|
||||
# "description": "Example updated content"
|
||||
# }
|
||||
|
||||
# {'key':'value','description':'Example updated content'}
|
||||
@previous_slug = cool-zenrice-aurora-249dd7f2-d669-4d1f-892c-df4caa6fcbfe
|
||||
@previous_token = 528bb297-274f-4fe1-87f1-956b9b26e0df
|
||||
|
||||
PUT {{baseUrl}}/rices/{{previous_slug}}
|
||||
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
|
||||
|
||||
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