Skip to content

Commit

Permalink
Trade: add server-side-storage
Browse files Browse the repository at this point in the history
  • Loading branch information
enricoros committed Oct 17, 2023
1 parent 45cfb14 commit 49755ab
Show file tree
Hide file tree
Showing 3 changed files with 246 additions and 20 deletions.
18 changes: 18 additions & 0 deletions src/apps/chat/trade/server/publish.pastegg.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,24 @@
import { z } from 'zod';

import { fetchJsonOrTRPCError } from '~/server/api/trpc.serverutils';


export const publishToInputSchema = z.object({
to: z.enum(['paste.gg']),
title: z.string(),
fileContent: z.string(),
fileName: z.string(),
origin: z.string(),
});

export const publishToOutputSchema = z.object({
url: z.string(),
expires: z.string(),
deletionKey: z.string(),
created: z.string(),
});


/**
* Post a paste to paste.gg
* [called by the API]
Expand Down
207 changes: 207 additions & 0 deletions src/apps/chat/trade/server/share.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
import { z } from 'zod';

import { SharingDataType } from '@prisma/client';

import { db } from '~/server/db';
import { publicProcedure } from '~/server/api/trpc.server';
import { v4 as uuidv4 } from 'uuid';


/// Zod schemas

const dataTypesSchema = z.enum([SharingDataType.CHAT_V1]);
const dataSchema = z.object({}).nonstrict();


const sharePutInputSchema = z.object({
ownerId: z.string().optional(),
dataType: dataTypesSchema,
dataObject: dataSchema,
expiresSeconds: z.number().optional(),
});

export const sharePutOutputSchema = z.union([
z.object({
type: z.literal('success'),
sharedId: z.string(),
ownerId: z.string(),
expiresAt: z.date().nullable(),
deletionKey: z.string(),
createdAt: z.date(),
}),
z.object({
type: z.literal('error'),
error: z.string(),
}),
]);

const shareGetInputSchema = z.object({
sharedId: z.string(),
});

export const shareGetOutputSchema = z.union([
z.object({
type: z.literal('success'),
dataType: dataTypesSchema,
dataObject: dataSchema,
expiresAt: z.date().nullable(),
}),
z.object({
type: z.literal('error'),
error: z.string(),
}),
]);

const shareDeleteInputSchema = z.object({
sharedId: z.string(),
deletionKey: z.string(),
});

export const shareDeleteOutputSchema = z.object({
type: z.enum(['success', 'error']),
error: z.string().optional(),
});


/// tRPC procedures

/**
* Writes dataObject to DB, returns sharedId / ownerId
*/
export const sharePutProcedure =
publicProcedure
.input(sharePutInputSchema)
.output(sharePutOutputSchema)
.mutation(async ({ input: { ownerId, dataType, dataObject, expiresSeconds } }) => {

// expire in 30 days, if unspecified
if (!expiresSeconds)
expiresSeconds = 60 * 60 * 24 * 30;

const dataSizeEstimate = JSON.stringify(dataObject).length;

const { id: sharedId, ...rest } = await db.sharing.create({
select: {
id: true,
ownerId: true,
createdAt: true,
expiresAt: true,
deletionKey: true,
},
data: {
ownerId: ownerId || uuidv4(),
isPublic: true,
dataType,
dataSize: dataSizeEstimate,
data: dataObject,
expiresAt: new Date(Date.now() + 1000 * expiresSeconds),
deletionKey: uuidv4(),
isDeleted: false,
},
});

return {
type: 'success',
sharedId,
...rest,
};

});


