change databases from surrealdb to pocketbase

This commit is contained in:
2025-06-26 01:17:45 +02:00
parent 18d7fe8caa
commit 274b49dd27
9 changed files with 89 additions and 112 deletions

View File

@@ -21,9 +21,5 @@ STREAMER_ID= # Twitch ID of the streaming user
CHATTER_ID= # Twitch ID of the chatting user CHATTER_ID= # Twitch ID of the chatting user
CHATTER_IS_STREAMER= # If the bot that activates on commands is on the same account as the streamer, set this to true. Make sure the STREAMER_ID and CHATTER_ID match in that case. CHATTER_IS_STREAMER= # If the bot that activates on commands is on the same account as the streamer, set this to true. Make sure the STREAMER_ID and CHATTER_ID match in that case.
# SurrealDB config # Pocketbase config
SURREAL_URL= # SurrealDB URL, can either be remotely hosted or selfhosted # POCKETBASE_URL= # Pocketbase URL. Defaults to localhost:8090
SURREAL_NAMESPACE= # SurrealDB Namespace. You need to create this manually
SURREAL_DATABASE= # SurrealDB Database. You need to create this manually
SURREAL_USERNAME= # SurrealDB username for authenticating
SURREAL_PASSWORD= # SurrealDB password for authenticating. Remember to escape characters like $ with a backslash

3
.gitignore vendored
View File

@@ -33,5 +33,6 @@ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# Finder (MacOS) folder config # Finder (MacOS) folder config
.DS_Store .DS_Store
# Redis/Valkey database # Redis/Valkey & Pocketbase databases
db/redis/ db/redis/
db/pocketbase/

View File

