Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

remove image file when uploaded successfully #13

Merged
merged 3 commits into from
Dec 20, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions database/applied/prod/post.sql
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,11 @@ CREATE TABLE posts (
created_by UUID NOT NULL,
CONSTRAINT fk_created_by FOREIGN KEY (created_by) REFERENCES users (id) ON DELETE SET NULL
);

ALTER TABLE posts ADD COLUMN updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL;

-- Create the trigger that calls the above function before any UPDATE
CREATE TRIGGER trigger_set_updated_at
BEFORE UPDATE ON posts
FOR EACH ROW
EXECUTE FUNCTION set_updated_at();
mohit2152sharma marked this conversation as resolved.
Show resolved Hide resolved
24 changes: 23 additions & 1 deletion src/lib/lib-utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { RequestEvent } from '@sveltejs/kit';
import { Logger } from './logger';
import fs from 'fs/promises';

type FetchMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
const logger = new Logger();
Expand Down Expand Up @@ -58,4 +59,25 @@ async function retryFetch(
return lastResponse || new Response(null, { status: 500, statusText: 'Fetch failed' });
}

export { onlyOneParam, retryFetch, redirectToLogin };
async function removeFile(filePath: string): Promise<void> {
try {
logger.info(`Removing file ${filePath}`);
await fs.unlink(filePath);
} catch (e: unknown) {
if (isNodeError(e)) {
if (e.code === 'ENOENT') {
logger.error(`File ${filePath} does not exist`);
} else {
logger.error(`Error while removing file ${filePath}: ${e.message}`);
}
} else {
logger.error(`Error while removing file ${filePath}: ${e}`);
}
}
}

function isNodeError(e: unknown): e is NodeJS.ErrnoException {
return e instanceof Error && 'code' in e;
}

export { onlyOneParam, retryFetch, redirectToLogin, removeFile };
4 changes: 4 additions & 0 deletions src/lib/server/bsky/code-images.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import { codeToHtml } from 'shiki';
import puppeteer from 'puppeteer';
import { onlyOneParam } from '$lib/lib-utils';
import { getHtmlString } from './code-template';
import { Logger } from '$lib/logger';

const logger = new Logger();

interface CodeSnippet {
language?: string;
Expand Down Expand Up @@ -90,6 +93,7 @@ async function htmlToImage(html: string, outputPath: string): Promise<ImagePrope
type: 'png',
captureBeyondViewport: true
});
logger.info(`Image saved to ${outputPath}`);
}
}

Expand Down
29 changes: 17 additions & 12 deletions src/lib/server/bsky/posts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { BskyAgent, RichText } from '@atproto/api';
import { type Post, createThread } from './threader';
import fs from 'fs';
import type { CodeImage } from './code-images';
import { retryFetch } from '$lib/lib-utils';
import { removeFile, retryFetch } from '$lib/lib-utils';
import { Logger } from '$lib/logger';

const logger = new Logger();
Expand Down Expand Up @@ -68,15 +68,21 @@ async function uploadImages(
): Promise<Array<ImageBlobType>> {
const blobs: Array<ImageBlobType> = [];
for (const image of images) {
const blob = await bskyUploadImage(
pdsUrl,
image.imageProperties.outputPath,
session,
'image/png',
retry,
retryLimit
);
blobs.push(blob);
try {
const blob = await bskyUploadImage(
pdsUrl,
image.imageProperties.outputPath,
session,
'image/png',
retry,
retryLimit
);
blobs.push(blob);
} catch (error) {
logger.error(`Failed to upload image: ${error}`);
} finally {
mohit2152sharma marked this conversation as resolved.
Show resolved Hide resolved
await removeFile(image.imageProperties.outputPath);
}
}
return blobs;
}
Expand All @@ -102,11 +108,11 @@ async function createEmbed(images: Array<CodeImage>, blobs: Array<ImageBlobType>
return embed;
}

