Skip to content

feat: fourthwall update command #265

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

Merged
merged 1 commit into from
Apr 26, 2025
Merged
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
30 changes: 30 additions & 0 deletions src/discord/commands.ts
Original file line number Diff line number Diff line change
@@ -422,4 +422,34 @@ export class DiscordCommands {

return interaction.reply({ content: `Successfully removed ${url}.`, flags: [MessageFlags.SuppressEmbeds] });
}

@Slash({ name: 'fourthwall', description: 'Fourthwall related commmands' })
async handleFourthwall(
@SlashChoice('update')
@SlashOption({
name: 'action',
description: 'update: Updates Fourthwall orders',
required: true,
type: ApplicationCommandOptionType.String,
})
action: 'update',
@SlashOption({
name: 'id',
description: 'Id of a specific order to update',
type: ApplicationCommandOptionType.String,
required: false,
})
id: string | null,
interaction: CommandInteraction,
) {
switch (action) {
case 'update': {
await this.service.updateFourthwallOrders(id);
return interaction.reply({
content: 'Successfully updated Fourthwall orders',
flags: [MessageFlags.Ephemeral],
});
}
}
}
}
1 change: 1 addition & 0 deletions src/interfaces/database.interface.ts
Original file line number Diff line number Diff line change
@@ -132,6 +132,7 @@ export interface IDatabaseRepository {
createFourthwallOrder(entity: NewFourthwallOrder): Promise<void>;
updateFourthwallOrder(entity: UpdateFourthwallOrder): Promise<void>;
getTotalFourthwallOrders(options?: ReportOptions): Promise<{ revenue: number; profit: number }>;
streamFourthwallOrders(): AsyncIterableIterator<{ id: string }>;
createRSSFeed(entity: NewRSSFeed): Promise<void>;
getRSSFeeds(channelId?: string): Promise<RSSFeed[]>;
removeRSSFeed(url: string, channelId: string): Promise<void>;
4 changes: 4 additions & 0 deletions src/repositories/database.repository.ts
Original file line number Diff line number Diff line change
@@ -196,6 +196,10 @@ export class DatabaseRepository implements IDatabaseRepository {
return { revenue: Number(revenue) || 0, profit: Number(profit) || 0 };
}

streamFourthwallOrders() {
return this.db.selectFrom('fourthwall_orders').select('id').stream();
}

async createRSSFeed(entity: NewRSSFeed): Promise<void> {
await this.db.insertInto('rss_feeds').values(entity).execute();
}
10 changes: 9 additions & 1 deletion src/services/discord.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { IDatabaseRepository } from 'src/interfaces/database.interface';
import { IDiscordInterface } from 'src/interfaces/discord.interface';
import { IFourthwallRepository } from 'src/interfaces/fourthwall.interface';
import { IGithubInterface } from 'src/interfaces/github.interface';
import { IOutlineInterface } from 'src/interfaces/outline.interface';
import { DiscordService } from 'src/services/discord.service';
@@ -48,28 +49,35 @@ const newDatabaseMockRepository = (): Mocked<IDatabaseRepository> => ({
updateDiscordMessage: vitest.fn(),
createFourthwallOrder: vitest.fn(),
getTotalFourthwallOrders: vitest.fn(),
streamFourthwallOrders: vitest.fn(),
updateFourthwallOrder: vitest.fn(),
createRSSFeed: vitest.fn(),
getRSSFeeds: vitest.fn(),
updateRSSFeed: vitest.fn(),
removeRSSFeed: vitest.fn(),
});

const newFourthwallMockRepository = (): Mocked<IFourthwallRepository> => ({
getOrder: vitest.fn(),
});

describe('Bot test', () => {
let sut: DiscordService;

let discordMock: Mocked<IDiscordInterface>;
let fourthwallMock: Mocked<IFourthwallRepository>;
let githubMock: Mocked<IGithubInterface>;
let outlineMock: Mocked<IOutlineInterface>;
let databaseMock: Mocked<IDatabaseRepository>;

beforeEach(() => {
discordMock = newDiscordMockRepository();
fourthwallMock = newFourthwallMockRepository();
githubMock = newGithubMockRepository();
outlineMock = newOutlineMockRepository();
databaseMock = newDatabaseMockRepository();

sut = new DiscordService(databaseMock, discordMock, githubMock, outlineMock);
sut = new DiscordService(databaseMock, discordMock, fourthwallMock, githubMock, outlineMock);
});

it('should work', () => {
31 changes: 31 additions & 0 deletions src/services/discord.service.ts
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@ import { IDatabaseRepository } from 'src/interfaces/database.interface';
import { DiscordChannel, IDiscordInterface } from 'src/interfaces/discord.interface';
import { IGithubInterface } from 'src/interfaces/github.interface';
import { IOutlineInterface } from 'src/interfaces/outline.interface';
import { FourthwallRepository } from 'src/repositories/fourthwall.repository';
import { formatCommand, logError, shorten } from 'src/util';

const PREVIEW_BLACKLIST = [Constants.Urls.Immich, Constants.Urls.GitHub, Constants.Urls.MyImmich];
@@ -58,6 +59,7 @@ export class DiscordService {
constructor(
@Inject(IDatabaseRepository) private database: IDatabaseRepository,
@Inject(IDiscordInterface) private discord: IDiscordInterface,
@Inject(FourthwallRepository) private fourthwall: FourthwallRepository,
@Inject(IGithubInterface) private github: IGithubInterface,
@Inject(IOutlineInterface) private outline: IOutlineInterface,
) {}
@@ -464,4 +466,33 @@ export class DiscordService {
}
}
}

async updateFourthwallOrders(id?: string | null) {
const {
fourthwall: { user, password },
} = getConfig();

if (id) {
await this.updateOrder({ id, user, password });
return;
}

for await (const { id } of this.database.streamFourthwallOrders()) {
await this.updateOrder({ id, user, password });
}
}

private async updateOrder({ id, user, password }: { id: string; user: string; password: string }) {
const order = await this.fourthwall.getOrder({ id, user, password });

await this.database.updateFourthwallOrder({
id,
discount: order.discount,
status: order.status,
total: order.totalPrice.value,
profit: order.profit.value,
shipping: order.currentAmounts.shipping.value,
tax: order.currentAmounts.tax.value,
});
}
}
11 changes: 10 additions & 1 deletion src/services/webhook.service.ts
Original file line number Diff line number Diff line change
@@ -201,12 +201,21 @@ export class WebhookService {
}

async onFourthwallOrder(dto: FourthwallOrderCreateWebhook | FourthwallOrderUpdateWebhook, slug: string) {
const { slugs, fourthwall } = getConfig();
const { slugs } = getConfig();
if (!slugs.fourthwallWebhook || slug !== slugs.fourthwallWebhook) {
throw new UnauthorizedException();
}

void this.handleFourthwallOrder(dto);
}

private async handleFourthwallOrder(dto: FourthwallOrderCreateWebhook | FourthwallOrderUpdateWebhook) {
const { fourthwall } = getConfig();

const dtoOrder = dto.type === 'ORDER_PLACED' ? dto.data : dto.data.order;

await new Promise((resolve) => setTimeout(resolve, 10_000));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this'll block the webhook which might make things unhappy? Idk if it's allowed in node but it might be better to send off a promise that does the whole write, and return on the webhook immediately.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh that's a good call!


let order = await this.fourthwall.getOrder({
id: dtoOrder.id,
user: fourthwall.user,