Compare commits
13
Commits
@@ -57,13 +57,34 @@ jobs:
|
||||
docker push mts.dgnr.us:5000/${{ gitea.repository }}:frontend-$TAG &
|
||||
wait
|
||||
|
||||
- name: Deploy to production
|
||||
- name: Install kubectl
|
||||
run: |
|
||||
ARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')
|
||||
curl -sLO "https://dl.k8s.io/release/v1.35.0/bin/linux/${ARCH}/kubectl"
|
||||
chmod +x kubectl
|
||||
mv kubectl /usr/local/bin/kubectl
|
||||
|
||||
- name: Configure kubectl
|
||||
run: |
|
||||
kubectl config set-cluster buzz --server=https://100.75.220.33:6443 --insecure-skip-tls-verify=true
|
||||
kubectl config set-credentials ci-deployer --token=${{ secrets.KUBE_TOKEN }}
|
||||
kubectl config set-context buzz --cluster=buzz --user=ci-deployer --namespace=buzz
|
||||
kubectl config use-context buzz
|
||||
|
||||
- name: Deploy to k3s
|
||||
run: |
|
||||
TAG=${{ gitea.sha }}
|
||||
|
||||
docker service update \
|
||||
--image mts.dgnr.us:5000/${{ gitea.repository }}:frontend-$TAG \
|
||||
buzz_app
|
||||
docker service update \
|
||||
--image mts.dgnr.us:5000/${{ gitea.repository }}:backend-$TAG \
|
||||
buzz_backend
|
||||
# DB migration
|
||||
kubectl delete job db-migrate -n buzz --ignore-not-found
|
||||
kubectl create job db-migrate \
|
||||
--image=mts.dgnr.us:5000/${{ gitea.repository }}:frontend-$TAG \
|
||||
-n buzz \
|
||||
-- bun dr push
|
||||
kubectl wait --for=condition=complete job/db-migrate -n buzz --timeout=180s
|
||||
|
||||
# Roll app & backend
|
||||
kubectl set image deployment/app app=mts.dgnr.us:5000/${{ gitea.repository }}:frontend-$TAG -n buzz
|
||||
kubectl set image deployment/backend backend=mts.dgnr.us:5000/${{ gitea.repository }}:backend-$TAG -n buzz
|
||||
kubectl rollout status deployment/app -n buzz --timeout=180s
|
||||
kubectl rollout status deployment/backend -n buzz --timeout=180s
|
||||
|
||||
+1
-15
@@ -21,21 +21,6 @@ COPY patches ./patches
|
||||
# Install into cache, then persist into real node_modules
|
||||
RUN --mount=type=cache,target=/root/.bun bun install --frozen-lockfile
|
||||
|
||||
### Stage N: drizzle ###
|
||||
FROM oven/bun:canary-alpine as drizzle
|
||||
|
||||
# Isolation
|
||||
RUN adduser -Du 1001 container
|
||||
USER container
|
||||
WORKDIR /home/container
|
||||
COPY --from=deps /home/container/node_modules ./node_modules
|
||||
COPY --from=deps /home/container/package*.json ./
|
||||
COPY --from=deps /home/container/bun.lock ./
|
||||
COPY --chown=1001 ./drizzle.config.ts ./.env ./
|
||||
COPY --chown=1001 ./lib/db ./lib/db
|
||||
|
||||
RUN bun dr push
|
||||
|
||||
### Stage 2: builder ###
|
||||
FROM oven/bun:canary-alpine AS builder
|
||||
|
||||
@@ -86,5 +71,6 @@ COPY --from=builder /home/container/util ./util
|
||||
COPY --from=builder /home/container/lib ./lib
|
||||
COPY --from=builder /home/container/tsconfig.json ./tsconfig.json
|
||||
COPY --from=builder /home/container/.version ./.version
|
||||
COPY --from=builder /home/container/drizzle.config.ts ./drizzle.config.ts
|
||||
|
||||
CMD ["bun", "start"]
|
||||
|
||||
@@ -6,6 +6,7 @@ import Link from "next/link";
|
||||
import { useParams, useRouter } from "next/navigation";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { MaybeWrap } from "@/components/action-button";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import {
|
||||
@@ -23,6 +24,11 @@ import {
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import {
|
||||
HoverCard,
|
||||
HoverCardContent,
|
||||
HoverCardTrigger,
|
||||
} from "@/components/ui/hover-card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import {
|
||||
SidebarMenu,
|
||||
@@ -40,12 +46,6 @@ import { setLimit, toggleCheck, toggleLock } from "@/lib/api";
|
||||
import { shared } from "@/lib/comms";
|
||||
import { sse } from "@/lib/db/sse-endpoints";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MaybeWrap } from "@/components/action-button";
|
||||
import {
|
||||
HoverCard,
|
||||
HoverCardContent,
|
||||
HoverCardTrigger,
|
||||
} from "@/components/ui/hover-card";
|
||||
|
||||
export function SidebarLink({
|
||||
submission,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { isNotNull, sql } from "drizzle-orm";
|
||||
import { Dice3, Trash2 } from "lucide-react";
|
||||
import { redirect } from "next/navigation";
|
||||
import {
|
||||
@@ -27,9 +28,8 @@ import {
|
||||
import { getArtifactConfig, random, wipe } from "@/lib/api";
|
||||
import { adminCheck } from "@/lib/auth";
|
||||
import { db } from "@/lib/db";
|
||||
import { LimitManager, SubmissionList, Watcher } from "./client";
|
||||
import { isNotNull, sql } from "drizzle-orm";
|
||||
import { submissions } from "@/lib/db/schema";
|
||||
import { LimitManager, SubmissionList, Watcher } from "./client";
|
||||
|
||||
export const metadata = {
|
||||
title: "เสือกไอดีชาวบ้าน (แอดมิน)",
|
||||
|
||||
@@ -2,7 +2,9 @@ import { PlusIcon } from "lucide-react";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { redirect } from "next/navigation";
|
||||
import z from "zod";
|
||||
import { ActionButton } from "@/components/action-button";
|
||||
import { FormAction, FormInput, FormProvider } from "@/components/form";
|
||||
import { formParse } from "@/components/form-submit";
|
||||
import { ModalBase } from "@/components/modal";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { DialogClose, DialogFooter } from "@/components/ui/dialog";
|
||||
@@ -12,9 +14,7 @@ import { actionLog } from "@/lib/api";
|
||||
import { adminCheck } from "@/lib/auth";
|
||||
import { db } from "@/lib/db";
|
||||
import { settings } from "@/lib/db/schema";
|
||||
import { formParse } from "@/components/form-submit";
|
||||
import { sse } from "@/lib/db/sse-endpoints";
|
||||
import { ActionButton } from "@/components/action-button";
|
||||
import { resetGoal } from "../../api";
|
||||
|
||||
const Schema = z.object({
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
"use server";
|
||||
|
||||
import { eq } from "drizzle-orm";
|
||||
import { adminCheck } from "@/lib/auth";
|
||||
import { db } from "@/lib/db";
|
||||
import { donations, settings } from "@/lib/db/schema";
|
||||
import { sse } from "@/lib/db/sse-endpoints";
|
||||
import { fileToDataUrl } from "@/lib/utils";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { getPostHogClient } from "@/lib/posthog-server";
|
||||
import { fileToDataUrl } from "@/lib/utils";
|
||||
|
||||
export async function resetGoal() {
|
||||
if (!(await adminCheck())) throw new Error("Unauthorized");
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { DataTable } from "@/components/tantable";
|
||||
import { SimpleTooltip } from "@/components/tooltip";
|
||||
import {
|
||||
HoverCard,
|
||||
HoverCardContent,
|
||||
HoverCardTrigger,
|
||||
} from "@/components/ui/hover-card";
|
||||
import type { donations } from "@/lib/db/schema";
|
||||
import type { ColumnDef } from "@tanstack/react-table";
|
||||
import {
|
||||
BitcoinIcon,
|
||||
@@ -18,15 +10,23 @@ import {
|
||||
ImageIcon,
|
||||
MessageCircleWarning,
|
||||
} from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import posthog from "posthog-js";
|
||||
import { useEffect } from "react";
|
||||
import { useEllipsisVisible } from "react-hook-text-overflow";
|
||||
import { DataTable } from "@/components/tantable";
|
||||
import { SimpleTooltip } from "@/components/tooltip";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
HoverCard,
|
||||
HoverCardContent,
|
||||
HoverCardTrigger,
|
||||
} from "@/components/ui/hover-card";
|
||||
import type { donations } from "@/lib/db/schema";
|
||||
import { sse } from "@/lib/db/sse-endpoints";
|
||||
import { ActionButton } from "../../../../components/action-button";
|
||||
import { getImage, reloadWidget, resendPopup, testPopup } from "./api";
|
||||
import { useEffect } from "react";
|
||||
import posthog from "posthog-js";
|
||||
import { sse } from "@/lib/db/sse-endpoints";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import Link from "next/link";
|
||||
|
||||
const columns: ColumnDef<typeof donations.$inferSelect>[] = [
|
||||
{ accessorKey: "name", header: "Name", meta: { className: "w-50 truncate" } },
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ErrorModal } from "@/components/error";
|
||||
import { ErrorBoundary } from "next/dist/client/components/error-boundary";
|
||||
import type { ReactNode } from "react";
|
||||
import { ErrorModal } from "@/components/error";
|
||||
|
||||
export default async function DonateAdminLayout({
|
||||
children,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { db } from "@/lib/db";
|
||||
import { DonateAdminPage } from "./client";
|
||||
import { donations } from "@/lib/db/schema";
|
||||
import { desc, getTableColumns, sql } from "drizzle-orm";
|
||||
import { adminCheck } from "@/lib/auth";
|
||||
import { redirect } from "next/navigation";
|
||||
import type { Metadata } from "next";
|
||||
import { redirect } from "next/navigation";
|
||||
import { adminCheck } from "@/lib/auth";
|
||||
import { db } from "@/lib/db";
|
||||
import { donations } from "@/lib/db/schema";
|
||||
import { DonateAdminPage } from "./client";
|
||||
|
||||
export default async function () {
|
||||
if (!(await adminCheck()))
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { sql } from "drizzle-orm";
|
||||
import { QrCodeIcon, SendIcon } from "lucide-react";
|
||||
import type { Metadata } from "next";
|
||||
import z from "zod";
|
||||
import { th } from "zod/v4/locales";
|
||||
import PromptpayImage from "#/assets/promptpay.jpg";
|
||||
import TruemoneyIcon from "#/assets/tmn.webp";
|
||||
import DonateLogo from "#/logos/donate.webp";
|
||||
import Cropper from "@/components/cropper";
|
||||
import {
|
||||
FormAction,
|
||||
@@ -27,15 +29,13 @@ import { db } from "@/lib/db";
|
||||
import { donations, endgameSlips, submissions } from "@/lib/db/schema";
|
||||
import { sse } from "@/lib/db/sse-endpoints";
|
||||
import { checkSlip } from "@/lib/payment";
|
||||
import { getPostHogClient } from "@/lib/posthog-server";
|
||||
import { fileToDataUrl } from "@/lib/utils";
|
||||
import {
|
||||
CurrencyInput,
|
||||
SlipUpload,
|
||||
} from "../rubgram/admin/@modal/manual/client";
|
||||
import { DownloadButton } from "../rubgram/client";
|
||||
import { getPostHogClient } from "@/lib/posthog-server";
|
||||
import { sql } from "drizzle-orm";
|
||||
import DonateLogo from "#/logos/donate.webp";
|
||||
|
||||
const { TMN_DEST_PHONE_NUM, SASTIFY_API_PRIVKEY } = process.env as Record<
|
||||
string,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { SQL } from "drizzle-orm";
|
||||
import { asc, desc, max, sql, sum } from "drizzle-orm";
|
||||
import type { Metadata } from "next";
|
||||
import { Suspense } from "react";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { db } from "@/lib/db";
|
||||
@@ -7,7 +8,6 @@ import { donations } from "@/lib/db/schema";
|
||||
import { fileToDataUrl } from "@/lib/utils";
|
||||
import { Podium } from "./podium";
|
||||
import { TopDonateTable } from "./table";
|
||||
import type { Metadata } from "next";
|
||||
|
||||
type NumberString = `${number}`;
|
||||
type NumberLike = number | NumberString;
|
||||
|
||||
+1
-1
@@ -1,3 +1,4 @@
|
||||
import { Anuphan } from "next/font/google";
|
||||
import { ViewTransition } from "react";
|
||||
import { Toaster } from "sonner";
|
||||
import Background from "#/bg.webp";
|
||||
@@ -5,7 +6,6 @@ import Image from "@/components/image";
|
||||
import { StarsRenderer } from "@/components/stars";
|
||||
import Providers, { VersionCheck } from "../client";
|
||||
import { Navbar } from "./navbar";
|
||||
import { Anuphan } from "next/font/google";
|
||||
|
||||
const anuphan = Anuphan({
|
||||
variable: "--font-anuphan",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { Loader2, UserLock } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import posthog from "posthog-js";
|
||||
import { type FormEvent, use, useEffect, useState } from "react";
|
||||
@@ -11,7 +12,6 @@ import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
import { Brash } from "./brash";
|
||||
import Link from "next/link";
|
||||
|
||||
export default function LoginPage({
|
||||
searchParams,
|
||||
|
||||
+1
-1
@@ -3,10 +3,10 @@ import Link from "next/link";
|
||||
import type { ReactNode } from "react";
|
||||
import Banner from "#/banner_t.webp";
|
||||
import ArtifactLogo from "#/logos/artifact.webp";
|
||||
import DonateLogo from "#/logos/donate.webp";
|
||||
import GuideLogo from "#/logos/guide.webp";
|
||||
import RubgramLogo from "#/logos/rubgram.webp";
|
||||
import TierlistLogo from "#/logos/tierlist.webp";
|
||||
import DonateLogo from "#/logos/donate.webp";
|
||||
import Image from "@/components/image";
|
||||
import { SimpleTooltip } from "@/components/tooltip";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
@@ -4,15 +4,16 @@
|
||||
import { FileSearch2, RefreshCw, X } from "lucide-react";
|
||||
import { type ComponentProps, useEffect, useRef, useState } from "react";
|
||||
import { b2sClient } from "@/app/(ui)/admin/cdn/table";
|
||||
import { VirtualizedComboBox } from "@/components/combobox";
|
||||
import { useFormContext } from "@/components/form";
|
||||
import { SimpleTooltip } from "@/components/tooltip";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import type { Input } from "@/components/ui/input";
|
||||
import {
|
||||
InputGroup,
|
||||
InputGroupAddon,
|
||||
InputGroupInput,
|
||||
} from "@/components/ui/input-group";
|
||||
import type { Input } from "@/components/ui/input";
|
||||
import { Kbd } from "@/components/ui/kbd";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import {
|
||||
@@ -23,9 +24,8 @@ import {
|
||||
MultiSelectTrigger,
|
||||
MultiSelectValue,
|
||||
} from "@/components/ui/multi-select";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { VirtualizedComboBox } from "@/components/combobox";
|
||||
import { Spinner } from "@/components/ui/spinner";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export function CurrencyInput(props: ComponentProps<typeof Input>) {
|
||||
return (
|
||||
|
||||
@@ -2,6 +2,8 @@ import { PlusIcon } from "lucide-react";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { redirect } from "next/navigation";
|
||||
import z from "zod";
|
||||
import { th } from "zod/v4/locales";
|
||||
import { getDiscordUsers } from "@/app/api/discord/users/api";
|
||||
import {
|
||||
FormAction,
|
||||
FormInput,
|
||||
@@ -12,6 +14,15 @@ import { ModalBase } from "@/components/modal";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { DialogClose, DialogFooter } from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectLabel,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Spinner } from "@/components/ui/spinner";
|
||||
import { actionLog } from "@/lib/api";
|
||||
import { adminCheck } from "@/lib/auth";
|
||||
@@ -22,19 +33,8 @@ import {
|
||||
endgameSubmissions,
|
||||
endgameTypes,
|
||||
} from "@/lib/db/schema";
|
||||
import { CurrencyInput, ServiceSelect, SlipUpload, UserSelect } from "./client";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectLabel,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import type { SlipokResponse } from "../../../api";
|
||||
import { getDiscordUsers } from "@/app/api/discord/users/api";
|
||||
import { th } from "zod/v4/locales";
|
||||
import { CurrencyInput, ServiceSelect, SlipUpload, UserSelect } from "./client";
|
||||
|
||||
const Schema = z.object({
|
||||
name: z.string(),
|
||||
|
||||
@@ -16,7 +16,6 @@ import Link from "next/link";
|
||||
import { useMemo, useTransition } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { SimpleTooltip } from "@/components/tooltip";
|
||||
import { toggleMonth } from "../../api";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
@@ -34,6 +33,8 @@ import {
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { toggleMonth } from "../../api";
|
||||
|
||||
type Row = {
|
||||
id: string;
|
||||
name: string;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { endOfMonth, startOfMonth } from "date-fns";
|
||||
import { and, between, eq, getTableColumns } from "drizzle-orm";
|
||||
import { startOfMonth, endOfMonth } from "date-fns";
|
||||
import { redirect } from "next/navigation";
|
||||
import { adminCheck } from "@/lib/auth";
|
||||
import { db } from "@/lib/db";
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { sql } from "drizzle-orm";
|
||||
import { Dice5, PlusIcon, Trash2 } from "lucide-react";
|
||||
import { ErrorBoundary } from "next/dist/client/components/error-boundary";
|
||||
import Link from "next/link";
|
||||
import { redirect } from "next/navigation";
|
||||
import { ErrorModal } from "@/components/error";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
@@ -34,10 +38,6 @@ import {
|
||||
SubmissionList,
|
||||
Watcher,
|
||||
} from "./client";
|
||||
import { sql } from "drizzle-orm";
|
||||
import Link from "next/link";
|
||||
import { ErrorBoundary } from "next/dist/client/components/error-boundary";
|
||||
import { ErrorModal } from "@/components/error";
|
||||
|
||||
export default async function AdminLayout({
|
||||
modal,
|
||||
|
||||
@@ -17,8 +17,9 @@ import {
|
||||
settings,
|
||||
} from "@/lib/db/schema";
|
||||
import { sse } from "@/lib/db/sse-endpoints";
|
||||
import { getPostHogClient } from "@/lib/posthog-server";
|
||||
import { checkSlip } from "@/lib/payment";
|
||||
import { getPostHogClient } from "@/lib/posthog-server";
|
||||
|
||||
const { DISCORD_WEBHOOK_URL, DISCORD_CLIENT_ID, BASE_URL } =
|
||||
process.env as Record<string, string>;
|
||||
|
||||
|
||||
@@ -449,8 +449,7 @@ export function TierList({
|
||||
>
|
||||
<span>
|
||||
{type.name} เวอร์ชั่น {version.name} ระดับ
|
||||
<span className="text-yellow-400">{type.mode}</span>{" "}
|
||||
ใช้ได้ถึง{" "}
|
||||
<span className="text-yellow-400">{type.mode}</span> ใช้ได้ถึง{" "}
|
||||
</span>
|
||||
<span className="text-green-400">{version.deprecates}</span>
|
||||
{deleteMode && (
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { getAmberVh } from "@/lib/api";
|
||||
|
||||
export const revalidate = 3600;
|
||||
|
||||
export async function GET() {
|
||||
return new Response(
|
||||
await fetch(
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { getAmberVh } from "@/lib/api";
|
||||
|
||||
export const revalidate = 3600;
|
||||
|
||||
export async function GET() {
|
||||
return new Response(
|
||||
await fetch(
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
import { env } from "node:process";
|
||||
import Snowflake, { GatewayIntents } from "@sfjs/snowflake";
|
||||
import { redis } from "@/lib/db/redis";
|
||||
import { adminCheck } from "@/lib/auth";
|
||||
import { forbidden } from "next/navigation";
|
||||
import { adminCheck } from "@/lib/auth";
|
||||
import { redis } from "@/lib/db/redis";
|
||||
|
||||
const bot = new Snowflake({
|
||||
token: env.DISCORD_BOT_TOKEN || "",
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { and, asc, not, sql } from "drizzle-orm";
|
||||
import type { NextRequest } from "next/server";
|
||||
import { db } from "@/lib/db";
|
||||
import { donations } from "@/lib/db/schema";
|
||||
import { sse } from "@/lib/db/sse-endpoints";
|
||||
import { fileToDataUrl } from "@/lib/utils";
|
||||
import { and, asc, not, sql } from "drizzle-orm";
|
||||
import type { NextRequest } from "next/server";
|
||||
import { getPostHogClient } from "@/lib/posthog-server";
|
||||
import { fileToDataUrl } from "@/lib/utils";
|
||||
|
||||
async function runResume() {
|
||||
const [havent] = await db
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { GoogleGenAI } from "@google/genai";
|
||||
import { hash } from "bun";
|
||||
import { PassThrough, Readable } from "stream";
|
||||
import wav from "wav";
|
||||
import { Readable, PassThrough } from "stream";
|
||||
import z from "zod";
|
||||
import { redis } from "@/lib/db/redis";
|
||||
import { hash } from "bun";
|
||||
|
||||
const { DONATE_WIDGET_KEY, GEMINI_TTS_API_KEY } = process.env as Record<
|
||||
string,
|
||||
string
|
||||
|
||||
@@ -3,11 +3,7 @@ import { notFound } from "next/navigation";
|
||||
import { db } from "@/lib/db";
|
||||
import { cdn } from "@/lib/db/schema";
|
||||
|
||||
export const revalidate = 86400; // 24 hours
|
||||
|
||||
export async function generateStaticParams() {
|
||||
return await db.select({ id: cdn.id }).from(cdn);
|
||||
}
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export async function GET(
|
||||
_req: Request,
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
// oxlint-disable tailwindcss/no-unknown-classes
|
||||
|
||||
import { isNotNull, sql } from "drizzle-orm";
|
||||
import { Kanit } from "next/font/google";
|
||||
import { Watcher } from "@/app/(ui)/artifact/admin/client";
|
||||
import { VersionCheck } from "@/app/client";
|
||||
@@ -6,7 +8,6 @@ import AutoFitText from "@/components/fit";
|
||||
import { getArtifactConfig } from "@/lib/api";
|
||||
import { db } from "@/lib/db";
|
||||
import { submissions } from "@/lib/db/schema";
|
||||
import { isNotNull, sql } from "drizzle-orm";
|
||||
|
||||
const kanit = Kanit({
|
||||
variable: "--font-kanit",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"use server";
|
||||
|
||||
import { eq } from "drizzle-orm";
|
||||
import { db } from "@/lib/db";
|
||||
import { donations } from "@/lib/db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { getPostHogClient } from "@/lib/posthog-server";
|
||||
|
||||
export async function markRunning(id: string) {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
"use server";
|
||||
|
||||
import { gt, sum } from "drizzle-orm";
|
||||
import { getArtifactConfig } from "@/lib/api";
|
||||
import { db } from "@/lib/db";
|
||||
import { donations, settings } from "@/lib/db/schema";
|
||||
import { gt, sum } from "drizzle-orm";
|
||||
|
||||
export async function getDonateBar() {
|
||||
const { donateGoalStarting } = await getArtifactConfig();
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import { sse } from "@/lib/db/sse-endpoints";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { getDonateBar } from "./api";
|
||||
import { motion } from "motion/react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { sse } from "@/lib/db/sse-endpoints";
|
||||
import { pausePass } from "@/lib/utils";
|
||||
import { getDonateBar } from "./api";
|
||||
|
||||
type Data = Awaited<ReturnType<typeof getDonateBar>>;
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
import { AnimatePresence, motion } from "motion/react";
|
||||
import Image from "next/image";
|
||||
import posthog from "posthog-js";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { VersionCheck } from "@/app/client";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { sse } from "@/lib/db/sse-endpoints";
|
||||
import DefaultImage from "#/favicon.webp";
|
||||
import { VersionCheck } from "@/app/client";
|
||||
import { sse } from "@/lib/db/sse-endpoints";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { markDone, markRunning } from "./api";
|
||||
|
||||
type DonateData = {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"use server";
|
||||
|
||||
import { asc, desc, max, sum } from "drizzle-orm";
|
||||
import { db } from "@/lib/db";
|
||||
import { donations } from "@/lib/db/schema";
|
||||
import { asc, desc, max, sum } from "drizzle-orm";
|
||||
|
||||
export async function getTopDonate() {
|
||||
const [top] = await db
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { sse } from "@/lib/db/sse-endpoints";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { sse } from "@/lib/db/sse-endpoints";
|
||||
import { getTopDonate } from "./api";
|
||||
|
||||
type Data = Awaited<ReturnType<typeof getTopDonate>>;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Spinner } from "@/components/ui/spinner";
|
||||
import type { MaybePromise } from "bun";
|
||||
import { Check } from "lucide-react";
|
||||
import type { ComponentProps, ReactNode } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Spinner } from "@/components/ui/spinner";
|
||||
|
||||
export function ActionButton({
|
||||
action,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import { useVirtualizer } from "@tanstack/react-virtual";
|
||||
import { Check, ChevronsUpDown } from "lucide-react";
|
||||
import * as React from "react";
|
||||
import { useVirtualizer } from "@tanstack/react-virtual";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Command,
|
||||
|
||||
@@ -9,8 +9,6 @@ import {
|
||||
ZoomOutIcon,
|
||||
} from "lucide-react";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
|
||||
import { useFileUpload } from "@/hooks/use-file-upload";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Cropper,
|
||||
@@ -27,6 +25,7 @@ import {
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Slider } from "@/components/ui/slider";
|
||||
import { useFileUpload } from "@/hooks/use-file-upload";
|
||||
|
||||
// Define type for pixel crop area
|
||||
type Area = { x: number; y: number; width: number; height: number };
|
||||
|
||||
+2
-2
@@ -1,14 +1,14 @@
|
||||
// oxlint-disable react-hooks/exhaustive-deps
|
||||
"use client";
|
||||
|
||||
import { Divide } from "lucide-react";
|
||||
import * as React from "react";
|
||||
import { toast } from "sonner";
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { FormSubmitResult } from "./form-submit";
|
||||
import { DialogClose } from "./ui/dialog";
|
||||
import { Label } from "./ui/label";
|
||||
import { Tabs, TabsList, TabsTrigger } from "./ui/tabs";
|
||||
import { Divide } from "lucide-react";
|
||||
import type { FormSubmitResult } from "./form-submit";
|
||||
|
||||
const AUTOSAVE_TTL_MS = 10 * 60 * 1000;
|
||||
const STORAGE_PREFIX = "form:autosave:";
|
||||
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
# Buzz Events — k3s Deployment
|
||||
|
||||
## Architecture Decisions
|
||||
|
||||
### Ingress
|
||||
- **k3s built-in Traefik**, not nginx.
|
||||
- Annotations `traefik.ingress.kubernetes.io/response-buffering: "false"` + `read-timeout: "200"` preserve SSE (Server-Sent Events).
|
||||
- Traefik Middleware `sse-optimizer` caps `maxRequestBodyBytes` at 100 MB (for slip image uploads).
|
||||
- Standard `networking.k8s.io/v1` Ingress resource (not Traefik CRD IngressRoute) for portability.
|
||||
- External port: **80/443** (standard). `m.dgnr.us` DNS should point at k3s node IP(s). Old port 8604 is retired.
|
||||
|
||||
### Namespace
|
||||
- Everything lives in namespace `buzz`.
|
||||
- The namespace is created once manually (`kubectl create ns buzz`) and listed in kustomization.yaml.
|
||||
|
||||
### Secrets
|
||||
- **`buzz-env`** Secret created once from `.env`:
|
||||
```
|
||||
kubectl create secret generic buzz-env --from-env-file=.env -n buzz
|
||||
```
|
||||
- All Deployments and the migration Job reference it via `envFrom.secretRef`.
|
||||
- Update by deleting and recreating (or editing in place):
|
||||
```
|
||||
kubectl delete secret buzz-env -n buzz && kubectl create secret generic buzz-env --from-env-file=.env -n buzz
|
||||
```
|
||||
|
||||
### Database Migration
|
||||
- **k8s Job** (deploy-time), not in Dockerfile.
|
||||
- The `drizzle` stage was removed from `Dockerfile`. The build no longer runs `bun dr push`.
|
||||
- CI creates the `db-migrate` Job imperatively, waits for completion, then updates deployments.
|
||||
- The Job uses the frontend image (has drizzle config) and `command: ["bun", "dr", "push"]`.
|
||||
- Old job is deleted before creating the new one (`kubectl delete job ... --ignore-not-found`).
|
||||
|
||||
### Service Discovery
|
||||
- Frontend Service: `app` (ClusterIP, port 3000).
|
||||
- Backend reaches frontend at `http://app:3000` (same namespace, k8s DNS resolves `<service>`).
|
||||
- Backend references in `backend/index.ts:231,279` using `http://app:3000` remain unchanged.
|
||||
- `lib/auth.ts` `trustedOrigins` includes `http://app.buzz.svc.cluster.local:3000` for completeness.
|
||||
|
||||
### CI/CD
|
||||
- Gitea Actions builds and pushes images as before (same tags: `:frontend`, `:backend`, `:frontend-$SHA`, `:backend-$SHA`).
|
||||
- Deploy step in `build.yml`:
|
||||
1. Delete old migration Job, create fresh one with `:frontend-$SHA`, wait for completion.
|
||||
2. `kubectl set image deployment/app` and `deployment/backend` with the new SHA tags.
|
||||
- Kustomize is used for initial cluster bootstrap (`kubectl apply -k k8s/`). Subsequent updates use imperative `kubectl set image` because Kustomize's `images` field cannot differentiate two images that share the same image name (different tags). If image naming changes to separate repos (e.g., `buzz-frontend:tag` / `buzz-backend:tag`), switch to `kustomize edit set image` + `kubectl apply -k .`.
|
||||
|
||||
### CI Authentication
|
||||
- **ServiceAccount `ci-deployer`** in namespace `buzz`.
|
||||
- RBAC Role grants: Deployments (get/list/watch/update/patch), Pods (get/list/watch), Jobs (full CRUD), Services/Ingresses/Middlewares (get/list/watch/create/update/patch).
|
||||
- Token for the runner: `kubectl create token ci-deployer -n buzz` (or use a long-lived token from a Secret tied to the SA).
|
||||
- The runner also needs `docker` login access to `mts.dgnr.us:5000` (same as today).
|
||||
|
||||
### Replicated Stateful Services (external to buzz namespace)
|
||||
- **PostgreSQL**: CloudNativePG operator. `DATABASE_URL` in Secret points to the CNPG cluster's read-write service (e.g. `postgres://...@<cluster>-rw.<cnpg-ns>.svc:5432/buzz`).
|
||||
- **Redis**: redis-operator. `REDIS_URL` in Secret points to the Redis operator's endpoint.
|
||||
|
||||
### Container Registry
|
||||
- **Zot** running in-cluster (namespace `registry`), exposed via ServiceLB on host port 5000.
|
||||
- All k3s nodes have `/etc/rancher/k3s/registries.yaml` mirroring `mts.dgnr.us:5000` → `http://localhost:5000` (svclb binds host port 5000). k3s must be restarted after writing this file.
|
||||
- CI runner pushes to `mts.dgnr.us:5000` (Tailscale → `100.75.220.33` → svclb → zot pod).
|
||||
|
||||
### Resource Limits
|
||||
- **app**: 2 replicas, limits 1 CPU / 1 Gi memory, requests 500m / 512 Mi. Liveness probe on `/api/health` (60s interval). Readiness probe same.
|
||||
- **backend**: 1 replica, limits 1 CPU / 512 Mi memory, requests 250m / 256 Mi. No probes (no HTTP endpoint).
|
||||
- **db-migrate Job**: limits 1 CPU / 512 Mi, requests 250m / 256 Mi.
|
||||
|
||||
### HPA / PDB
|
||||
- HPA scales `app` on CPU > 70%, min 2 / max 6 replicas (replaces `swarm.autoscale` label).
|
||||
- PDB ensures min 1 pod always available during voluntary disruptions (node drains, rollouts).
|
||||
|
||||
### TLS
|
||||
- Not configured in manifests. Currently HTTP-only behind Traefik. Add `spec.tls` + cert-manager or k3s Traefik's built-in Let's Encrypt when ready.
|
||||
|
||||
## First Deploy (manual bootstrap)
|
||||
```bash
|
||||
kubectl create ns buzz
|
||||
kubectl create secret generic buzz-env --from-env-file=.env -n buzz
|
||||
kubectl apply -k k8s/
|
||||
```
|
||||
|
||||
## Subsequent Deploys (CI)
|
||||
```bash
|
||||
# In .github/workflows/build.yml
|
||||
TAG=${{ gitea.sha }}
|
||||
|
||||
# Install kubectl (arch-detected)
|
||||
ARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')
|
||||
curl -sLO "https://dl.k8s.io/release/v1.35.0/bin/linux/${ARCH}/kubectl"
|
||||
chmod +x kubectl && mv kubectl /usr/local/bin/kubectl
|
||||
|
||||
# Wire up kubeconfig
|
||||
kubectl config set-cluster buzz --server=https://100.75.220.33:6443 --insecure-skip-tls-verify=true
|
||||
kubectl config set-credentials ci-deployer --token=${{ secrets.KUBE_TOKEN }}
|
||||
kubectl config set-context buzz --cluster=buzz --user=ci-deployer --namespace=buzz
|
||||
kubectl config use-context buzz
|
||||
|
||||
# DB migration
|
||||
kubectl delete job db-migrate -n buzz --ignore-not-found
|
||||
kubectl create job db-migrate \
|
||||
--image=mts.dgnr.us:5000/astral/buzz:frontend-$TAG \
|
||||
-n buzz \
|
||||
--command -- bun dr push
|
||||
kubectl wait --for=condition=complete job/db-migrate -n buzz --timeout=120s
|
||||
|
||||
# Roll app & backend
|
||||
kubectl set image deployment/app app=mts.dgnr.us:5000/astral/buzz:frontend-$TAG -n buzz
|
||||
kubectl set image deployment/backend backend=mts.dgnr.us:5000/astral/buzz:backend-$TAG -n buzz
|
||||
```
|
||||
|
||||
CI runner needs Gitea secret `KUBE_TOKEN` containing the `ci-deployer` SA token. Rotate via:
|
||||
```bash
|
||||
kubectl delete secret ci-deployer-token -n buzz && \
|
||||
kubectl apply -f k8s/service-account.yaml && \
|
||||
kubectl create secret generic ci-deployer-token \
|
||||
--namespace buzz \
|
||||
--type=kubernetes.io/service-account-token \
|
||||
--annotation=kubernetes.io/service-account.name=ci-deployer
|
||||
# then get new token and update Gitea secret
|
||||
TOKEN=$(kubectl get secret ci-deployer-token -n buzz -o jsonpath='{.data.token}' | base64 -d)
|
||||
```
|
||||
|
||||
## Files Changed from Swarm
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `Dockerfile` | Removed `drizzle` stage (migration moved to k8s Job) |
|
||||
| `lib/auth.ts` | Added `app.buzz.svc.cluster.local:3000` to `trustedOrigins` |
|
||||
| `.github/workflows/build.yml` | Replaced `docker service update` with k8s deploy commands |
|
||||
| `nginx/nginx.conf` | Archived — replaced by Traefik Ingress |
|
||||
| `docker-compose.yml` | Archived — replaced by k8s manifests |
|
||||
|
||||
## Key Distinctions from Swarm
|
||||
- No `overlay` network (k8s CNI handles multi-node networking).
|
||||
- No host port `8604` (Traefik on 80/443).
|
||||
- Service discovery via k8s DNS, not Swarm DNS.
|
||||
- Rolling updates via `kubectl set image`, not `docker service update`.
|
||||
- Health checks via k8s probes, not Docker HEALTHCHECK.
|
||||
- Autoscaling via HPA, not `swarm.autoscale` label.
|
||||
@@ -0,0 +1,56 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: app
|
||||
namespace: buzz
|
||||
labels:
|
||||
app: buzz
|
||||
component: app
|
||||
spec:
|
||||
replicas: 2
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxUnavailable: 1
|
||||
maxSurge: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: buzz
|
||||
component: app
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: buzz
|
||||
component: app
|
||||
spec:
|
||||
terminationGracePeriodSeconds: 5
|
||||
containers:
|
||||
- name: app
|
||||
image: mts.dgnr.us:5000/astral/buzz:frontend
|
||||
ports:
|
||||
- containerPort: 3000
|
||||
protocol: TCP
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: buzz-env
|
||||
resources:
|
||||
limits:
|
||||
cpu: "1"
|
||||
memory: 1Gi
|
||||
requests:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /api/health
|
||||
port: 3000
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 60
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /api/health
|
||||
port: 3000
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
@@ -0,0 +1,42 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: backend
|
||||
namespace: buzz
|
||||
labels:
|
||||
app: buzz
|
||||
component: backend
|
||||
spec:
|
||||
replicas: 1
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxUnavailable: 0
|
||||
maxSurge: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: buzz
|
||||
component: backend
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: buzz
|
||||
component: backend
|
||||
spec:
|
||||
terminationGracePeriodSeconds: 5
|
||||
containers:
|
||||
- name: backend
|
||||
image: mts.dgnr.us:5000/astral/buzz:backend
|
||||
env:
|
||||
- name: NO_AUTH_CHECK
|
||||
value: "true"
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: buzz-env
|
||||
resources:
|
||||
limits:
|
||||
cpu: "1"
|
||||
memory: 512Mi
|
||||
requests:
|
||||
cpu: 250m
|
||||
memory: 256Mi
|
||||
@@ -0,0 +1,19 @@
|
||||
apiVersion: autoscaling/v2
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: app
|
||||
namespace: buzz
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: app
|
||||
minReplicas: 2
|
||||
maxReplicas: 6
|
||||
metrics:
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 70
|
||||
@@ -0,0 +1,30 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: app
|
||||
namespace: buzz
|
||||
annotations:
|
||||
traefik.ingress.kubernetes.io/read-timeout: "200"
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
rules:
|
||||
- host: m.dgnr.us
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: app
|
||||
port:
|
||||
number: 3000
|
||||
- host: buzz.sudloh.com
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: app
|
||||
port:
|
||||
number: 3000
|
||||
@@ -0,0 +1,17 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
namespace: buzz
|
||||
|
||||
resources:
|
||||
- namespace.yaml
|
||||
- deployment-app.yaml
|
||||
- service-app.yaml
|
||||
- deployment-backend.yaml
|
||||
- ingress.yaml
|
||||
- middleware.yaml
|
||||
- hpa.yaml
|
||||
- pdb.yaml
|
||||
- service-account.yaml
|
||||
- role.yaml
|
||||
- role-binding.yaml
|
||||
@@ -0,0 +1,9 @@
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: sse-optimizer
|
||||
namespace: buzz
|
||||
spec:
|
||||
buffering:
|
||||
maxRequestBodyBytes: 104857600
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: buzz
|
||||
@@ -0,0 +1,11 @@
|
||||
apiVersion: policy/v1
|
||||
kind: PodDisruptionBudget
|
||||
metadata:
|
||||
name: app
|
||||
namespace: buzz
|
||||
spec:
|
||||
minAvailable: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: buzz
|
||||
component: app
|
||||
@@ -0,0 +1,13 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: ci-deployer
|
||||
namespace: buzz
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: ci-deployer
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: ci-deployer
|
||||
namespace: buzz
|
||||
@@ -0,0 +1,30 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: ci-deployer
|
||||
namespace: buzz
|
||||
rules:
|
||||
- apiGroups: ["apps"]
|
||||
resources: ["deployments"]
|
||||
verbs: ["get", "list", "watch", "update", "patch"]
|
||||
- apiGroups: [""]
|
||||
resources: ["pods"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
- apiGroups: [""]
|
||||
resources: ["services"]
|
||||
verbs: ["get", "list", "watch", "create", "update", "patch"]
|
||||
- apiGroups: ["batch"]
|
||||
resources: ["jobs"]
|
||||
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
|
||||
- apiGroups: ["networking.k8s.io"]
|
||||
resources: ["ingresses"]
|
||||
verbs: ["get", "list", "watch", "create", "update", "patch"]
|
||||
- apiGroups: ["traefik.io"]
|
||||
resources: ["middlewares"]
|
||||
verbs: ["get", "list", "watch", "create", "update", "patch"]
|
||||
- apiGroups: ["autoscaling"]
|
||||
resources: ["horizontalpodautoscalers"]
|
||||
verbs: ["get", "list", "watch", "create", "update", "patch"]
|
||||
- apiGroups: ["policy"]
|
||||
resources: ["poddisruptionbudgets"]
|
||||
verbs: ["get", "list", "watch", "create", "update", "patch"]
|
||||
@@ -0,0 +1,5 @@
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: ci-deployer
|
||||
namespace: buzz
|
||||
@@ -0,0 +1,18 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: app
|
||||
namespace: buzz
|
||||
labels:
|
||||
app: buzz
|
||||
component: app
|
||||
spec:
|
||||
selector:
|
||||
app: buzz
|
||||
component: app
|
||||
ports:
|
||||
- port: 3000
|
||||
targetPort: 3000
|
||||
protocol: TCP
|
||||
name: http
|
||||
type: ClusterIP
|
||||
+8
-6
@@ -1,6 +1,6 @@
|
||||
"use server";
|
||||
|
||||
import { randomUUIDv7 } from "bun";
|
||||
import { fetch, randomUUIDv7 } from "bun";
|
||||
import { and, eq, inArray, isNotNull, lt, not, or, sql } from "drizzle-orm";
|
||||
import type { PgDatabase } from "drizzle-orm/pg-core";
|
||||
import { revalidatePath } from "next/cache";
|
||||
@@ -9,6 +9,7 @@ import z from "zod";
|
||||
import { adminCheck } from "./auth";
|
||||
import { uidRegex } from "./const";
|
||||
import { db } from "./db";
|
||||
import { redis } from "./db/redis";
|
||||
import { cdnReferences } from "./db/references";
|
||||
import {
|
||||
artifactSettings,
|
||||
@@ -23,8 +24,6 @@ import {
|
||||
} from "./db/schema";
|
||||
import { sse, tlSse } from "./db/sse-endpoints";
|
||||
import { b2s } from "./utils";
|
||||
import { redis } from "./db/redis";
|
||||
import { fetch } from "bun";
|
||||
|
||||
export async function getCharacters(chars: string[]) {
|
||||
return await db
|
||||
@@ -410,8 +409,11 @@ export async function actionLog(text: string, details?: unknown) {
|
||||
}
|
||||
|
||||
export async function getAmberVh() {
|
||||
const cached = await redis!.get("amber:vh");
|
||||
if (cached) return cached;
|
||||
try {
|
||||
const cached = await redis?.get("amber:vh");
|
||||
if (cached) return cached;
|
||||
} catch {}
|
||||
|
||||
const Schema = z.object({
|
||||
response: z.number().positive(),
|
||||
data: z.object({
|
||||
@@ -425,6 +427,6 @@ export async function getAmberVh() {
|
||||
e.json(),
|
||||
),
|
||||
);
|
||||
queueMicrotask(() => redis!.setex("amber:vh", 86400, vh));
|
||||
queueMicrotask(() => redis?.setex("amber:vh", 86400, vh));
|
||||
return vh;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ export const auth = betterAuth({
|
||||
"http://localhost:3000",
|
||||
"https://dev3000.astrxl.dev",
|
||||
"http://m.dgnr.us",
|
||||
"http://buzz.sudloh.com",
|
||||
"http://app.buzz.svc.cluster.local:3000",
|
||||
],
|
||||
database: drizzleAdapter(db, {
|
||||
provider: "pg", // or "mysql", "sqlite"
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user