Skip to content

Commit

Permalink
handle overlay editing and creation logic
Browse files Browse the repository at this point in the history
  • Loading branch information
gempir committed Feb 6, 2024
1 parent 6ee04f1 commit 8ec1fe1
Show file tree
Hide file tree
Showing 13 changed files with 255 additions and 25 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ staticcheck:
staticcheck ./...

web:
yarn dev
cd web && yarn dev

deploy:
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -a -installsuffix cgo -o gempbot main.go
Expand Down
70 changes: 70 additions & 0 deletions internal/server/overlay.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package server

import (
"net/http"
"strings"

"github.com/gempir/gempbot/internal/api"
"github.com/gempir/gempbot/internal/store"
"github.com/google/uuid"
"github.com/teris-io/shortid"
)

func (a *Api) OverlayHandler(w http.ResponseWriter, r *http.Request) {
authResp, _, apiErr := a.authClient.AttemptAuth(r, w)
if apiErr != nil {
return
}
userID := authResp.Data.UserID

if r.URL.Query().Get("managing") != "" {
userID, apiErr = a.userAdmin.CheckEditor(r, a.userAdmin.GetUserConfig(userID))
if apiErr != nil {
http.Error(w, apiErr.Error(), apiErr.Status())
return
}
}

if r.Method == http.MethodGet {
if r.URL.Query().Get("roomId") != "" {
overlay := a.db.GetOverlayByRoomId(r.URL.Query().Get("roomId"))
api.WriteJson(w, overlay, http.StatusOK)
return
}

if r.URL.Query().Get("id") != "" {
overlay := a.db.GetOverlay(r.URL.Query().Get("id"), userID)
api.WriteJson(w, overlay, http.StatusOK)
return
}

overlays := a.db.GetOverlays(userID)
api.WriteJson(w, overlays, http.StatusOK)
} else if r.Method == http.MethodPost {
overlay := store.Overlay{}
overlay.OwnerTwitchID = userID
overlay.ID = shortid.MustGenerate()
// long string so you cant read addressbar easily
var roomID []string
for i := 0; i < 16; i++ {
roomID = append(roomID, uuid.New().String())
}
overlay.RoomID = strings.Join(roomID, "-")

err := a.db.SaveOverlay(overlay)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

api.WriteJson(w, overlay, http.StatusCreated)

} else if r.Method == http.MethodDelete {
if r.URL.Query().Get("id") == "" {
http.Error(w, "missing id", http.StatusBadRequest)
}

a.db.DeleteOverlay(r.URL.Query().Get("id"))
w.WriteHeader(http.StatusOK)
}
}
6 changes: 6 additions & 0 deletions internal/store/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ type Store interface {
CountNominationDownvotes(ctx context.Context, channelTwitchID string, voteBy string) (int, error)
CountNominationVotes(ctx context.Context, channelTwitchID string, voteBy string) (int, error)
IsAlreadyNominated(ctx context.Context, channelTwitchID string, emoteID string) (bool, error)
GetOverlays(userID string) []Overlay
GetOverlay(ID string, userID string) Overlay
GetOverlayByRoomId(roomID string) Overlay
DeleteOverlay(ID string)
SaveOverlay(overlay Overlay) error
}

