mirror of
https://github.com/zen-browser/rices.git
synced 2025-07-07 00:45:40 +02:00
spaces storage
This commit is contained in:
parent
079cde591e
commit
f05fd437aa
14 changed files with 256 additions and 100 deletions
|
@ -1,22 +1,32 @@
|
|||
--DROP TABLE IF EXISTS rices;
|
||||
-- Drop tables if they exist
|
||||
DROP TABLE IF EXISTS shared;
|
||||
DROP TABLE IF EXISTS shared_types;
|
||||
|
||||
CREATE TABLE shared (
|
||||
id UUID NOT NULL, -- Unique 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
|
||||
os VARCHAR(30) NOT NULL, -- Operating system
|
||||
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)
|
||||
created_at TIMESTAMP DEFAULT NOW(), -- Creation date
|
||||
updated_at TIMESTAMP, -- Last update date
|
||||
PRIMARY KEY (id), -- Composite primary key
|
||||
UNIQUE (slug) -- Ensure slug is unique
|
||||
-- Create table for shared types
|
||||
CREATE TABLE shared_types (
|
||||
key VARCHAR(50) PRIMARY KEY -- Type key (e.g., 'WORKSPACE', 'RICE')
|
||||
);
|
||||
|
||||
-- Create table for shared items
|
||||
CREATE TABLE shared (
|
||||
id UUID NOT NULL PRIMARY KEY, -- Unique identifier
|
||||
slug VARCHAR(75) NOT NULL UNIQUE, -- Unique user-friendly identifier
|
||||
type VARCHAR(15) NOT NULL REFERENCES shared_types(key) ON DELETE CASCADE, -- Foreign key to shared_types
|
||||
version VARCHAR(10) NOT NULL, -- Data version
|
||||
os VARCHAR(30) NOT NULL, -- Operating system
|
||||
name VARCHAR(75) NOT NULL, -- Name of the rice
|
||||
author VARCHAR(100) NOT NULL, -- Name of the author
|
||||
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)
|
||||
created_at TIMESTAMP DEFAULT NOW(), -- Creation date
|
||||
updated_at TIMESTAMP -- Last update date
|
||||
);
|
||||
|
||||
-- Insert default types
|
||||
INSERT INTO shared_types (key) VALUES ('WORKSPACE'), ('RICE');
|
||||
|
||||
-- Create function to increment visit count
|
||||
CREATE OR REPLACE FUNCTION increment_visits(slug_param TEXT)
|
||||
RETURNS VOID AS $$
|
||||
BEGIN
|
||||
|
@ -24,4 +34,4 @@ BEGIN
|
|||
SET visits = visits + 1
|
||||
WHERE slug = slug_param;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
|
|
@ -16,11 +16,12 @@ import {
|
|||
import { Response } from 'express';
|
||||
import { SharedService } from './shared.service';
|
||||
import { ApiTags, ApiOperation, ApiResponse, ApiHeader } from '@nestjs/swagger';
|
||||
import { SHARED_TYPES } from './shared.module';
|
||||
|
||||
@ApiTags('rices')
|
||||
@Controller('rices')
|
||||
export class RicesController {
|
||||
constructor(private readonly sharedService: SharedService) {}
|
||||
constructor(private readonly sharedService: SharedService) { }
|
||||
|
||||
@ApiOperation({ summary: 'Upload a new Rice' })
|
||||
@ApiResponse({ status: 201, description: 'Rice successfully created.' })
|
||||
|
@ -50,7 +51,7 @@ export class RicesController {
|
|||
|
||||
this.validateFileSize(contentString); // Validate file size
|
||||
|
||||
return this.sharedService.create(contentString, token, headers);
|
||||
return this.sharedService.create(SHARED_TYPES.RICE, contentString, token, headers);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: 'Get information about a Rice' })
|
||||
|
@ -60,7 +61,7 @@ export class RicesController {
|
|||
})
|
||||
@Get(':slug')
|
||||
async getRice(@Param('slug') slug: string, @Res() res: Response) {
|
||||
const riceMetadata = await this.sharedService.getRiceMetadata(slug);
|
||||
const riceMetadata = await this.sharedService.getRiceMetadata(SHARED_TYPES.RICE, slug);
|
||||
|
||||
const htmlContent = `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
@ -120,7 +121,7 @@ export class RicesController {
|
|||
|
||||
this.validateFileSize(contentString); // Validate file size
|
||||
|
||||
return this.sharedService.update(slug, token, contentString, headers);
|
||||
return this.sharedService.update(SHARED_TYPES.RICE, slug, token, contentString, headers);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: 'Delete an existing Rice' })
|
||||
|
@ -131,7 +132,7 @@ export class RicesController {
|
|||
@Param('slug') slug: string,
|
||||
@Headers('x-zen-shared-token') token: string,
|
||||
) {
|
||||
await this.sharedService.remove(slug, token);
|
||||
await this.sharedService.remove(SHARED_TYPES.RICE, slug, token);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -150,7 +151,7 @@ export class RicesController {
|
|||
if (moderationSecret !== process.env.MODERATION_SECRET) {
|
||||
throw new UnauthorizedException('Invalid moderation secret');
|
||||
}
|
||||
await this.sharedService.moderateRemove(slug);
|
||||
await this.sharedService.moderateRemove(SHARED_TYPES.RICE, slug);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -4,12 +4,19 @@ import { Module } from '@nestjs/common';
|
|||
import { SharedService } from './shared.service';
|
||||
import { GitHubModule } from '../github/github.module';
|
||||
import { RicesController } from './rices.controller';
|
||||
import { SpacesController } from './spaces.controller';
|
||||
import { SupabaseService } from '../supabase/supabase.service';
|
||||
|
||||
export const SHARED_TYPES = {
|
||||
WORKSPACE: "WORKSPACE",
|
||||
RICE: "RICE",
|
||||
};
|
||||
|
||||
|
||||
@Module({
|
||||
imports: [GitHubModule],
|
||||
providers: [SharedService, SupabaseService],
|
||||
controllers: [RicesController],
|
||||
controllers: [RicesController, SpacesController],
|
||||
exports: [SharedService],
|
||||
})
|
||||
export class SharedModule {}
|
||||
export class SharedModule { }
|
||||
|
|
|
@ -13,6 +13,7 @@ import { generateSlug } from './utils/slug.util';
|
|||
import { ConfigService } from '@nestjs/config';
|
||||
import { GitHubService } from '../github/github.service';
|
||||
import { SupabaseService } from '../supabase/supabase.service';
|
||||
import { SHARED_TYPES } from './shared.module';
|
||||
|
||||
const userAgentRegex = /ZenBrowser\/(\d+\.\d\w?\.\d) \((.+)\)/;
|
||||
|
||||
|
@ -22,9 +23,10 @@ export class SharedService {
|
|||
private readonly gitHubService: GitHubService,
|
||||
private readonly supabaseService: SupabaseService,
|
||||
private readonly configService: ConfigService,
|
||||
) {}
|
||||
) { }
|
||||
|
||||
async create(
|
||||
type: string,
|
||||
content: string,
|
||||
token: string | null,
|
||||
headers: Record<string, string>,
|
||||
|
@ -36,7 +38,7 @@ export class SharedService {
|
|||
const userAgent = headers['user-agent'];
|
||||
|
||||
if (!name || !author || !userAgent) {
|
||||
throw new BadRequestException('Rice name and author are required!');
|
||||
throw new BadRequestException('shared name and author are required!');
|
||||
}
|
||||
|
||||
// Validate content
|
||||
|
@ -45,14 +47,19 @@ export class SharedService {
|
|||
}
|
||||
|
||||
try {
|
||||
this.validateJsonStructure(content);
|
||||
if (type == SHARED_TYPES.RICE) {
|
||||
this.validateRicesJsonStructure(content);
|
||||
}
|
||||
else if (type == SHARED_TYPES.WORKSPACE) {
|
||||
this.validateWorkspaceJsonStructure(content);
|
||||
}
|
||||
|
||||
content = this.sanitizeJson(content);
|
||||
content = this.minimizeJson(content);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
} catch (e) {
|
||||
throw new BadRequestException('Invalid json request');
|
||||
throw new BadRequestException(e);
|
||||
}
|
||||
|
||||
// Validate lengths
|
||||
|
@ -106,29 +113,36 @@ export class SharedService {
|
|||
throw new BadRequestException(`Invalid name provided`);
|
||||
}
|
||||
|
||||
if (!type) {
|
||||
throw new BadRequestException(`Invalid type provided`);
|
||||
}
|
||||
|
||||
if (!token) {
|
||||
token = uuidv4();
|
||||
} else {
|
||||
const tokenMaxCount = this.configService.get<number>(
|
||||
'MAX_RICES_BY_TOKEN',
|
||||
5,
|
||||
);
|
||||
const tokenCount = await this.supabaseService.countSharedByToken(token);
|
||||
if (tokenCount >= tokenMaxCount) {
|
||||
throw new ConflictException(
|
||||
`The token '${token}' is already associated with 5 or more rices.`,
|
||||
if (type == SHARED_TYPES.RICE) {
|
||||
const tokenMaxCount = this.configService.get<number>(
|
||||
'MAX_RICES_BY_TOKEN',
|
||||
5,
|
||||
);
|
||||
const tokenCount = await this.supabaseService.countSharedByToken(token);
|
||||
if (tokenCount >= tokenMaxCount) {
|
||||
throw new ConflictException(
|
||||
`The token '${token}' is already associated with 5 or more rices.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const metadata = {
|
||||
id: uuidv4(),
|
||||
token,
|
||||
name,
|
||||
author,
|
||||
version,
|
||||
os,
|
||||
slug,
|
||||
slug: slug,
|
||||
type: type,
|
||||
version: version,
|
||||
os: os,
|
||||
name: name,
|
||||
author: author,
|
||||
token: token,
|
||||
visits: 0,
|
||||
level: 0,
|
||||
created_at: new Date().toISOString(),
|
||||
|
@ -137,11 +151,11 @@ export class SharedService {
|
|||
// Insert metadata into Supabase
|
||||
await this.supabaseService.insertShared(metadata);
|
||||
|
||||
const uploadedFilePath = `rices/${slug}/data.zenrice`;
|
||||
const uploadedFilePath = this.getSharedFilePath(type, slug);
|
||||
await this.gitHubService.createOrUpdateFile(
|
||||
uploadedFilePath,
|
||||
content,
|
||||
`Add content to rice ${slug}`,
|
||||
`Add content to shared ${slug}`,
|
||||
);
|
||||
|
||||
return { slug, token };
|
||||
|
@ -151,17 +165,40 @@ export class SharedService {
|
|||
}
|
||||
}
|
||||
|
||||
async findOne(slug: string) {
|
||||
getShortSharedFilePath(type: string, slug: string) {
|
||||
switch (type) {
|
||||
case SHARED_TYPES.WORKSPACE:
|
||||
return `spaces/${slug}`;
|
||||
case SHARED_TYPES.RICE:
|
||||
return `rices/${slug}`;
|
||||
default:
|
||||
throw new Error(`Unknown shared type: ${type}`);
|
||||
}
|
||||
}
|
||||
|
||||
getSharedFilePath(type: string, slug: string) {
|
||||
switch (type) {
|
||||
case SHARED_TYPES.WORKSPACE:
|
||||
return `spaces/${slug}/data.zenspace`;
|
||||
case SHARED_TYPES.RICE:
|
||||
return `rices/${slug}/data.zenrice`;
|
||||
default:
|
||||
throw new Error(`Unknown shared type: ${type}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async findOne(type: string, slug: string) {
|
||||
// Check if the rice exists in the database
|
||||
const rice = await this.supabaseService.getSharedBySlug(slug);
|
||||
if (!rice) throw new NotFoundException('Rice not found');
|
||||
const shared = await this.supabaseService.getSharedBySlug(type, slug);
|
||||
if (!shared) throw new NotFoundException('shared not found');
|
||||
|
||||
// Fetch the file from GitHub
|
||||
const filePath = `rices/${slug}/data.zenrice`;
|
||||
const filePath = this.getSharedFilePath(type, slug);
|
||||
const fileContent = await this.gitHubService.getFileContent(filePath);
|
||||
|
||||
if (!fileContent) {
|
||||
throw new NotFoundException('Rice file not found in GitHub');
|
||||
throw new NotFoundException('Shared file not found in GitHub');
|
||||
}
|
||||
|
||||
// Remove unescaped double quotes at the beginning and end, if present
|
||||
|
@ -170,14 +207,15 @@ export class SharedService {
|
|||
return fileContent;
|
||||
}
|
||||
|
||||
async getRiceMetadata(slug: string) {
|
||||
const rice = await this.supabaseService.getSharedBySlug(slug);
|
||||
if (!rice) throw new NotFoundException('Rice not found');
|
||||
async getRiceMetadata(type: string, slug: string) {
|
||||
const shared = await this.supabaseService.getSharedBySlug(type, slug);
|
||||
if (!shared) throw new NotFoundException('Shared not found');
|
||||
|
||||
return rice;
|
||||
return shared;
|
||||
}
|
||||
|
||||
async update(
|
||||
type: string,
|
||||
slug: string,
|
||||
token: string,
|
||||
content: string,
|
||||
|
@ -203,7 +241,12 @@ export class SharedService {
|
|||
}
|
||||
|
||||
try {
|
||||
this.validateJsonStructure(content);
|
||||
if (type == SHARED_TYPES.RICE) {
|
||||
this.validateRicesJsonStructure(content);
|
||||
}
|
||||
else if (type == SHARED_TYPES.WORKSPACE) {
|
||||
this.validateWorkspaceJsonStructure(content);
|
||||
}
|
||||
|
||||
content = this.sanitizeJson(content);
|
||||
content = this.minimizeJson(content);
|
||||
|
@ -215,19 +258,19 @@ export class SharedService {
|
|||
|
||||
const [, version, os] = match;
|
||||
|
||||
// Check if the rice exists
|
||||
const rice = await this.supabaseService.getSharedBySlug(slug);
|
||||
if (!rice) {
|
||||
throw new NotFoundException('Rice not found');
|
||||
// Check if the shared exists
|
||||
const shared = await this.supabaseService.getSharedBySlug(type, slug);
|
||||
if (!shared) {
|
||||
throw new NotFoundException('Shared not found');
|
||||
}
|
||||
|
||||
// Validate token, name, and author match the existing record
|
||||
if (rice.token !== token) {
|
||||
if (shared.token !== token) {
|
||||
throw new UnauthorizedException('Invalid token.');
|
||||
}
|
||||
|
||||
const updatedMetadata = {
|
||||
...rice,
|
||||
...shared,
|
||||
version,
|
||||
os,
|
||||
updated_at: new Date().toISOString(),
|
||||
|
@ -235,33 +278,33 @@ export class SharedService {
|
|||
|
||||
await this.supabaseService.updateShared(slug, updatedMetadata);
|
||||
|
||||
const uploadedFilePath = `rices/${slug}/data.zenrice`;
|
||||
const uploadedFilePath = this.getSharedFilePath(type, slug);
|
||||
await this.gitHubService.createOrUpdateFile(
|
||||
uploadedFilePath,
|
||||
content,
|
||||
`Update content in rice ${slug}`,
|
||||
`Update content in shared ${slug}`,
|
||||
);
|
||||
|
||||
return { message: `Rice ${slug} updated successfully.` };
|
||||
return { message: `shared ${slug} updated successfully.` };
|
||||
} catch (error) {
|
||||
console.error('Error in update method:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async remove(slug: string, token: string): Promise<void> {
|
||||
const rice = await this.supabaseService.getSharedBySlug(slug);
|
||||
if (!rice) throw new NotFoundException('Rice not found');
|
||||
if (rice.token !== token) throw new UnauthorizedException('Invalid token');
|
||||
async remove(type: string, slug: string, token: string): Promise<void> {
|
||||
const shared = await this.supabaseService.getSharedBySlug(type, slug);
|
||||
if (!shared) throw new NotFoundException('shared not found');
|
||||
if (shared.token !== token) throw new UnauthorizedException('Invalid token');
|
||||
|
||||
// Validate token, name, and author match the existing record
|
||||
if (rice.token !== token) {
|
||||
if (shared.token !== token) {
|
||||
throw new UnauthorizedException('Invalid token.');
|
||||
}
|
||||
|
||||
await this.supabaseService.deleteShared(slug);
|
||||
|
||||
const folderPath = `rices/${slug}`;
|
||||
const folderPath = this.getShortSharedFilePath(type, slug);
|
||||
|
||||
// List all files in the folder
|
||||
const files = await this.gitHubService.listFilesInDirectory(folderPath);
|
||||
|
@ -271,7 +314,7 @@ export class SharedService {
|
|||
const filePath = `${folderPath}/${file}`;
|
||||
await this.gitHubService.deleteFile(
|
||||
filePath,
|
||||
`Remove file ${file} in rice ${slug}`,
|
||||
`Remove file ${file} in shared ${slug}`,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -283,36 +326,37 @@ export class SharedService {
|
|||
}
|
||||
|
||||
/**
|
||||
* Delete a rice without checking the user's token.
|
||||
* Delete a shared without checking the user's token.
|
||||
* Exclusive use for moderators with the secret key.
|
||||
*/
|
||||
public async moderateRemove(slug: string): Promise<void> {
|
||||
public async moderateRemove(type: string, slug: string): Promise<void> {
|
||||
try {
|
||||
// 1. Check if rice exists in Supabase
|
||||
const rice = await this.supabaseService.getSharedBySlug(slug);
|
||||
if (!rice) {
|
||||
throw new NotFoundException('Rice not found');
|
||||
// 1. Check if shared exists in Supabase
|
||||
const shared = await this.supabaseService.getSharedBySlug(type, slug);
|
||||
if (!shared) {
|
||||
throw new NotFoundException('shared not found');
|
||||
}
|
||||
|
||||
// 2. Delete metadata from Supabase
|
||||
await this.supabaseService.deleteShared(slug);
|
||||
|
||||
// 3. Delete data.zenrice from GitHub
|
||||
const riceJsonPath = `rices/${slug}/data.zenrice`;
|
||||
|
||||
const jsonPath = this.getSharedFilePath(type, slug);
|
||||
await this.gitHubService.deleteFile(
|
||||
riceJsonPath,
|
||||
`[MODERATION] Remove rice ${slug}`,
|
||||
jsonPath,
|
||||
`[MODERATION] Remove shared ${slug}`,
|
||||
);
|
||||
|
||||
// 4. List and delete uploaded files from GitHub (if any)
|
||||
const filesPath = `rices/${slug}`;
|
||||
const filesPath = this.getShortSharedFilePath(type, slug);
|
||||
const files = await this.gitHubService.listFilesInDirectory(filesPath);
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = `rices/${slug}/${file}`;
|
||||
const filePath = filesPath + `/${file}`;
|
||||
await this.gitHubService.deleteFile(
|
||||
filePath,
|
||||
`[MODERATION] Remove file ${file} from rice ${slug}`,
|
||||
`[MODERATION] Remove file ${file} from shared ${slug}`,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -322,15 +366,19 @@ export class SharedService {
|
|||
`[MODERATION] Remove folder ${filesPath}`,
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Error removing rice by moderation:', error);
|
||||
console.error('Error removing shared by moderation:', error);
|
||||
if (error instanceof NotFoundException) {
|
||||
throw error;
|
||||
}
|
||||
throw new Error('Failed to remove rice by moderation');
|
||||
throw new Error('Failed to remove shared by moderation');
|
||||
}
|
||||
}
|
||||
|
||||
validateJsonStructure(jsonString: string): boolean {
|
||||
validateWorkspaceJsonStructure(jsonString: string): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
validateRicesJsonStructure(jsonString: string): boolean {
|
||||
const requiredKeys: string[] = [
|
||||
'userChrome',
|
||||
'userContent',
|
||||
|
|
59
src/shared/spaces.controller.ts
Normal file
59
src/shared/spaces.controller.ts
Normal file
|
@ -0,0 +1,59 @@
|
|||
import {
|
||||
Controller,
|
||||
Post,
|
||||
Body,
|
||||
Headers,
|
||||
HttpCode,
|
||||
HttpStatus,
|
||||
BadRequestException,
|
||||
} from '@nestjs/common';
|
||||
import { SharedService } from './shared.service';
|
||||
import { ApiTags, ApiOperation, ApiResponse, ApiHeader } from '@nestjs/swagger';
|
||||
import { SHARED_TYPES } from './shared.module';
|
||||
|
||||
@ApiTags('spaces')
|
||||
@Controller('spaces')
|
||||
export class SpacesController {
|
||||
constructor(private readonly sharedService: SharedService) { }
|
||||
|
||||
@ApiOperation({ summary: 'Shared a Space' })
|
||||
@ApiResponse({ status: 201, description: 'Space successfully shared.' })
|
||||
@ApiHeader({
|
||||
name: 'x-zen-shared-name',
|
||||
description: 'Name of the Space',
|
||||
required: true,
|
||||
})
|
||||
@ApiHeader({
|
||||
name: 'x-zen-shared-author',
|
||||
description: 'Author of the Space',
|
||||
required: true,
|
||||
})
|
||||
@ApiHeader({
|
||||
name: 'User-Agent',
|
||||
description: 'User-Agent',
|
||||
required: true,
|
||||
})
|
||||
@Post()
|
||||
async createSpace(
|
||||
@Body() content: string,
|
||||
@Headers() headers: Record<string, string>,
|
||||
@Headers('x-zen-shared-token') token: string,
|
||||
) {
|
||||
const contentString =
|
||||
typeof content === 'string' ? content : JSON.stringify(content);
|
||||
|
||||
this.validateFileSize(contentString); // Validate file size
|
||||
|
||||
return this.sharedService.create(SHARED_TYPES.WORKSPACE, contentString, token, headers);
|
||||
}
|
||||
|
||||
private validateFileSize(content: string) {
|
||||
const sizeInBytes = Buffer.byteLength(content, 'utf-8');
|
||||
const maxSizeInBytes = 1 * 1024 * 512; // 1 MB
|
||||
if (sizeInBytes > maxSizeInBytes) {
|
||||
throw new BadRequestException(
|
||||
`The uploaded content exceeds the size limit of 512 KB.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -44,10 +44,11 @@ export class SupabaseService {
|
|||
return data;
|
||||
}
|
||||
|
||||
async getSharedBySlug(slug: string) {
|
||||
async getSharedBySlug(type: string, slug: string) {
|
||||
const { data, error } = await this.supabase
|
||||
.from('shared')
|
||||
.select('*')
|
||||
.eq('type', type)
|
||||
.eq('slug', slug)
|
||||
.single();
|
||||
if (error) {
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
@baseUrl = http://localhost:3000
|
||||
@previous_slug = cool-zenrice-test-base-0ae004db-fdca-4df6-8833-1104ac1662f6
|
||||
|
||||
|
||||
GET {{baseUrl}}/rices/{{previous_slug}}
|
|
@ -1,6 +0,0 @@
|
|||
@baseUrl = http://localhost:3000
|
||||
@previous_slug = cool-zenrice-aurora2-b970a742-789c-4349-8a4d-da63c8bbe77d
|
||||
@previous_token = 03fbfdb4-d3a5-4d64-8740-feac7d32e7a8
|
||||
|
||||
DELETE {{baseUrl}}/rices/{{previous_slug}}
|
||||
x-zen-shared-token: {{previous_token}}
|
5
test/restclient/rice/02_download_rice.http
Normal file
5
test/restclient/rice/02_download_rice.http
Normal file
|
@ -0,0 +1,5 @@
|
|||
@baseUrl = http://localhost:3000
|
||||
@previous_slug = cool-zenrice-test-base-a069a4c2-237d-433f-ab1c-38c6e6ba5244
|
||||
|
||||
|
||||
GET {{baseUrl}}/rices/{{previous_slug}}
|
|
@ -1,6 +1,6 @@
|
|||
@baseUrl = http://localhost:3000
|
||||
@previous_slug = cool-zenrice-test-base64211-5f874c8c-71f7-4b45-830a-aa86c9328455
|
||||
@previous_token = 84780af0-191e-4f77-8c23-25165c89d27e
|
||||
@previous_slug = cool-zenrice-test-base-1d576eeb-de28-4df8-a12f-bcfc8f0e9b6e
|
||||
@previous_token = 00472a9f-8a8c-423d-b4a5-7137c4cc13f6
|
||||
|
||||
PUT {{baseUrl}}/rices/{{previous_slug}}
|
||||
Content-Type: application/json
|
6
test/restclient/rice/04_delete_rice.http
Normal file
6
test/restclient/rice/04_delete_rice.http
Normal file
|
@ -0,0 +1,6 @@
|
|||
@baseUrl = http://localhost:3000
|
||||
@previous_slug = cool-zenrice-test-base-1d576eeb-de28-4df8-a12f-bcfc8f0e9b6e
|
||||
@previous_token = 00472a9f-8a8c-423d-b4a5-7137c4cc13f6
|
||||
|
||||
DELETE {{baseUrl}}/rices/{{previous_slug}}
|
||||
x-zen-shared-token: {{previous_token}}
|
30
test/restclient/spaces/01_create_space.http
Normal file
30
test/restclient/spaces/01_create_space.http
Normal file
|
@ -0,0 +1,30 @@
|
|||
@baseUrl = http://localhost:3000
|
||||
|
||||
POST {{baseUrl}}/spaces
|
||||
Content-Type: application/json
|
||||
x-zen-shared-name: cool-zenrice-test-base
|
||||
x-zen-shared-author: jhon@doe.com
|
||||
User-Agent: ZenBrowser/1.2b.0 (EndeavourOS x86_64)
|
||||
|
||||
{
|
||||
"space_id": "UNIQUE-IDENTIFIER",
|
||||
"owner_id": "USER_OR_TEAM_ID",
|
||||
"name": "Project X Workspace",
|
||||
"description": "Workspace for Project X collaboration",
|
||||
"tabs": [
|
||||
{
|
||||
"url": "https://example.com",
|
||||
"title": "Example Site",
|
||||
"pinned": true,
|
||||
"archived": false,
|
||||
"metadata": {
|
||||
"last_accessed": "2024-02-20T14:30:00Z",
|
||||
"preview_image": "base64_thumbnail"
|
||||
}
|
||||
}
|
||||
],
|
||||
"permissions": {
|
||||
"public_sharing": false,
|
||||
"edit_restrictions": "owner_only"
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue