diff --git a/src/util/entities/Application.ts b/src/util/entities/Application.ts
index e00b4a988..014361bca 100644
--- a/src/util/entities/Application.ts
+++ b/src/util/entities/Application.ts
@@ -21,6 +21,22 @@ import { BaseClass } from "./BaseClass";
import { Team } from "./Team";
import { User } from "./User";
import { Guild } from "./Guild";
+import {
+ ApplicationApprovableConsoleType,
+ ApplicationCompanySchema,
+ ApplicationDiscoverabilityState,
+ ApplicationEventWebhooksStatus,
+ ApplicationEventWebhooksType,
+ ApplicationExecutableSchema,
+ ApplicationGuildRestriction,
+ ApplicationIntegrationTypeConfigurationSchema,
+ ApplicationInteractionsVersion,
+ ApplicationMonetizationState,
+ ApplicationSKUSchema,
+ ApplicationVerificationState,
+ RPCApplicationState,
+ StoreApplicationState,
+} from "@spacebar/schemas";
@Entity({
name: "applications",
@@ -45,9 +61,11 @@ export class Application extends BaseClass {
hook: boolean = true;
@Column()
+ // deprecated
bot_public?: boolean = true;
@Column()
+ // deprecated
bot_require_code_grant?: boolean = false;
@Column()
@@ -57,21 +75,21 @@ export class Application extends BaseClass {
@ManyToOne(() => User, { onDelete: "CASCADE" })
owner: User;
- // TODO: enum this? https://discord.com/developers/docs/resources/application#application-object-application-flags
@Column()
+ // ApplicationFlags bitfield
flags: number = 0;
@Column({ type: "simple-array", nullable: true })
redirect_uris: string[] = [];
- @Column({ nullable: true })
- rpc_application_state: number = 0;
+ @Column()
+ rpc_application_state: RPCApplicationState = RPCApplicationState.DISABLED;
- @Column({ nullable: true })
- store_application_state: number = 1;
+ @Column()
+ store_application_state: StoreApplicationState = StoreApplicationState.NONE;
- @Column({ nullable: true })
- verification_state: number = 1;
+ @Column()
+ verification_state: ApplicationVerificationState = ApplicationVerificationState.INELIGIBLE;
@Column({ nullable: true })
interactions_endpoint_url?: string;
@@ -83,10 +101,10 @@ export class Application extends BaseClass {
integration_require_code_grant: boolean = false;
@Column({ nullable: true })
- discoverability_state: number = 1;
+ discoverability_state: ApplicationDiscoverabilityState = ApplicationDiscoverabilityState.INELIGIBLE;
@Column({ nullable: true })
- discovery_eligibility_flags: number = 2240;
+ discovery_eligibility_flags: number = 0; // ApplicationDiscoveryEligibilityFlags bitfield
@JoinColumn({ name: "bot_user_id" })
@OneToOne(() => User, { onDelete: "CASCADE" })
@@ -118,16 +136,131 @@ export class Application extends BaseClass {
@Column({ nullable: true })
custom_install_url?: string;
- //just for us
+ @Column({ nullable: true })
+ splash?: string;
+
+ @Column({ nullable: true })
+ primary_sku_id?: string; // if this application is a game sold, this field will be the id of the "Game SKU" that is created,
+
+ @Column({ nullable: true })
+ eula_id?: string;
+
+ @Column({ nullable: true })
+ slug?: string; // if this application is a game sold, this field will be the URL slug that links to the store page
+
+ @Column({ type: "simple-array", nullable: true })
+ aliases?: string[];
+
+ @Column({ type: "simple-json", nullable: true })
+ executables?: ApplicationExecutableSchema[];
+
+ @Column({ type: "simple-json", nullable: true })
+ third_party_skus?: ApplicationSKUSchema[];
+
+ @Column({ nullable: true })
+ overlay?: boolean = false;
+
+ @Column({ nullable: true })
+ overlay_methods?: number; // only one right now is 1 << 0 OUT_OF_PROCESS
+
+ @Column({ nullable: true })
+ overlay_warn?: boolean = false;
+
+ @Column({ nullable: true })
+ overlay_compatibility_hook?: boolean = false;
+
+ @Column({ type: "simple-json", nullable: true })
+ developers?: ApplicationCompanySchema[];
+
+ @Column({ type: "simple-json", nullable: true })
+ publishers?: ApplicationCompanySchema[];
+
+ @Column({ type: "simple-array", nullable: true })
+ rpc_origins?: string[];
+
+ @Column({ nullable: true })
+ deeplink_uri?: string;
+
+ @Column({ nullable: true })
+ bot_disabled?: boolean = false;
+
+ @Column({ nullable: true })
+ bot_quarantined?: boolean = false;
+
+ @Column({ nullable: true })
+ bot_approximate_guild_count?: number;
+
+ @Column({ nullable: true })
+ approximate_guild_count?: number;
+
+ @Column({ nullable: true })
+ approximate_user_install_count?: number;
+
+ @Column({ nullable: true })
+ approximate_user_authorization_count?: number;
+
+ @Column({ nullable: true })
+ internal_guild_restriction?: ApplicationGuildRestriction;
+
+ @Column({ nullable: true, type: "text" })
+ role_connections_verification_url: string | null = null;
+
+ @Column()
+ interactions_version: ApplicationInteractionsVersion = ApplicationInteractionsVersion.VERSION_1;
- //@Column({ type: "simple-array", nullable: true })
- //rpc_origins?: string[];
+ @Column({ type: "simple-array" })
+ interactions_event_types: ApplicationEventWebhooksType[] = [];
+
+ @Column({ nullable: true })
+ event_webhooks_status?: ApplicationEventWebhooksStatus;
+
+ @Column({ nullable: true })
+ event_webhooks_url?: string;
+
+ @Column({ type: "simple-array", nullable: true })
+ event_webhooks_types?: ApplicationEventWebhooksType[];
+
+ @Column()
+ explicit_content_filter: number = 0; // Inherits the guild's explicit content filter
+
+ @Column({ type: "simple-json", nullable: true })
+ integration_types_config?: Record<number, ApplicationIntegrationTypeConfigurationSchema>;
+
+ @Column({ nullable: true })
+ connection_entrypoint_url?: string;
+
+ @Column()
+ is_verified: boolean = false;
+
+ @Column()
+ creator_monetization_state: number = 0; // unknown values
+
+ @Column()
+ is_discoverable: boolean = false;
+
+ @Column()
+ is_monetized: boolean = false;
+
+ @Column()
+ storefront_available: boolean = false;
+
+ @Column()
+ monetization_state: ApplicationMonetizationState = ApplicationMonetizationState.NONE;
+
+ @Column({ nullable: true })
+ monetization_eligibility_flags?: number = 0; // ApplicationMonetizationEligibilityFlags
+
+ @Column({ nullable: true })
+ max_participants?: number; // max participants for embedded applications, -1 for unlimited
+
+ @Column({ type: "simple-json", nullable: true })
+ embedded_activity_config?: object; // dont feel like making types for this rn
- //@Column({ nullable: true })
- //primary_sku_id?: string; // if this application is a game sold, this field will be the id of the "Game SKU" that is created,
+ @Column({ type: "simple-array" })
+ approved_consoles: ApplicationApprovableConsoleType[] = [];
- //@Column({ nullable: true })
- //slug?: string; // if this application is a game sold, this field will be the URL slug that links to the store page
+ // @Column()
+ // pricing_localization_strategy: string;
@JoinColumn({ name: "team_id" })
@ManyToOne(() => Team, {
diff --git a/src/util/entities/SKU.ts b/src/util/entities/SKU.ts
new file mode 100644
index 000000000..e615ed088
--- /dev/null
+++ b/src/util/entities/SKU.ts
@@ -0,0 +1,225 @@
+/*
+ Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
+ Copyright (C) 2023 Spacebar and Spacebar Contributors
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+import { BaseEntity, Column, Entity, JoinColumn, ManyToOne, OneToMany, Unique } from "typeorm";
+import { BaseClass } from "./BaseClass";
+import { User } from "./User";
+import { Application } from "./Application";
+import {
+ BillingPaymentGateway,
+ CreateSKUSchema,
+ LocalizedStringSchema,
+ SKUAccessType,
+ SKUContentRatingAgency,
+ SKUContentRatingSchema,
+ SKUExternalSKUStrategySchema,
+ SKUFeature,
+ SKUGenre,
+ SKUOperatingSystem,
+ SKUPriceSchema,
+ SKUProductLine,
+ SKUSystemRequirementsSchema,
+ SKUTenantMetadataSchema,
+ SKUType,
+} from "@spacebar/schemas";
+import { SKUGuildPowerupMetadataSchema } from "../../schemas/api/store/SKUGuildPowerupMetadataSchema";
+import { slugify } from "../util";
+
+@Entity({
+ name: "skus",
+})
+export class SKU extends BaseClass {
+ @Column()
+ type: SKUType;
+
+ @Column()
+ application_id: string;
+
+ @JoinColumn({ name: "application_id" })
+ @ManyToOne(() => Application, { onDelete: "CASCADE" })
+ application: Application;
+
+ @Column()
+ product_line: SKUProductLine;
+
+ @Column()
+ product_id?: string;
+
+ @Column({ type: "bigint" })
+ flags: number = 0; // SKUFlags
+
+ @Column({ type: "simple-json" })
+ name: LocalizedStringSchema;
+
+ @Column({ nullable: true, type: "simple-json" })
+ summary?: LocalizedStringSchema;
+
+ @Column({ nullable: true, type: "simple-json" })
+ description?: LocalizedStringSchema;
+
+ @Column({ nullable: true, type: "simple-json" })
+ legal_notice?: LocalizedStringSchema;
+
+ @Column()
+ slug: string;
+
+ @Column({ nullable: true })
+ thumbnail_asset_id?: string;
+
+ @Column({ nullable: true, type: "text" })
+ dependent_sku_id?: string | null;
+
+ @JoinColumn({ name: "bundled_sku_ids" })
+ @OneToMany(() => SKU, (sku: SKU) => sku.id, {
+ cascade: true,
+ orphanedRowAction: "delete",
+ onDelete: "CASCADE",
+ })
+ bundled_skus?: SKU[];
+
+ @Column({ nullable: true, type: "simple-array" })
+ bundled_sku_ids?: string[];
+
+ @Column()
+ access_type: SKUAccessType;
+
+ @Column({ nullable: true, type: "simple-array" })
+ manifest_labels?: string[] | null;
+
+ @Column({ type: "simple-array" })
+ features: SKUFeature[];
+
+ @Column({ nullable: true, type: "simple-array" })
+ locales?: string[];
+
+ @Column({ nullable: true, type: "simple-array" })
+ genres?: SKUGenre[];
+
+ @Column({ nullable: true, type: "simple-array" })
+ available_regions?: string[];
+
+ @Column({ nullable: true, type: "simple-json" })
+ // only for localized skus
+ content_rating?: SKUContentRatingSchema;
+
+ @Column({ nullable: true, type: "simple-json" })
+ // only for localized skus
+ content_rating_agency?: SKUContentRatingAgency;
+
+ @Column({ nullable: true, type: "simple-json" })
+ // only for unlocalized skus
+ content_ratings?: Record<SKUContentRatingAgency, SKUContentRatingSchema>;
+
+ @Column({ nullable: true, type: "simple-json" })
+ system_requirements?: Record<SKUOperatingSystem, SKUSystemRequirementsSchema>;
+
+ @Column({ nullable: true, type: "simple-json" })
+ // used for localized skus
+ price?: SKUPriceSchema | Record<string, unknown>; // discord.food says map[string, integer] but i dont think thats true, its probably SKUPriceSchema
+
+ @Column({ nullable: true })
+ // used for unlocalized skus
+ price_tier?: number;
+
+ @Column({ nullable: true })
+ // used for unlocalized skus
+ sale_price_tier?: number;
+
+ @Column({ nullable: true, type: "simple-json" })
+ // used for localized skus
+ sale_price?: Record<string, unknown>; // same here, discord.food says map[string, integer] which i dont believe
+
+ @Column()
+ created_at: Date;
+
+ @Column()
+ updated_at: Date;
+
+ @Column({ nullable: true, type: Date })
+ release_date?: Date | null;
+
+ @Column({ nullable: true })
+ preorder_approximate_release_date?: string;
+
+ @Column({ nullable: true })
+ preorder_released_at?: string;
+
+ @Column({ nullable: true })
+ external_purchase_url?: string;
+
+ @Column({ nullable: true, type: "simple-json" })
+ external_sku_strategies?: Record<BillingPaymentGateway, SKUExternalSKUStrategySchema>;
+
+ @Column({ nullable: true, type: "simple-array" })
+ eligible_payment_gateways?: BillingPaymentGateway[];
+
+ @Column()
+ premium: boolean;
+
+ @Column()
+ show_age_gate: boolean;
+
+ @Column({ nullable: true })
+ // only for localized skus
+ restricted?: boolean;
+
+ @Column({ nullable: true })
+ exclusive?: boolean;
+
+ @Column({ nullable: true, default: false })
+ deleted?: boolean = false;
+
+ @Column({ nullable: true, type: "simple-json" })
+ tenant_metadata?: SKUTenantMetadataSchema;
+
+ @Column({ nullable: true, type: "simple-json" })
+ powerup_metadata?: SKUGuildPowerupMetadataSchema;
+
+ static async createSku(data: CreateSKUSchema): Promise<SKU> {
+ const n = {
+ ...data,
+ bundled_skus: undefined,
+ name: undefined,
+ slug: undefined,
+ };
+
+ // convert string to localized string or use as is
+ const name = typeof data.name === "string" ? { default: data.name } : data.name;
+
+ const sku = await SKU.create({
+ product_line: SKUProductLine.APPLICATION,
+ dependent_sku_id: null,
+ manifest_labels: null,
+ access_type: SKUAccessType.FULL,
+ features: [],
+ release_date: null,
+ premium: false,
+ flags: 0,
+ show_age_gate: false,
+ tenant_metadata: {},
+ created_at: new Date(),
+ updated_at: new Date(),
+ ...n,
+ bundled_sku_ids: data.bundled_skus,
+ name,
+ slug: slugify(name.default),
+ });
+
+ return sku;
+ }
+}
diff --git a/src/util/entities/index.ts b/src/util/entities/index.ts
index 0e67675a8..dcd91ba7f 100644
--- a/src/util/entities/index.ts
+++ b/src/util/entities/index.ts
@@ -62,3 +62,4 @@ export * from "./UserSettingsProtos";
export * from "./ValidRegistrationTokens";
export * from "./VoiceState";
export * from "./Webhook";
+export * from "./SKU";
|