type Database struct {
Expand Down Expand Up @@ -77,6 +82,7 @@ func (db *Database) Migrate() {
Nomination{},
NominationVote{},
NominationDownvote{},
Overlay{},
)
if err != nil {
panic("Failed to migrate, " + err.Error())
Expand Down
45 changes: 45 additions & 0 deletions internal/store/overlay.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package store

import "gorm.io/gorm/clause"

type Overlay struct {
ID string `gorm:"primaryKey"`
OwnerTwitchID string `gorm:"index"`
RoomID string `gorm:"index"`
}

func (db *Database) GetOverlays(userID string) []Overlay {
var overlays []Overlay

db.Client.Where("owner_twitch_id = ?", userID).Find(&overlays)

return overlays
}

func (db *Database) GetOverlay(ID string, userID string) Overlay {
var overlay Overlay

db.Client.Where("id = ? AND owner_twitch_id = ?", ID, userID).First(&overlay)

return overlay
}

func (db *Database) GetOverlayByRoomId(roomID string) Overlay {
var overlay Overlay

db.Client.Where("room_id = ?", roomID).First(&overlay)

return overlay
}

func (db *Database) DeleteOverlay(ID string) {
db.Client.Delete(&Overlay{}, "id = ?", ID)
}

func (db *Database) SaveOverlay(overlay Overlay) error {
update := db.Client.Clauses(clause.OnConflict{
UpdateAll: true,
}).Create(&overlay)

return update.Error
}
1 change: 1 addition & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ func main() {
mux.HandleFunc("/api/reward", apiHandlers.RewardHandler)
mux.HandleFunc("/api/subscriptions", apiHandlers.SubscriptionsHandler)
mux.HandleFunc("/api/userconfig", apiHandlers.UserConfigHandler)
mux.HandleFunc("/api/overlay", apiHandlers.OverlayHandler)
mux.HandleFunc("/api/ws", wsHandler.HandleWs)

handler := cors.New(cors.Options{
Expand Down
4 changes: 2 additions & 2 deletions web/src/components/Overlay/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ import { useStore } from '../../store';


type Props = {
overlayId: string;
roomId: string;
readonly?: boolean;
}

export function Editor(props: Partial<TldrawProps> & Props) {
const yjsWsUrl = useStore(state => state.yjsWsUrl);
const store = useYjsStore({
roomId: props.overlayId,
roomId: props.roomId,
hostUrl: yjsWsUrl,
});
const editor = useEditor();
Expand Down
6 changes: 4 additions & 2 deletions web/src/components/Overlay/IframeOverlayPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import { useParams } from 'next/navigation';
const Editor = dynamic(async () => (await import('./Editor')).Editor, { ssr: false })

export function IframeOverlayPage() {
const { overlayId } = useParams<{ overlayId: string }>();
const params = useParams<{ roomId: string }>();

console.log("Joining", params.roomId);

return (
<div className="relative w-full h-[100vh]">
Expand All @@ -22,7 +24,7 @@ export function IframeOverlayPage() {
}
`}</style>
</Head>
<Editor hideUi overlayId={overlayId} readonly />
<Editor hideUi roomId={params.roomId} readonly />
</div>
);
}
15 changes: 15 additions & 0 deletions web/src/components/Overlay/OverlayEditPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const Editor = dynamic(async () => (await import('./Editor')).Editor, { ssr: false })
import dynamic from "next/dynamic";
import { useOverlay } from "../../hooks/useOverlays";
import { useParams } from "next/navigation";

export function OverlayEditPage() {
const params = useParams<{ overlayId: string }>();
const [overlay] = useOverlay(params.overlayId);

console.log("Joining", overlay?.RoomID);

return <div className="relative w-full h-[100vh]">
{overlay?.RoomID && <Editor roomId={overlay.RoomID} />}
</div>;
}
33 changes: 24 additions & 9 deletions web/src/components/Overlay/OverlaysPage.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,29 @@
import dynamic from "next/dynamic";
import { useUserConfig } from "../../hooks/useUserConfig";
const Editor = dynamic(async () => (await import('./Editor')).Editor, { ssr: false })
import Link from "next/link";
import { useOverlays } from "../../hooks/useOverlays";

export function OverlaysPage() {
const [userCfg, setUserConfig, , loading, errorMessage] = useUserConfig();
if (!userCfg) {
return null;
}
const [overlays, addOverlay, deleteOverlay, errorMessage, loading] = useOverlays();

return <div className="relative w-full h-[100vh]">
Table with overlays

return <div className="relative w-full h-[100vh] p-4">
<div className="p-4 bg-gray-800 rounded shadow max-w-[800px]">
<button onClick={addOverlay} className="bg-green-700 hover:bg-green-600 p-2 rounded shadow block cursor-pointer">Add Overlay</button>
<div className="mt-5">
{overlays.map(overlay => <div key={overlay.ID} className="flex items-center justify-between p-4 bg-gray-900">
<div>
<button className="bg-red-700 hover:bg-red-600 p-2 rounded shadow block cursor-pointer" onClick={() => {
confirm("Are you sure you want to delete this overlay?") && deleteOverlay(overlay.ID)
}}>Delete</button>
</div>
<div>{overlay.ID}</div>
<div>
<Link href={`/overlay/edit/${overlay.ID}`} className="bg-blue-700 hover:bg-blue-600 p-2 rounded shadow block cursor-pointer">Edit</Link>
</div>
<div>
<input type="text" value={`${window?.location?.href}/${overlay.RoomID}`} readOnly className="bg-gray-900" />
</div>
</div>)}
</div>
</div>
</div>;
}
2 changes: 1 addition & 1 deletion web/src/components/Sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export function Sidebar() {
<ChatBubbleLeftIcon className="h-6" />Bot
</Link>
{isDev && <Link
href="/overlays"
href="/overlay"
className="flex gap-2 items-center py-4 justify-start hover:text-blue-500">
<PhotoIcon className="h-6" />Overlays
</Link>}
Expand Down
81 changes: 81 additions & 0 deletions web/src/hooks/useOverlays.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { useEffect, useState } from "react";
import { Method, doFetch } from "../service/doFetch";
import { useStore } from "../store";

type Overlay = {
ID: string;
RoomID: string;
}

export function useOverlays(): [Overlay[], () => void, (id: string) => void, string | null, boolean] {
const [overlays, setOverlays] = useState<Overlay[]>([]);
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const managing = useStore(state => state.managing);
const apiBaseUrl = useStore(state => state.apiBaseUrl);
const scToken = useStore(state => state.scToken);

const fetchOverlays = () => {
setLoading(true);
const endPoint = "/api/overlay";

doFetch({ apiBaseUrl, managing, scToken }, Method.GET, endPoint).then(setOverlays).catch(setErrorMessage).finally(() => setLoading(false));
}

useEffect(fetchOverlays, []);

const addOverlay = () => {
setLoading(true);
const endPoint = "/api/overlay";

doFetch({ apiBaseUrl, managing, scToken }, Method.POST, endPoint).then(fetchOverlays).catch(setErrorMessage).finally(() => setLoading(false));
}

const deleteOverlay = (id: string) => {
setLoading(true);
const endPoint = "/api/overlay";

doFetch({ apiBaseUrl, managing, scToken }, Method.DELETE, endPoint, new URLSearchParams({id})).then(() => setErrorMessage(null)).then(fetchOverlays).catch(setErrorMessage).finally(() => setLoading(false));
}

return [overlays, addOverlay, deleteOverlay, errorMessage, loading];
}


export function useOverlay(id: string): [Overlay|null, boolean] {
const [overlay, setOverlay] = useState<Overlay | null>(null);
const [loading, setLoading] = useState(false);
const managing = useStore(state => state.managing);
const apiBaseUrl = useStore(state => state.apiBaseUrl);
const scToken = useStore(state => state.scToken);

const fetchOverlay = () => {
setLoading(true);
const endPoint = "/api/overlay";

doFetch({ apiBaseUrl, managing, scToken }, Method.GET, endPoint, new URLSearchParams({id})).then(setOverlay).finally(() => setLoading(false));
}

useEffect(fetchOverlay, [id]);

return [overlay, loading];
}

export function useOverlayByRoomId(roomId: string): [Overlay|null, boolean] {
const [overlay, setOverlay] = useState<Overlay | null>(null);
const [loading, setLoading] = useState(false);
const managing = useStore(state => state.managing);
const apiBaseUrl = useStore(state => state.apiBaseUrl);
const scToken = useStore(state => state.scToken);

const fetchOverlay = () => {
setLoading(true);
const endPoint = "/api/overlay";

doFetch({ apiBaseUrl, managing, scToken }, Method.GET, endPoint, new URLSearchParams({roomId})).then(setOverlay).finally(() => setLoading(false));
}

useEffect(fetchOverlay, [roomId]);

return [overlay, loading];
}
File renamed without changes.
15 changes: 5 additions & 10 deletions web/src/pages/overlay/edit/[overlayId].tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
'use client';

import dynamic from "next/dynamic";
import { useParams } from "next/navigation";
const Editor = dynamic(async () => (await import('../../../components/Overlay/Editor')).Editor, { ssr: false })
import { OverlayEditPage } from "../../../components/Overlay/OverlayEditPage";
import { initializeStore } from "../../../service/initializeStore";

export default function OverlaysEditPage() {
const params = useParams<{ overlayId: string }>();
return <OverlayEditPage />
}

return <div className="relative w-full h-[100vh]">
<Editor overlayId={params.overlayId} />
</div>;
}
export const getServerSideProps = initializeStore;

0 comments on commit 8ec1fe1

Please sign in to comment.