Skip to content

Commit

Permalink
remove image file when uploaded successfully
Browse files Browse the repository at this point in the history
  • Loading branch information
mohit2152sharma committed Dec 20, 2024
1 parent 05f9da4 commit c4b0d1a
Show file tree
Hide file tree
Showing 7 changed files with 100 additions and 18 deletions.
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();
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 {
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`;
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();
});
});

0 comments on commit c4b0d1a

Please sign in to comment.