// TODO: Do proper error handling to propagate to the end user if upload fails
async function createBskyPost(
agent: BskyAgent,
pdsUrl: string = PDS_URL,
session: SessionData,
// text: string,
post: Post,
reply?: BskyReply
): Promise<BskyPostResponse> {
Expand Down Expand Up @@ -168,7 +174,6 @@ async function bskyThreads(
}
try {
const agent = new BskyAgent({ service: pdsURL });
// const text0 = texts?.shift() as string;
const post0 = posts?.shift() as Post;
const bskyPost0 = await createBskyPost(agent, pdsURL, session, post0);
let reply: BskyReply = {
Expand Down
3 changes: 2 additions & 1 deletion src/lib/server/bsky/threader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ async function createThread(
const title = `image_${index + 1}`;
// FIXME: What if there are two codesnippet exactly same
text = text.replace(snippet.codeSnippet, `[${title}]`);
const imageName = `output_${index + 1}.png`;
const imageId = crypto.randomUUID();
const imageName = `code-image-${imageId}.png`;
mohit2152sharma marked this conversation as resolved.
Show resolved Hide resolved
const imagePath = path.join(os.tmpdir(), imageName);
const codeImage = await renderCodeSnippet(snippet, imagePath, { imageTitle: title });
codeImages.push(codeImage);
Expand Down
8 changes: 4 additions & 4 deletions src/lib/server/db/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { drizzle } from 'drizzle-orm/postgres-js'
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';
import { env } from '$env/dynamic/private';

Expand Down Expand Up @@ -32,8 +32,8 @@ export function getDbConfig(
const _username = username || (checkEnvParam('DATABASE_USERNAME', true) as string);
const _password = password || (checkEnvParam('DATABASE_PASSWORD', true) as string);
const _host = host || (checkEnvParam('DATABASE_HOST', true) as string);
const _port = port || parseInt(checkEnvParam('DATABASE_PORT', true) as string)
const _dbName = dbName || checkEnvParam('DATABASE_NAME', true) as string;
const _port = port || parseInt(checkEnvParam('DATABASE_PORT', true) as string);
const _dbName = dbName || (checkEnvParam('DATABASE_NAME', true) as string);
return { username: _username, password: _password, host: _host, port: _port, dbName: _dbName };
}

Expand All @@ -51,5 +51,5 @@ if (env.MY_ENV === 'development' || env.MY_ENV === 'test' || env.MY_ENV == 'dev'
throw new Error(`Environment not provided or unknown environment: ${env.MY_ENV}`);
}

const client = postgres(databaseUrl)
const client = postgres(databaseUrl);
export const db = drizzle(client);
42 changes: 42 additions & 0 deletions tests/utils/remove-file.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { removeFile } from '../../src/lib/lib-utils'; // Adjust the import path as necessary
import fs from 'fs';
import path from 'path';
import { vi, expect, test, beforeAll, afterAll, describe } from 'vitest';

describe('removeFile', () => {
const testFilePath = path.join(__dirname, 'test-file.txt');

beforeAll(() => {
// Create a test file to remove
fs.writeFileSync(testFilePath, 'This is a test file.');
});

afterAll(() => {
// Clean up: remove the test file if it exists
if (fs.existsSync(testFilePath)) {
fs.unlinkSync(testFilePath);
}
});

test('should remove an existing file', async () => {
await removeFile(testFilePath);
expect(fs.existsSync(testFilePath)).toBe(false);
});

test('should handle removing a non-existent file', async () => {
const nonExistentFilePath = path.join(__dirname, 'non-existent-file.txt');
const result = await removeFile(nonExistentFilePath);
expect(result).toBeUndefined();
});

test('it logs an error if file does not exist', async () => {
const nonExistentFilePath = path.join(__dirname, 'non-existent-file.txt');
const spy = vi.spyOn(console, 'error');
await removeFile(nonExistentFilePath);
expect(spy).toHaveBeenCalledOnce();
expect(spy).toHaveBeenCalledWith(
expect.stringContaining(`File ${nonExistentFilePath} does not exist`)
);
spy.mockRestore();
});
});
Loading