+import DropSessions from './ui/drop-sessions.vue'
+import EventsubSubscribe from './ui/eventsub-subscribe.vue'
+
+
+
+
+
+
+
+
diff --git a/frontend/dashboard/src/features/admin-panel/actions/ui/drop-sessions.vue b/frontend/dashboard/src/features/admin-panel/actions/ui/drop-sessions.vue
new file mode 100644
index 000000000..6a181567d
--- /dev/null
+++ b/frontend/dashboard/src/features/admin-panel/actions/ui/drop-sessions.vue
@@ -0,0 +1,47 @@
+
+
+
+
+ {{ t('adminPanel.adminActions.dangerZone.title') }}
+
+
+
+
+
+
+ {{ t('adminPanel.adminActions.dangerZone.revokeSessions') }}
+
+
+ {{ t('adminPanel.adminActions.dangerZone.revokeAllSessionsDescription') }}
+
+
+
+
+
+
+
+
+
diff --git a/frontend/dashboard/src/features/admin-panel/actions/ui/eventsub-subscribe.vue b/frontend/dashboard/src/features/admin-panel/actions/ui/eventsub-subscribe.vue
new file mode 100644
index 000000000..8d8c173ea
--- /dev/null
+++ b/frontend/dashboard/src/features/admin-panel/actions/ui/eventsub-subscribe.vue
@@ -0,0 +1,167 @@
+
+
+
+
+ {{ t('adminPanel.adminActions.eventsub.title') }}
+
+
+
+
+
+
diff --git a/frontend/dashboard/src/features/admin-panel/admin-actions/admin-actions.vue b/frontend/dashboard/src/features/admin-panel/admin-actions/admin-actions.vue
deleted file mode 100644
index 8bd71acb9..000000000
--- a/frontend/dashboard/src/features/admin-panel/admin-actions/admin-actions.vue
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
diff --git a/frontend/dashboard/src/features/admin-panel/admin-actions/components/drop-sessions.vue b/frontend/dashboard/src/features/admin-panel/admin-actions/components/drop-sessions.vue
deleted file mode 100644
index e763660af..000000000
--- a/frontend/dashboard/src/features/admin-panel/admin-actions/components/drop-sessions.vue
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/frontend/dashboard/src/features/admin-panel/manage-badges/components/badges-form.vue b/frontend/dashboard/src/features/admin-panel/manage-badges/components/badges-form.vue
deleted file mode 100644
index f9aa21bdf..000000000
--- a/frontend/dashboard/src/features/admin-panel/manage-badges/components/badges-form.vue
+++ /dev/null
@@ -1,96 +0,0 @@
-
-
-
-
- {{ t('adminPanel.manageBadges.formTitle') }}
-
-
-
-
-
diff --git a/frontend/dashboard/src/features/admin-panel/manage-badges/manage-badges.vue b/frontend/dashboard/src/features/admin-panel/manage-badges/manage-badges.vue
index 0ae82652a..1fd91d064 100644
--- a/frontend/dashboard/src/features/admin-panel/manage-badges/manage-badges.vue
+++ b/frontend/dashboard/src/features/admin-panel/manage-badges/manage-badges.vue
@@ -1,10 +1,10 @@
-
+
diff --git a/frontend/dashboard/src/features/admin-panel/manage-badges/ui/badges-form.vue b/frontend/dashboard/src/features/admin-panel/manage-badges/ui/badges-form.vue
new file mode 100644
index 000000000..4ef4c7531
--- /dev/null
+++ b/frontend/dashboard/src/features/admin-panel/manage-badges/ui/badges-form.vue
@@ -0,0 +1,98 @@
+
+
+
+
+ {{ t('adminPanel.manageBadges.formTitle') }}
+
+
+
+
+
+
diff --git a/frontend/dashboard/src/features/admin-panel/manage-badges/components/badges-list.vue b/frontend/dashboard/src/features/admin-panel/manage-badges/ui/badges-list.vue
similarity index 83%
rename from frontend/dashboard/src/features/admin-panel/manage-badges/components/badges-list.vue
rename to frontend/dashboard/src/features/admin-panel/manage-badges/ui/badges-list.vue
index b5837a679..b4a2e9e07 100644
--- a/frontend/dashboard/src/features/admin-panel/manage-badges/components/badges-list.vue
+++ b/frontend/dashboard/src/features/admin-panel/manage-badges/ui/badges-list.vue
@@ -1,6 +1,6 @@
diff --git a/frontend/dashboard/src/features/admin-panel/manage-notifications/components/created-at-tooltip.vue b/frontend/dashboard/src/features/admin-panel/manage-notifications/ui/created-at-tooltip.vue
similarity index 100%
rename from frontend/dashboard/src/features/admin-panel/manage-notifications/components/created-at-tooltip.vue
rename to frontend/dashboard/src/features/admin-panel/manage-notifications/ui/created-at-tooltip.vue
diff --git a/frontend/dashboard/src/features/admin-panel/manage-notifications/components/notifications-form.vue b/frontend/dashboard/src/features/admin-panel/manage-notifications/ui/notifications-form.vue
similarity index 50%
rename from frontend/dashboard/src/features/admin-panel/manage-notifications/components/notifications-form.vue
rename to frontend/dashboard/src/features/admin-panel/manage-notifications/ui/notifications-form.vue
index a50be2b2b..f3897e858 100644
--- a/frontend/dashboard/src/features/admin-panel/manage-notifications/components/notifications-form.vue
+++ b/frontend/dashboard/src/features/admin-panel/manage-notifications/ui/notifications-form.vue
@@ -1,5 +1,4 @@
diff --git a/frontend/dashboard/src/pages/alerts.vue b/frontend/dashboard/src/pages/alerts.vue
index 6798ec37c..47cc55fb1 100644
--- a/frontend/dashboard/src/pages/alerts.vue
+++ b/frontend/dashboard/src/pages/alerts.vue
@@ -1,7 +1,7 @@
-
+
{{ t('overlays.kappagen.info') }}
-
+
-
+
{{ t('overlays.kappagen.settings.spawn') }}
-
+
{{ t('overlays.kappagen.settings.size') }}({{ formatSizeValue(formValue.size!.ratioNormal) }})
-
{
{{ t('overlays.kappagen.settings.sizeSmall') }}({{
formatSizeValue(formValue.size!.ratioSmall)
}})
- {
-
+
{{ t('overlays.kappagen.settings.emotes.bttvEnabled') }}
-
+
{{ t('overlays.kappagen.settings.emotes.ffzEnabled') }}
-
+
{{ t('overlays.kappagen.settings.emotes.seventvEnabled') }}
{{ t('overlays.kappagen.settings.emotes.emojiStyle') }}
- {
-
+
{{ t('overlays.kappagen.settings.time') }}({{ formValue.emotes!.time }}s)
-
{
{{ t('overlays.kappagen.settings.maxEmotes') }}({{ formValue.emotes!.max }})
-
-
+
{{ t('overlays.kappagen.settings.animationsOnAppear') }}
-
+
Fade
-
+
Zoom
-
+
{{ t('overlays.kappagen.settings.animationsOnDisappear') }}
-
+
Fade
-
+
Zoom
-
+
-
+
{{ t('overlays.kappagen.settings.rave') }}
-
+
{{ t('overlays.kappagen.settings.excludedEmotes') }}
- {
:show-arrow="false"
:show="false"
/>
-
+
Clear
-
+
diff --git a/frontend/landing/src/api/gql.ts b/frontend/landing/src/api/gql.ts
index 7d4fc8f4b..45d525d54 100644
--- a/frontend/landing/src/api/gql.ts
+++ b/frontend/landing/src/api/gql.ts
@@ -3,5 +3,6 @@ import { config } from '@twir/config'
const host = config.SITE_BASE_URL ?? 'localhost:3005'
const isDev = config.NODE_ENV === 'development'
const apiAddr = isDev ? `${host}/api` : 'api-gql:3009'
+const protocol = isDev && !config.USE_WSS ? 'http' : 'https'
-export const gqlUrl = process.env.API_GQL_ADDR || `http://${apiAddr}/query`
+export const gqlUrl = process.env.API_GQL_ADDR || `${protocol}://${apiAddr}/query`
diff --git a/frontend/landing/src/api/user.ts b/frontend/landing/src/api/user.ts
index d9dcb2483..a0a43aadf 100644
--- a/frontend/landing/src/api/user.ts
+++ b/frontend/landing/src/api/user.ts
@@ -1,24 +1,24 @@
-import { gqlUrl } from '@/api/gql.ts';
+import { gqlUrl } from '@/api/gql.ts'
export async function getAuthenticatedUser(session: string) {
const request = await fetch(gqlUrl, {
headers: {
- Cookie: `session=${session}`,
+ 'Cookie': `session=${session}`,
'Content-Type': 'application/json',
},
credentials: 'include',
- 'body': '{"query":"query {\\n authenticatedUser {\\n twitchProfile {\\n displayName\\n profileImageUrl\\n }\\n }\\n}"}',
- 'method': 'POST',
- });
+ body: '{"query":"query {\\n authenticatedUser {\\n twitchProfile {\\n displayName\\n profileImageUrl\\n }\\n }\\n}"}',
+ method: 'POST',
+ })
- const response = await request.json();
+ const response = await request.json()
if (!request.ok || response.errors) {
- console.log(response);
- throw new Error(response.errors.toString());
+ console.log(response)
+ throw new Error(response.errors.toString())
}
- const profile = response.data.authenticatedUser.twitchProfile;
+ const profile = response.data.authenticatedUser.twitchProfile
- return profile;
+ return profile
}
diff --git a/frontend/landing/src/middleware.ts b/frontend/landing/src/middleware.ts
index e2144fb21..4e81ea00e 100644
--- a/frontend/landing/src/middleware.ts
+++ b/frontend/landing/src/middleware.ts
@@ -1,35 +1,36 @@
-import type { APIContext } from 'astro';
-import { defineMiddleware } from 'astro/middleware';
+import { defineMiddleware } from 'astro/middleware'
-import { getAuthLink } from '@/api/auth-link.ts';
-import { getAuthenticatedUser } from '@/api/user.ts';
+import type { APIContext } from 'astro'
+
+import { getAuthLink } from '@/api/auth-link.ts'
+import { getAuthenticatedUser } from '@/api/user.ts'
export const onRequest = defineMiddleware(async (context, next) => {
await Promise.all([
assignProfile(context),
assignLoginLink(context),
- ]);
+ ])
- await next();
-});
+ await next()
+})
-const assignProfile = async (context: APIContext) => {
- const session = context.cookies.get('session');
+async function assignProfile(context: APIContext) {
+ const session = context.cookies.get('session')
if (session && session.value) {
try {
- context.locals.profile = await getAuthenticatedUser(session.value);
+ context.locals.profile = await getAuthenticatedUser(session.value)
} catch (err) {
- console.log('User profile error:', err);
+ console.log('User profile error:', err)
}
}
-};
+}
-const assignLoginLink = async (context: APIContext) => {
- const redirectTo = `${context.url.origin}/dashboard`;
+async function assignLoginLink(context: APIContext) {
+ const redirectTo = `${context.url.origin}/dashboard`
try {
- context.locals.authLink = await getAuthLink(redirectTo);
+ context.locals.authLink = await getAuthLink(redirectTo)
} catch { /* empty */
}
-};
+}
diff --git a/libs/baseapp/basefxapp.go b/libs/baseapp/basefxapp.go
index 49724b8ec..d96fc37ad 100644
--- a/libs/baseapp/basefxapp.go
+++ b/libs/baseapp/basefxapp.go
@@ -2,6 +2,8 @@ package baseapp
import (
"context"
+ "log"
+ "os"
"time"
"github.com/redis/go-redis/v9"
@@ -12,6 +14,7 @@ import (
"go.uber.org/fx"
"gorm.io/driver/postgres"
"gorm.io/gorm"
+ gormlogger "gorm.io/gorm/logger"
)
func CreateBaseApp(appName string) fx.Option {
@@ -41,8 +44,22 @@ func newRedis(cfg config.Config) (*redis.Client, error) {
}
func newGorm(cfg config.Config, lc fx.Lifecycle) (*gorm.DB, error) {
+ newLogger := gormlogger.New(
+ log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
+ gormlogger.Config{
+ SlowThreshold: 100 * time.Millisecond,
+ LogLevel: gormlogger.Warn,
+ IgnoreRecordNotFoundError: true,
+ ParameterizedQueries: true,
+ Colorful: true,
+ },
+ )
+
db, err := gorm.Open(
postgres.Open(cfg.DatabaseUrl),
+ &gorm.Config{
+ Logger: newLogger,
+ },
)
if err != nil {
return nil, err
diff --git a/libs/config/src/index.js b/libs/config/src/index.js
index 61f420a53..92383d0a5 100644
--- a/libs/config/src/index.js
+++ b/libs/config/src/index.js
@@ -1,11 +1,10 @@
-import { resolve } from 'node:path';
+import { resolve } from 'node:path'
-import * as dotenv from 'dotenv';
-import { bool, cleanEnv, str } from 'envalid';
+import * as dotenv from 'dotenv'
+import { bool, cleanEnv, str } from 'envalid'
try {
- dotenv.config({ path: resolve(process.cwd(), '../../.env') });
- // eslint-disable-next-line no-empty
+ dotenv.config({ path: resolve(process.cwd(), '../../.env') })
} catch {
}
@@ -34,4 +33,5 @@ export const config = cleanEnv(process.env, {
ODESLI_API_KEY: str({ default: '' }),
DISCORD_FEEDBACK_URL: str({ default: '' }),
NATS_URL: str({ default: 'nats://localhost:4222' }),
-});
+ USE_WSS: bool({ default: false }),
+})
diff --git a/libs/config/src/types.d.ts b/libs/config/src/types.d.ts
index d977d5fc0..790ee7e63 100644
--- a/libs/config/src/types.d.ts
+++ b/libs/config/src/types.d.ts
@@ -1,5 +1,5 @@
declare module '@twir/config' {
- type Config = typeof import('./index.js').config;
- const config: Config;
- export { config };
+ type Config = typeof import('./index.js').config
+ const config: Config
+ export { config }
}
diff --git a/libs/gomodels/eventsub_subscription.go b/libs/gomodels/eventsub_subscription.go
new file mode 100644
index 000000000..cdf9eb7c7
--- /dev/null
+++ b/libs/gomodels/eventsub_subscription.go
@@ -0,0 +1,17 @@
+package model
+
+import (
+ "github.com/google/uuid"
+)
+
+type EventsubSubscription struct {
+ ID uuid.UUID `gorm:"column:id;type:uuid;primaryKey;default:uuid_generate_v4()"`
+ TopicID uuid.UUID `gorm:"column:topic_id;type:uuid;not null"`
+ UserID string `gorm:"column:user_id;type:text;not null"`
+ Status string `gorm:"column:status;type:varchar(255);not null"`
+ Version string `gorm:"column:version;type:varchar(255);not null"`
+ CallbackUrl string `gorm:"column:callback_url;type:varchar(255);not null"`
+
+ User *Users `gorm:"foreignKey:UserID;references:ID"`
+ Topic *EventsubTopic `gorm:"foreignKey:TopicID;references:ID"`
+}
diff --git a/libs/gomodels/eventsub_topic.go b/libs/gomodels/eventsub_topic.go
new file mode 100644
index 000000000..6467fd66a
--- /dev/null
+++ b/libs/gomodels/eventsub_topic.go
@@ -0,0 +1,33 @@
+package model
+
+import (
+ "github.com/google/uuid"
+)
+
+type EventsubTopic struct {
+ ID uuid.UUID `gorm:"column:id;type:uuid;primaryKey;default:uuid_generate_v4()"`
+ Topic string `gorm:"column:topic;type:varchar(255);not null"`
+ Version string `gorm:"column:version;type:varchar(255);not null"`
+ ConditionType EventsubConditionType `gorm:"column:condition_type;type:int;not null"`
+
+ Subscriptions []EventsubSubscription `gorm:"foreignKey:TopicID;references:ID"`
+}
+
+func (EventsubTopic) TableName() string {
+ return "eventsub_topics"
+}
+
+type EventsubConditionType string
+
+const (
+ // EventsubConditionTypeBroadcasterUserID { "broadcaster_user_id": "1234" }
+ EventsubConditionTypeBroadcasterUserID EventsubConditionType = "BROADCASTER_USER_ID"
+ // EventsubConditionTypeUserID { "user_id": "1234" }
+ EventsubConditionTypeUserID EventsubConditionType = "USER_ID"
+ // EventsubConditionTypeBroadcasterWithUserID { "broadcaster_user_id": "1234", "user_id": "1234" }
+ EventsubConditionTypeBroadcasterWithUserID EventsubConditionType = "BROADCASTER_WITH_USER_ID"
+ // EventsubConditionTypeBroadcasterWithModeratorID { "broadcaster_user_id": "1234", "moderator_user_id": "1234" }
+ EventsubConditionTypeBroadcasterWithModeratorID EventsubConditionType = "BROADCASTER_WITH_MODERATOR_ID"
+ // EventsubConditionTypeToBroadcasterID { "to_broadcaster_user_id": userId }
+ EventsubConditionTypeToBroadcasterID EventsubConditionType = "TO_BROADCASTER_ID"
+)
diff --git a/libs/gomodels/tokens.go b/libs/gomodels/tokens.go
index 9b848261e..62f90c384 100755
--- a/libs/gomodels/tokens.go
+++ b/libs/gomodels/tokens.go
@@ -2,9 +2,10 @@ package model
import (
"database/sql"
- "github.com/lib/pq"
"time"
+ "github.com/lib/pq"
+
"github.com/guregu/null"
uuid "github.com/satori/go.uuid"
)
@@ -17,7 +18,7 @@ var (
)
type Tokens struct {
- ID string `gorm:"primary_key;AUTO_INCREMENT;column:id;type:TEXT;" json:"id"`
+ ID string `gorm:"column:id;type:TEXT;" json:"id"`
AccessToken string `gorm:"column:accessToken;type:TEXT;" json:"accessToken"`
RefreshToken string `gorm:"column:refreshToken;type:TEXT;" json:"refreshToken"`
ExpiresIn int32 `gorm:"column:expiresIn;type:INT4;" json:"expiresIn"`
diff --git a/libs/migrations/migrations/20240512073747_add_eventsub.sql b/libs/migrations/migrations/20240512073747_add_eventsub.sql
new file mode 100644
index 000000000..129e8a100
--- /dev/null
+++ b/libs/migrations/migrations/20240512073747_add_eventsub.sql
@@ -0,0 +1,69 @@
+-- +goose Up
+-- +goose StatementBegin
+SELECT 'up SQL query';
+
+CREATE TYPE eventsub_topics_condition_type_enum AS ENUM (
+ 'BROADCASTER_USER_ID',
+ 'USER_ID',
+ 'BROADCASTER_WITH_USER_ID',
+ 'BROADCASTER_WITH_MODERATOR_ID',
+ 'TO_BROADCASTER_ID'
+);
+
+CREATE TABLE "eventsub_topics"
+(
+ "id" UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
+ "topic" VARCHAR(255) NOT NULL,
+ "version" VARCHAR(255) NOT NULL,
+ "condition_type" eventsub_topics_condition_type_enum NOT NULL
+);
+
+CREATE TABLE "eventsub_subscriptions"
+(
+ "id" UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
+ "topic_id" UUID NOT NULL references eventsub_topics (id),
+ "user_id" text NOT NULL references users (id),
+ "status" VARCHAR(255) NOT NULL,
+ "created_at" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
+ "version" VARCHAR(255) NOT NULL,
+ "callback_url" VARCHAR(255) NOT NULL
+);
+
+CREATE UNIQUE INDEX "eventsub_topics_topic_idx" ON "eventsub_topics" ("topic");
+CREATE INDEX "eventsub_subscriptions_user_id_idx" ON "eventsub_subscriptions" ("user_id");
+
+INSERT INTO eventsub_topics (topic, version, condition_type)
+VALUES ('channel.update', '2', 'BROADCASTER_USER_ID'),
+ ('stream.online', '1', 'BROADCASTER_USER_ID'),
+ ('stream.offline', '1', 'BROADCASTER_USER_ID'),
+ ('user.update', '1', 'USER_ID'),
+ ('channel.follow', '2', 'BROADCASTER_WITH_MODERATOR_ID'),
+ ('channel.moderator.add', '1', 'BROADCASTER_USER_ID'),
+ ('channel.moderator.remove', '1', 'BROADCASTER_USER_ID'),
+ ('channel.channel_points_custom_reward_redemption.add', '1', 'BROADCASTER_USER_ID'),
+ ('channel.channel_points_custom_reward_redemption.update', '1', 'BROADCASTER_USER_ID'),
+ ('channel.poll.begin', '1', 'BROADCASTER_USER_ID'),
+ ('channel.poll.progress', '1', 'BROADCASTER_USER_ID'),
+ ('channel.poll.end', '1', 'BROADCASTER_USER_ID'),
+ ('channel.prediction.begin', '1', 'BROADCASTER_USER_ID'),
+ ('channel.prediction.lock', '1', 'BROADCASTER_USER_ID'),
+ ('channel.prediction.progress', '1', 'BROADCASTER_USER_ID'),
+ ('channel.prediction.end', '1', 'BROADCASTER_USER_ID'),
+ ('channel.ban', '1', 'BROADCASTER_USER_ID'),
+ ('channel.subscribe', '1', 'BROADCASTER_USER_ID'),
+ ('channel.subscription.gift', '1', 'BROADCASTER_USER_ID'),
+ ('channel.subscription.message', '1', 'BROADCASTER_USER_ID'),
+ ('channel.raid', '1', 'TO_BROADCASTER_ID'),
+ ('channel.chat.clear', '1', 'BROADCASTER_WITH_USER_ID'),
+ ('channel.chat.clear_user_messages', '1', 'BROADCASTER_WITH_USER_ID'),
+ ('channel.chat.message_delete', '1', 'BROADCASTER_WITH_USER_ID'),
+ ('channel.chat.notification', '1', 'BROADCASTER_WITH_USER_ID'),
+ ('channel.chat.message', '1', 'BROADCASTER_WITH_USER_ID'),
+ ('channel.unban_request.create', '1', 'BROADCASTER_WITH_MODERATOR_ID'),
+ ('channel.unban_request.resolve', '1', 'BROADCASTER_WITH_MODERATOR_ID');
+-- +goose StatementEnd
+
+-- +goose Down
+-- +goose StatementBegin
+SELECT 'down SQL query';
+-- +goose StatementEnd