diff --git a/Makefile b/Makefile index cdceab9..035d6e2 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,10 @@ .PHONY: migrate tern + +migrate: $(shell find migrations -type f -name "*.sql") + $(MAKE) tern ARGS="migrate" + tern: @echo "running tern $(ARGS)" @cd migrations && PGUSER=postgres PGPASSWORD=postgres PGHOST=127.0.0.1 PGPORT=54327 PGDATABASE=postgres tern $(ARGS) - -migrate: $(shell find migrations -type f -name "*.sql") - $(MAKE) tern ARGS="migrate" diff --git a/migrations/003_wynn_items.sql b/migrations/003_wynn_items.sql new file mode 100644 index 0000000..e4763fc --- /dev/null +++ b/migrations/003_wynn_items.sql @@ -0,0 +1,24 @@ +-- Write your migrate up statements here + +create table wynn.items ( + name text not null primary key, + display_name text not null, + type text not null, + hash text not null, + data jsonb not null +); + +create schema if not exists meta; + +create table meta.hashes ( + key text not null primary key, + value text not null +); + +---- create above / drop below ---- + +drop table if exists wynn.items; +drop schema if exists meta cascade; + +-- Write your migrate down statements here. If this migration is irreversible +-- Then delete the separator line above. diff --git a/migrations/yarn.lock b/migrations/yarn.lock new file mode 100644 index 0000000..f3438ff --- /dev/null +++ b/migrations/yarn.lock @@ -0,0 +1,160 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" + integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + +call-bind@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.8.tgz#0736a9660f537e3388826f440d5ec45f744eaa4c" + integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww== + dependencies: + call-bind-apply-helpers "^1.0.0" + es-define-property "^1.0.0" + get-intrinsic "^1.2.4" + set-function-length "^1.2.2" + +call-bound@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.3.tgz#41cfd032b593e39176a71533ab4f384aa04fd681" + integrity sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA== + dependencies: + call-bind-apply-helpers "^1.0.1" + get-intrinsic "^1.2.6" + +define-data-property@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" + integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + gopd "^1.0.1" + +dunder-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" + integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== + dependencies: + call-bind-apply-helpers "^1.0.1" + es-errors "^1.3.0" + gopd "^1.2.0" + +es-define-property@^1.0.0, es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" + integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== + dependencies: + es-errors "^1.3.0" + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +get-intrinsic@^1.2.4, get-intrinsic@^1.2.6: + version "1.3.0" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" + integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== + dependencies: + call-bind-apply-helpers "^1.0.2" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + function-bind "^1.1.2" + get-proto "^1.0.1" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + math-intrinsics "^1.1.0" + +get-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== + dependencies: + dunder-proto "^1.0.1" + es-object-atoms "^1.0.0" + +gopd@^1.0.1, gopd@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== + +has-property-descriptors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" + integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== + dependencies: + es-define-property "^1.0.0" + +has-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== + +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +isarray@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" + integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== + +json-stable-stringify@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.2.1.tgz#addb683c2b78014d0b78d704c2fcbdf0695a60e2" + integrity sha512-Lp6HbbBgosLmJbjx0pBLbgvx68FaFU1sdkmBuckmhhJ88kL13OA51CDtR2yJB50eCNMH9wRqtQNNiAqQH4YXnA== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.3" + isarray "^2.0.5" + jsonify "^0.0.1" + object-keys "^1.1.1" + +jsonify@^0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.1.tgz#2aa3111dae3d34a0f151c63f3a45d995d9420978" + integrity sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg== + +math-intrinsics@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" + integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== + +object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +set-function-length@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" diff --git a/ts/.yarn/install-state.gz b/ts/.yarn/install-state.gz index 7c4da87..d99ea50 100644 Binary files a/ts/.yarn/install-state.gz and b/ts/.yarn/install-state.gz differ diff --git a/ts/barrelsby.json b/ts/barrelsby.json index dbf0dcd..b390548 100644 --- a/ts/barrelsby.json +++ b/ts/barrelsby.json @@ -2,8 +2,7 @@ "delete": true, "directory": [ "./src/activities", - "./src/workflows", - "./src/slashcommands" + "./src/workflows" ], "exclude": [ "types.ts" diff --git a/ts/src/activities/database.ts b/ts/src/activities/database.ts index 3c72821..c03a4c3 100644 --- a/ts/src/activities/database.ts +++ b/ts/src/activities/database.ts @@ -1,8 +1,11 @@ - +import stringify from 'json-stable-stringify'; import { c } from "#/di"; import { WApiV3ItemDatabase } from "#/lib/wynn/types"; import { WApi } from "#/lib/wynn/wapi"; +import { PG } from "#/services/pg"; import { ArkErrors } from "arktype"; +import { sha1Hash } from '#/lib/util/hashers'; +import { log } from '@temporalio/activity'; export async function update_wynn_items() { const api = await c.getAsync(WApi) @@ -14,7 +17,43 @@ export async function update_wynn_items() { if(parsed instanceof ArkErrors){ throw parsed } + const {sql} = await c.getAsync(PG) // iterate over all items with their names - for(const [name, item] of Object.entries(parsed)){ + const serializedData = stringify(parsed) + if(!serializedData){ + throw new Error('Failed to serialize wynn items') + } + const dataHash = sha1Hash(serializedData) + let found_new = false + await sql.begin(async (sql) => { + const [{currentHash} = {}] = await sql`select value as currentHash from meta.hashes where key = 'wynn.items' limit 1` + if(currentHash === dataHash) { + return + } + found_new = true + log.info("updating wynn with new hash") + for(const [displayName, item] of Object.entries(parsed)){ + const json = stringify(item) + if(!json){ + throw new Error('Failed to serialize wynn item') + } + const itemHash = sha1Hash(json) + // insert the items + await sql`insert into wynn.items(name, display_name, type, data, hash) values + (${item.internalName}, ${displayName}, ${item.type}, ${json}, ${itemHash}) + on conflict (name) do update set + display_name = EXCLUDED.display_name, + type = EXCLUDED.type, + data = EXCLUDED.data, + itemHash = EXCLUDED.itemHash` + // update the hash + await sql`insert into meta.hashes(key, value) values + ('wynn.items', ${dataHash}) + on conflict (key) do update set + value = EXCLUDED.value` + } + }) + return { + found_new, } } diff --git a/ts/src/activities/discord.ts b/ts/src/activities/discord.ts index 16c7e7f..f51199f 100644 --- a/ts/src/activities/discord.ts +++ b/ts/src/activities/discord.ts @@ -1,9 +1,9 @@ import { Bot } from "#/bot"; import {c} from "#/di" -import { InteractionResponse, InteractionResponseTypes, InteractionCallbackOptions, InteractionCallbackData, InteractionTypes, MessageFlags } from "discordeno"; +import { InteractionResponseTypes, InteractionCallbackOptions, InteractionCallbackData, InteractionTypes, MessageFlags } from "discordeno"; -interface InteractionRef { +export interface InteractionRef { id: string token: string type: InteractionTypes diff --git a/ts/src/activities/index.ts b/ts/src/activities/index.ts index 9159151..18acd15 100644 --- a/ts/src/activities/index.ts +++ b/ts/src/activities/index.ts @@ -3,6 +3,7 @@ */ export * from "./database"; +export * from "./discord"; export * from "./guild"; export * from "./leaderboards"; export * from "./players"; diff --git a/ts/src/cmd/worker.ts b/ts/src/cmd/worker.ts index a65bc60..1243205 100644 --- a/ts/src/cmd/worker.ts +++ b/ts/src/cmd/worker.ts @@ -22,7 +22,7 @@ const schedules: ScheduleOptions[] = [ action: { type: 'startWorkflow', workflowType: workflowSyncGuilds, - taskQueue: 'wynn-worker', + taskQueue: 'wynn-worker-ts', }, policies: { overlap: ScheduleOverlapPolicy.SKIP, @@ -38,7 +38,7 @@ const schedules: ScheduleOptions[] = [ action: { type: 'startWorkflow', workflowType: workflowSyncGuildLeaderboardInfo, - taskQueue: 'wynn-worker', + taskQueue: 'wynn-worker-ts', }, policies: { overlap: ScheduleOverlapPolicy.SKIP, @@ -54,7 +54,7 @@ const schedules: ScheduleOptions[] = [ action: { type: 'startWorkflow', workflowType: workflowSyncAllGuilds, - taskQueue: 'wynn-worker', + taskQueue: 'wynn-worker-ts', }, policies: { overlap: ScheduleOverlapPolicy.SKIP, @@ -70,7 +70,7 @@ const schedules: ScheduleOptions[] = [ action: { type: 'startWorkflow', workflowType: workflowSyncOnline, - taskQueue: 'wynn-worker', + taskQueue: 'wynn-worker-ts', }, policies: { overlap: ScheduleOverlapPolicy.SKIP, @@ -126,7 +126,7 @@ export class WorkerCommand extends Command { } return config; }}, - taskQueue: 'wynn-worker', + taskQueue: 'wynn-worker-ts', stickyQueueScheduleToStartTimeout: 5 * 1000, activities }); diff --git a/ts/src/workflows/index.ts b/ts/src/workflows/index.ts index 301b713..3dd55ff 100644 --- a/ts/src/workflows/index.ts +++ b/ts/src/workflows/index.ts @@ -3,4 +3,5 @@ */ export * from "./guilds"; +export * from "./items"; export * from "./players"; diff --git a/ts/src/workflows/items.ts b/ts/src/workflows/items.ts new file mode 100644 index 0000000..f5862c1 --- /dev/null +++ b/ts/src/workflows/items.ts @@ -0,0 +1,14 @@ + +import { proxyActivities } from '@temporalio/workflow'; +import type * as activities from '#/activities'; + +const { update_wynn_items } = proxyActivities({ + startToCloseTimeout: '1 minute', + retry: { + maximumAttempts: 1, + } +}); + +export const workflowSyncItemDatabase = async() => { + await update_wynn_items(); +}