feat: GIF search (Klipy), stickers, emoji picker, and bug fixes
- Add GIF search powered by Klipy API with correct response mapping (file.sm/hd tiers, not flat files structure) - Add sticker system: packs, upload with auto-downscale, send in messages - Add tabbed InputPopover with emoji, GIF, and sticker pickers - Fix GIF API key migration race condition (column-add loop vs rename) - Fix masked API key corruption on settings save (server + client guards) - Fix sticker packs 403 (reversed isMember parameter order) - Fix emoji picker not filling popover width (perLine 8→9, CSS 100%) - Add error logging for Klipy API failures
This commit is contained in:
@@ -70,6 +70,7 @@ export const messages = sqliteTable('messages', {
|
||||
userId: text('user_id').notNull().references(() => users.id),
|
||||
replyToId: text('reply_to_id'),
|
||||
content: text('content'),
|
||||
stickerId: text('sticker_id'),
|
||||
editedAt: integer('edited_at'),
|
||||
createdAt: integer('created_at').notNull(),
|
||||
}, (table) => ({
|
||||
@@ -112,6 +113,7 @@ export const dmMessages = sqliteTable('dm_messages', {
|
||||
userId: text('user_id').notNull().references(() => users.id),
|
||||
replyToId: text('reply_to_id'),
|
||||
content: text('content'),
|
||||
stickerId: text('sticker_id'),
|
||||
editedAt: integer('edited_at'),
|
||||
createdAt: integer('created_at').notNull(),
|
||||
}, (table) => ({
|
||||
@@ -213,6 +215,30 @@ export const userSpaceLayout = sqliteTable('user_space_layout', {
|
||||
updatedAt: integer('updated_at').notNull(),
|
||||
});
|
||||
|
||||
export const stickerPacks = sqliteTable('sticker_packs', {
|
||||
id: text('id').primaryKey(),
|
||||
spaceId: text('space_id').notNull().references(() => spaces.id, { onDelete: 'cascade' }),
|
||||
name: text('name').notNull(),
|
||||
description: text('description'),
|
||||
createdBy: text('created_by').notNull().references(() => users.id),
|
||||
createdAt: integer('created_at').notNull(),
|
||||
});
|
||||
|
||||
export const stickers = sqliteTable('stickers', {
|
||||
id: text('id').primaryKey(),
|
||||
packId: text('pack_id').notNull().references(() => stickerPacks.id, { onDelete: 'cascade' }),
|
||||
spaceId: text('space_id').notNull().references(() => spaces.id, { onDelete: 'cascade' }),
|
||||
name: text('name').notNull(),
|
||||
tags: text('tags').default(''),
|
||||
filename: text('filename').notNull(),
|
||||
mimetype: text('mimetype').notNull(),
|
||||
size: integer('size').notNull(),
|
||||
width: integer('width'),
|
||||
height: integer('height'),
|
||||
uploadedBy: text('uploaded_by').notNull().references(() => users.id),
|
||||
createdAt: integer('created_at').notNull(),
|
||||
});
|
||||
|
||||
export const instanceSettings = sqliteTable('instance_settings', {
|
||||
id: integer('id').primaryKey().default(1),
|
||||
instanceName: text('instance_name').default('Backspace'),
|
||||
@@ -226,6 +252,7 @@ export const instanceSettings = sqliteTable('instance_settings', {
|
||||
maxResolution: integer('max_resolution').notNull().default(1080),
|
||||
maxFramerate: integer('max_framerate').notNull().default(60),
|
||||
registrationOpen: integer('registration_open'), // null = use env var default, 0/1 = explicit
|
||||
gifApiKey: text('gif_api_key'),
|
||||
updatedAt: integer('updated_at').notNull(),
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user