/**
* Read a public object from DB, if it exists, and is not expired, and is not deleted
*/
export const shareGetProducedure =
publicProcedure
.input(shareGetInputSchema)
.output(shareGetOutputSchema)
.query(async ({ input: { sharedId } }) => {

// read object
const result = await db.sharing.findUnique({
select: {
dataType: true,
data: true,
expiresAt: true,
},
where: {
id: sharedId,
isPublic: true,
isDeleted: false,
OR: [
{ expiresAt: null },
{ expiresAt: { gt: new Date() } },
],
},
});

// if not found, return error
if (!result)
return {
type: 'error',
error: 'Not found',
};

if (typeof result.data !== 'object' || result.data === null)
return {
type: 'error',
error: 'Invalid data',
};

// increment the read count
// NOTE: fire-and-forget; we don't care about the result
{
db.sharing.update({
select: {
id: true,
},
where: {
id: sharedId,
},
data: {
readCount: {
increment: 1,
},
},
}).catch(() => null);
}

return {
type: 'success',
dataType: result.dataType,
dataObject: result.data as any,
expiresAt: result.expiresAt,
};

});


/**
* Mark a public object as deleted, if it exists, and is not expired, and is not deleted
*/
export const shareDeleteProcedure =
publicProcedure
.input(shareDeleteInputSchema)
.output(shareDeleteOutputSchema)
.mutation(async ({ input: { sharedId, deletionKey } }) => {

const result = await db.sharing.updateMany({
where: {
id: sharedId,
deletionKey,
isDeleted: false,
},
data: {
isDeleted: true,
deletedAt: new Date(),
},
});

const success = result.count === 1;

return {
type: success ? 'success' : 'error',
error: success ? undefined : 'Not found',
};
});
41 changes: 21 additions & 20 deletions src/apps/chat/trade/server/trade.router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,13 @@ import { createTRPCRouter, publicProcedure } from '~/server/api/trpc.server';
import { fetchTextOrTRPCError } from '~/server/api/trpc.serverutils';

import { chatGptImportConversation, chatGptSharedChatSchema } from './import.chatgpt';
import { postToPasteGGOrThrow } from './publish.pastegg';
import { postToPasteGGOrThrow, publishToInputSchema, publishToOutputSchema } from './publish.pastegg';
import { shareDeleteOutputSchema, shareDeleteProcedure, shareGetProducedure, sharePutOutputSchema, sharePutProcedure } from './share.server';


const chatGptImportInputSchema = z.object({
url: z.string().url().startsWith('https://chat.openai.com/share/'),
});
export type SharePutSchema = z.infer<typeof sharePutOutputSchema>;

const publishToInputSchema = z.object({
to: z.enum(['paste.gg']),
title: z.string(),
fileContent: z.string(),
fileName: z.string(),
origin: z.string(),
});

const publishToOutputSchema = z.object({
url: z.string(),
expires: z.string(),
deletionKey: z.string(),
created: z.string(),
});
export type ShareDeleteSchema = z.infer<typeof shareDeleteOutputSchema>;

export type PublishedSchema = z.infer<typeof publishToOutputSchema>;

Expand All @@ -36,7 +22,7 @@ export const tradeRouter = createTRPCRouter({
* ChatGPT Shared Chats Importer
*/
importChatGptShare: publicProcedure
.input(chatGptImportInputSchema)
.input(z.object({ url: z.string().url().startsWith('https://chat.openai.com/share/') }))
.output(z.object({ data: chatGptSharedChatSchema, conversationId: z.string() }))
.query(async ({ input: { url } }) => {
const htmlPage = await fetchTextOrTRPCError(url, 'GET', {}, undefined, 'ChatGPT Importer');
Expand All @@ -48,7 +34,22 @@ export const tradeRouter = createTRPCRouter({
}),

/**
* Publish a file (with title, content, name) to a sharing service
* Experimental: 'Sharing functionality': server-side storage
*/
sharePut: sharePutProcedure,

/**
* This function will read the shared data by ID, but only if not deleted or expired
*/
shareGet: shareGetProducedure,

/**
* This function will delete the shared data by ID, but only if not deleted or expired
*/
shareDelete: shareDeleteProcedure,

/**
* Publish a text file (with title, content, name) to a sharing service
* For now only 'paste.gg' is supported
*/
publishTo: publicProcedure
Expand Down

0 comments on commit 49755ab

Please sign in to comment.