@@ -66,7 +66,7 @@ export async function createAuthProvider(user: string, intents: string[], stream
authData.onRefresh(async (user, token) => { authData.onRefresh(async (user, token) => {
console.info(`Successfully refreshed auth for user ${user}`); console.info(`Successfully refreshed auth for user ${user}`);
await updateAuthRecord(user, token.scope, token); await updateAuthRecord(user, token);
}); });
authData.onRefreshFailure((user, err) => { authData.onRefreshFailure((user, err) => {

View File

@@ -1,31 +1,27 @@
import Surreal from "surrealdb"; import type { AccessToken } from "@twurple/auth";
import PocketBase, { RecordService } from "pocketbase";
const surrealurl = process.env.SURREAL_URL ?? ""; const pocketbaseurl = process.env.POCKETBASE_URL ?? "localhost:8090";
if (surrealurl === "") { console.error("Please provide a SURREAL_URL in .env."); process.exit(1); }; if (pocketbaseurl === "") { console.error("Please provide a POCKETBASE_URL in .env."); process.exit(1); };
const namespace = process.env.SURREAL_NAMESPACE ?? "";
if (namespace === "") { console.error("Please provide a SURREAL_NAMESPACE in .env."); process.exit(1); };
const database = process.env.SURREAL_DATABASE ?? "";
if (database === "") { console.error("Please provide a SURREAL_DATABASE in .env."); process.exit(1); };
const username = process.env.SURREAL_USERNAME ?? "";
if (username === "") { console.error("Please provide a SURREAL_USERNAME in .env."); process.exit(1); };
const password = process.env.SURREAL_PASSWORD ?? "";
if (password === "") { console.error("Please provide a SURREAL_PASSWORD in .env."); process.exit(1); };
export default async function DB(): Promise<Surreal> { export type authRecord = {
const db = new Surreal(); id: string;
try { accesstoken: AccessToken;
await db.connect(surrealurl, {
auth: {
username,
password
}
});
await db.use({ namespace, database });
return db;
}
catch (err) {
console.error("Failed to connect to SurrealDB:", err instanceof Error ? err.message : String(err));
await db.close();
throw err;
};
}; };
export type userRecord = {
id: string;
username: string;
balance: number;
inventory: object;
lastlootbox: string;
};
type TypedPocketBase = {
collection(idOrName: 'auth'): RecordService<authRecord>;
collection(idOrName: 'users'): RecordService<userRecord>;
};
const pb = new PocketBase(pocketbaseurl) as TypedPocketBase;
export default pb;

View File

@@ -1,77 +1,38 @@
import type { AccessToken } from "@twurple/auth"; import type { AccessToken } from "@twurple/auth";
import DB from "./connection"; import pocketbase, { type authRecord } from "./connection";
import type { RecordId } from "surrealdb"; const pb = pocketbase.collection('auth');
type authRecord = {
accesstoken: AccessToken,
user: string,
};
export async function createAuthRecord(token: AccessToken, userId: string): Promise<void> {
const db = await DB();
if (!db) return;
export async function createAuthRecord(token: AccessToken, userId: string) {
try {
const data: authRecord = { const data: authRecord = {
accesstoken: token, accesstoken: token,
user: userId id: userId
};
try {
await db.create("auth", data);
} catch (err) {
console.error(err);
} finally {
await db.close();
}; };
await pb.create(data);
} catch (err) { };
}; };
type getAuthRecordQuery = authRecord & { id: RecordId }; export async function getAuthRecord(userId: string, requiredIntents: string[]) {
type getAuthRecordResult = { accesstoken: AccessToken, id: RecordId };
export async function getAuthRecord(userId: string, requiredIntents: string[]): Promise<getAuthRecordResult | undefined> {
const db = await DB();
if (!db) return undefined;
try { try {
const data = await db.query<getAuthRecordQuery[][]>("SELECT * from auth WHERE user=$userId AND accesstoken.scope CONTAINSALL $intents;", { userId, intents: requiredIntents }); const data = await pb.getOne(userId);
if (!data[0] || !data[0][0]) return undefined; if (!requiredIntents.every(intent => data.accesstoken.scope.includes(intent))) return undefined;
return { accesstoken: data[0][0].accesstoken, id: data[0][0].id }; return { accesstoken: data.accesstoken };
} catch (err) { } catch (err) {
console.error(err);
return undefined; return undefined;
} finally {
await db.close();
}; };
}; };
export async function updateAuthRecord(userId: string, intents: string[], newtoken: AccessToken): Promise<boolean> { export async function updateAuthRecord(userId: string, newtoken: AccessToken) {
const db = await DB();
if (!db) return false;
try { try {
const data = await getAuthRecord(userId, intents); const newrecord = {
const newrecord: authRecord = {
accesstoken: newtoken, accesstoken: newtoken,
user: userId
};
await db.update(data?.id!, newrecord);
return true;
} catch (err) {
console.error(err);
return false;
} finally {
await db.close();
}; };
await pb.update(userId, newrecord);
} catch (err) { };
}; };
export async function deleteAuthRecord(userId: string): Promise<void> { export async function deleteAuthRecord(userId: string): Promise<void> {
const db = await DB();
if (!db) return;
try { try {
const data = await db.query<getAuthRecordQuery[][]>("SELECT * FROM auth WHERE user=$userId;", { userId }); await pb.delete(userId);
if (!data[0] || !data[0][0]) return undefined; } catch (err) { };
for (const obj of data[0]) {
db.delete(obj.id);
};
} catch (err) {
console.error(err);
};
}; };

View File

@@ -6,7 +6,7 @@
"dependencies": { "dependencies": {
"@twurple/auth": "^7.3.0", "@twurple/auth": "^7.3.0",
"@twurple/eventsub-http": "^7.3.0", "@twurple/eventsub-http": "^7.3.0",
"surrealdb": "^1.3.2", "pocketbase": "^0.26.1",
}, },
"devDependencies": { "devDependencies": {
"@types/bun": "latest", "@types/bun": "latest",
@@ -75,12 +75,12 @@
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"isows": ["isows@1.0.7", "", { "peerDependencies": { "ws": "*" } }, "sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg=="],
"klona": ["klona@2.0.6", "", {}, "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA=="], "klona": ["klona@2.0.6", "", {}, "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA=="],
"node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
"pocketbase": ["pocketbase@0.26.1", "", {}, "sha512-fjcPDpxyqTZCwqGUTPUV7vssIsNMqHxk9GxbhxYHPEf18RqX2d9cpSqbbHk7aas30jqkgptuKfG7aY/Mytjj3g=="],
"retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="], "retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="],
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
@@ -89,8 +89,6 @@
"statuses": ["statuses@1.5.0", "", {}, "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA=="], "statuses": ["statuses@1.5.0", "", {}, "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA=="],
"surrealdb": ["surrealdb@1.3.2", "", { "dependencies": { "isows": "^1.0.6", "uuidv7": "^1.0.1" }, "peerDependencies": { "tslib": "^2.6.3", "typescript": "^5.0.0" } }, "sha512-mL7nij33iuon3IQP72F46fgX3p2LAxFCWCBDbZB7IohZ13RTEwJVNq7nZeP1eMSceQUpKzS6OHIWOuF9LYAkNw=="],
"toidentifier": ["toidentifier@1.0.0", "", {}, "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="], "toidentifier": ["toidentifier@1.0.0", "", {}, "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="],
"tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
@@ -103,14 +101,10 @@
"unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="], "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
"uuidv7": ["uuidv7@1.0.2", "", { "bin": { "uuidv7": "cli.js" } }, "sha512-8JQkH4ooXnm1JCIhqTMbtmdnYEn6oKukBxHn1Ic9878jMkL7daTI7anTExfY18VRCX7tcdn5quzvCb6EWrR8PA=="],
"webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
"whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], "whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
"ws": ["ws@8.18.2", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ=="],
"httpanda/@types/node": ["@types/node@14.18.63", "", {}, "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ=="], "httpanda/@types/node": ["@types/node@14.18.63", "", {}, "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ=="],
} }
} }

View File

@@ -9,3 +9,12 @@ services:
- ./db/redis:/data - ./db/redis:/data
environment: environment:
- VALKEY_EXTRA_FLAGS=--save 60 1 - VALKEY_EXTRA_FLAGS=--save 60 1
pocketbase:
container_name: qweribot-pocketbase
build:
context: ./pocketbase
ports:
- 8090:8090
restart: no
volumes:
- ./db/pocketbase:/pb/pb_data

View File

@@ -12,6 +12,6 @@
"dependencies": { "dependencies": {
"@twurple/auth": "^7.3.0", "@twurple/auth": "^7.3.0",
"@twurple/eventsub-http": "^7.3.0", "@twurple/eventsub-http": "^7.3.0",
"surrealdb": "^1.3.2" "pocketbase": "^0.26.1"
} }
} }

20
pocketbase/Dockerfile Normal file
View File

@@ -0,0 +1,20 @@
FROM alpine:latest
ARG PB_VERSION=0.28.4
ARG PB_ZIPNAME=pocketbase_${PB_VERSION}_linux_amd64.zip
RUN apk add --no-cache \
unzip \
ca-certificates
ADD https://github.com/pocketbase/pocketbase/releases/download/v${PB_VERSION}/${PB_ZIPNAME} /tmp/${PB_ZIPNAME}
ADD https://github.com/pocketbase/pocketbase/releases/download/v${PB_VERSION}/checksums.txt /tmp/checksums.txt
WORKDIR /tmp
RUN grep ${PB_ZIPNAME} checksums.txt | sha256sum -c
RUN unzip /tmp/${PB_ZIPNAME} -d /pb/
COPY ./pb_migrations /pb/pb_migrations
COPY ./pb_hooks /pb/pb_hooks
EXPOSE 8090
CMD ["/pb/pocketbase", "serve", "--http=0.0.0.0:8090"]