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

feat(cache): add migration system #100

Merged
merged 2 commits into from
Oct 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
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
47 changes: 43 additions & 4 deletions src/configuration/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Counter from "@pm2/io/build/main/utils/metrics/counter.js";
import Gauge from "@pm2/io/build/main/utils/metrics/gauge.js";
import { Scraper } from "@the-convocation/twitter-scraper";
import { createRestAPIClient, mastodon } from "masto";
import ora from "ora";

import {
BLUESKY_IDENTIFIER,
Expand All @@ -18,7 +19,9 @@ import {
} from "../constants.js";
import { handleTwitterAuth } from "../helpers/auth/auth.js";
import { createCacheFile, getCache } from "../helpers/cache/index.js";
import { runMigrations } from "../helpers/cache/run-migrations.js";
import { TouitomamoutError } from "../helpers/error.js";
import { oraPrefixer } from "../helpers/logs/ora-prefixer.js";
import { buildConfigurationRules } from "./build-configuration-rules.js";

export const configuration = async (): Promise<{
Expand All @@ -45,6 +48,7 @@ export const configuration = async (): Promise<{

// Init configuration
await createCacheFile();
await runMigrations();

const synchronizedPostsCountThisRun = pm2.counter({
name: "Synced posts this run",
Expand All @@ -69,22 +73,58 @@ export const configuration = async (): Promise<{

let mastodonClient = null;
if (SYNC_MASTODON) {
const log = ora({
color: "gray",
prefixText: oraPrefixer("🦣 client"),
}).start("connecting to mastodon...");

mastodonClient = createRestAPIClient({
url: `https://${MASTODON_INSTANCE}`,
accessToken: MASTODON_ACCESS_TOKEN,
});
console.log("🦣 client: ✔ connected");

await mastodonClient.v1.accounts
.verifyCredentials()
.then(() => log.succeed("connected"))
.catch(() => {
log.fail("authentication failure");
throw new Error(
TouitomamoutError(
"Touitomamout was unable to connect to mastodon with the given credentials",
["Please check your .env settings."],
),
);
});
}

let blueskyClient = null;
if (SYNC_BLUESKY) {
const log = ora({
color: "gray",
prefixText: oraPrefixer("☁️ client"),
}).start("connecting to bluesky...");

blueskyClient = new bsky.BskyAgent({
service: `https://${BLUESKY_INSTANCE}`,
});
await blueskyClient
.login({ identifier: BLUESKY_IDENTIFIER, password: BLUESKY_PASSWORD })
.login({
identifier: BLUESKY_IDENTIFIER,
password: BLUESKY_PASSWORD,
})
.then(() => {
log.succeed("connected");
})
.catch(({ error }) => {
log.fail("authentication failure");
switch (error) {
case ResponseTypeNames[ResponseType.AuthRequired]:
throw new Error(
TouitomamoutError(
"Touitomamout was unable to connect to bluesky with the given credentials",
["Please check your .env settings."],
),
);
case ResponseTypeNames[ResponseType.XRPCNotSupported]:
throw new Error(
TouitomamoutError(
Expand All @@ -103,10 +143,9 @@ export const configuration = async (): Promise<{
),
);
default:
console.log({ error });
console.log(error);
}
});
console.log("☁️ client: ✔ connected");
}

return {
Expand Down
16 changes: 11 additions & 5 deletions src/helpers/auth/auth.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { Scraper } from "@the-convocation/twitter-scraper";
import ora from "ora";

import { TWITTER_PASSWORD, TWITTER_USERNAME } from "../../constants.js";
import { getCookies } from "../cookies/get-cookies.js";
import { saveCookies } from "../cookies/save-cookies.js";
import { TouitomamoutError } from "../error.js";
import { oraPrefixer } from "../logs/ora-prefixer.js";

const restorePreviousSession = async (client: Scraper): Promise<void> => {
try {
Expand All @@ -23,23 +25,27 @@ const restorePreviousSession = async (client: Scraper): Promise<void> => {
};

export const handleTwitterAuth = async (client: Scraper) => {
const log = ora({
color: "gray",
prefixText: oraPrefixer("🦤 client"),
}).start("connecting to twitter...");

if (!TWITTER_USERNAME || !TWITTER_PASSWORD) {
console.log(
"🦤 client: ✔ connected as guest | replies will not be synced",
);
log.succeed("connected as guest | replies will not be synced");
return;
}

// Try to restore the previous session
await restorePreviousSession(client);

if (await client.isLoggedIn()) {
console.log("🦤 client: ✔ connected (session restored)");
log.succeed("connected (session restored)");
} else {
// Handle restoration failure
await client.login(TWITTER_USERNAME, TWITTER_PASSWORD);
console.log("🦤 client: ✔ connected (using credentials)");
log.succeed("connected (using credentials)");
}
log.stop();

// Save session
if (await client.isLoggedIn()) {
Expand Down
21 changes: 21 additions & 0 deletions src/helpers/cache/migrations/__tests__/cache-migration-0.0.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { migration } from "../cache-migration-0.0.js";

describe("cache-migration-0.0", () => {
it("should migration initial cache", async () => {
const cache = {};
const migrationResult = await migration(cache);
expect(migrationResult).toStrictEqual({
version: "0.0",
});
});

describe("when the cache has a version", () => {
it("should not run the migration", async () => {
const cache = {
version: "x.x",
};
const migrationResult = await migration(cache);
expect(migrationResult).toStrictEqual(cache);
});
});
});
9 changes: 9 additions & 0 deletions src/helpers/cache/migrations/cache-migration-0.0.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const migration = async (outdatedCache: NonNullable<unknown>) => {
if (Object.hasOwn(outdatedCache, "version")) {
return outdatedCache;
}
return {
...outdatedCache,
version: "0.0",
};
};
3 changes: 3 additions & 0 deletions src/helpers/cache/migrations/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { migration as cacheMigration_0_0 } from "./cache-migration-0.0.js";

export default [cacheMigration_0_0];
37 changes: 37 additions & 0 deletions src/helpers/cache/run-migrations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import fs from "fs";
import ora from "ora";

import { CACHE_PATH } from "../../constants.js";
import { oraPrefixer } from "../logs/ora-prefixer.js";
import { oraProgress } from "../logs/ora-progress.js";
import { getCompleteCache } from "./get-cache.js";
import migrations from "./migrations/index.js";

export const runMigrations = async () => {
const log = ora({
color: "gray",
prefixText: oraPrefixer("⚙️ cache"),
}).start();
oraProgress(log, `running migrations`, 0, migrations.length);

let migrationCounter = 0;
for (const migration of migrations) {
const outdatedCache = await getCompleteCache();
await migration(outdatedCache)
.then(async (updatedCache) => {
try {
await fs.promises.writeFile(CACHE_PATH, JSON.stringify(updatedCache));
} catch (err) {
log.fail("Error updating cache file:" + err);
}
})
.catch((err) => {
throw new Error(`Error running migration ${migration.name}: ${err}`);
});

migrationCounter++;
oraProgress(log, `running migrations`, migrationCounter, migrations.length);
}

log.succeed("task finished");
};
17 changes: 17 additions & 0 deletions src/helpers/logs/ora-progress.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Ora } from "ora";

const SEGMENT_DONE = "█";
const SEGMENT_UNDONE = "░";
export const oraProgress = (
ora: Ora,
text: string,
index: number,
maximum: number,
) => {
const progress = Math.round((index / maximum) * 100);
const segments = Math.round(progress / 5);
const bar = `${SEGMENT_DONE.repeat(segments)}${SEGMENT_UNDONE.repeat(
20 - segments,
)}`;
ora.text = `${bar} ${progress}% ${text}`;
};