Skip to content

Commit

Permalink
Lots of frontend stuff, almost got a custom API endpoint working
Browse files Browse the repository at this point in the history
  • Loading branch information
initialed85 committed Sep 3, 2024
1 parent acd5165 commit 80ae83a
Show file tree
Hide file tree
Showing 61 changed files with 3,267 additions and 851 deletions.
19 changes: 13 additions & 6 deletions build.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/bin/bash

set -e
set -e -m

# this block ensures we can invoke this script from anywhere and have it automatically change to this folder first
pushd "$(dirname -- "${BASH_SOURCE[0]}")" >/dev/null 2>&1
Expand Down Expand Up @@ -42,14 +42,21 @@ done
echo -e "\ngenerating the api..."
DJANGOLANG_API_ROOT=/api DJANGOLANG_PACKAGE_NAME=api POSTGRES_DB=camry POSTGRES_PASSWORD=NoNVR!11 djangolang template

if test -e ./cmd/api; then
rm -frv ./cmd/api
fi
cp -frv ./pkg/api/cmd ./cmd/api
# TODO: you don't want to do this once you put some custom stuff in your api entrypoint
# if test -e ./cmd/api; then
# rm -frv ./cmd/api
# fi
# cp -frv ./pkg/api/cmd ./cmd/api

# dump out the OpenAPI v3 schema for the Djangolang API
mkdir -p ./schema
DJANGOLANG_API_ROOT=/api ./pkg/api/bin/api dump-openapi-json >./schema/openapi.json
# DJANGOLANG_API_ROOT=/api ./pkg/api/bin/api dump-openapi-json >./schema/openapi.json
go build -o ./cmd/api/api ./cmd/api
REDIS_URL=redis://localhost:6379 DJANGOLANG_API_ROOT=/api POSTGRES_DB=camry POSTGRES_PASSWORD=NoNVR\!11 ./cmd/api/api serve &
pid=$!
sleep 5
curl http://localhost:7070/api/openapi.json >./schema/openapi.json
kill -15 ${pid}

# generate the client for use by the frontend
echo -e "\ngenerating typescript client..."
Expand Down
Binary file added cmd/api/api
Binary file not shown.
159 changes: 158 additions & 1 deletion cmd/api/main.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,170 @@
package main

import (
"context"
"fmt"
"log"
"net/http"
"os"
"strings"
"time"

"github.com/go-chi/chi/v5"
"github.com/gomodule/redigo/redis"
"github.com/initialed85/camry/internal"
"github.com/initialed85/camry/pkg/api"
"github.com/initialed85/djangolang/pkg/helpers"
"github.com/initialed85/djangolang/pkg/server"
)

type ClaimRequest struct {
ClaimDurationSeconds float64 `json:"claim_duration_seconds"`
}

func RunServeWithEnvironment(
httpMiddlewares []server.HTTPMiddleware,
objectMiddlewares []server.ObjectMiddleware,
addCustomHandlers func(chi.Router) error,
) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

port, err := helpers.GetPort()
if err != nil {
log.Fatalf("%v", err)
}

db, err := helpers.GetDBFromEnvironment(ctx)
if err != nil {
log.Fatalf("%v", err)
}
defer func() {
db.Close()
}()

go func() {
helpers.WaitForCtrlC(ctx)
cancel()
}()

redisURL, err := helpers.GetRedisURL()
if err != nil {
log.Fatalf("%v", err)
}

redisPool := &redis.Pool{
DialContext: func(ctx context.Context) (redis.Conn, error) {
return redis.DialURLContext(ctx, redisURL)
},
MaxIdle: 2,
MaxActive: 100,
IdleTimeout: 300,
Wait: false,
MaxConnLifetime: 86400,
}

defer func() {
_ = redisPool.Close()
}()

actualAddCustomHandlers := func(r chi.Router) error {
claimVideoForObjectDetectorHandler, err := api.GetCustomHTTPHandler(
http.MethodPatch,
"/claim-video-for-object-detector",
http.StatusOK,
func(
ctx context.Context,
pathParams server.EmptyPathParams,
queryParams server.EmptyQueryParams,
req ClaimRequest,
rawReq any,
) (*api.Video, error) {
now := time.Now().UTC()

claimUntil := now.Add(time.Second * time.Duration(req.ClaimDurationSeconds))

if claimUntil.Sub(now) <= 0 {
return nil, fmt.Errorf("claim_duration_seconds too short; must result in a claim that expires in the future")
}

tx, err := db.Begin(ctx)
if err != nil {
return nil, err
}

defer func() {
_ = tx.Rollback(ctx)
}()

video := &api.Video{}

err = video.LockTable(ctx, tx, false)
if err != nil {
return nil, err
}

videos, _, _, _, _, err := api.SelectVideos(
ctx,
tx,
fmt.Sprintf(
"%v < now()",
api.VideoTableObjectDetectorClaimedUntilColumn,
),
internal.Ptr(fmt.Sprintf(
"%v DESC",
api.VideoTableObjectDetectorClaimedUntilColumn,
)),
internal.Ptr(1),
nil,
)
if err != nil {
return nil, err
}

if len(videos) != 1 {
return nil, fmt.Errorf("wanted exactly 1 unclaimed video, got %d", len(videos))
}

video = videos[0]

video.ObjectDetectorClaimedUntil = claimUntil
video.ObjectTrackerClaimedUntil = time.Time{} // zero to ensure we don't wipe out an existing value

err = video.Update(ctx, tx, false)
if err != nil {
return nil, err
}

err = tx.Commit(ctx)
if err != nil {
return nil, err
}

return video, nil
},
)
if err != nil {
return err
}

r.Patch(
claimVideoForObjectDetectorHandler.Path,
claimVideoForObjectDetectorHandler.ServeHTTP,
)

if addCustomHandlers != nil {
err = addCustomHandlers(r)
if err != nil {
return err
}
}

return nil
}

api.RunServeWithArguments(ctx, cancel, port, db, redisPool, nil, nil, actualAddCustomHandlers)
}

func main() {
if len(os.Args) < 2 {
log.Fatal("first argument must be command (one of 'serve', 'dump-openapi-json', 'dump-openapi-yaml')")
Expand All @@ -24,6 +181,6 @@ func main() {
api.RunDumpOpenAPIYAML()

case "serve":
api.RunServeWithEnvironment()
RunServeWithEnvironment(nil, nil, nil)
}
}
2 changes: 1 addition & 1 deletion cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func main() {
api.RunDumpOpenAPIYAML()

case "serve":
api.RunServeWithEnvironment()
api.RunServeWithEnvironment(nil, nil, nil)

case "segment_producer":
err = segment_producer.Run()
Expand Down
5 changes: 5 additions & 0 deletions database/migrations/00001_initial.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
DROP TABLE public.detection;

DROP TABLE public.video;

DROP TABLE public.camera;
2 changes: 2 additions & 0 deletions database/migrations/00002_more_detection_stuff.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER TABLE video
DROP COLUMN detection_summary;
2 changes: 2 additions & 0 deletions database/migrations/00002_more_detection_stuff.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER TABLE video
ADD COLUMN detection_summary jsonb NOT NULL default '[]'::jsonb;
15 changes: 15 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"prettier": "^3.3.3",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-intersection-observer": "^9.13.0",
"react-scripts": "5.0.1",
"typescript": "^5.5.4",
"use-local-storage-state": "^19.4.0",
Expand Down
79 changes: 53 additions & 26 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import Sheet from "@mui/joy/Sheet";
import Typography from "@mui/joy/Typography";
import { useEffect, useState } from "react";
import useLocalStorageState from "use-local-storage-state";
import CameraToggleButtonGroup from "./components/CameraToggleButtonGroup";
import DateDropdownMenu, { formatDate } from "./components/DateDropdownMenu";
import CameraDropdownMenu from "./components/CameraDropdownMenu";
import DateDropdownMenu from "./components/DateDropdownMenu";
import ModeToggle from "./components/ModeToggle";
import { VideoTable } from "./components/VideoTable";

Expand All @@ -24,18 +24,40 @@ function App() {
// };

const [responsive, setResponsive] = useState(window.innerWidth < 992);
const [cameraId, setCameraId] = useLocalStorageState<
string | null | undefined
>("cameraId", {
defaultValue: undefined,
});
const [date, setDate] = useLocalStorageState<string | null | undefined>(
"date",

const [cameraId, setCameraId] = useLocalStorageState<string | undefined>(
"cameraId",
{
defaultValue: formatDate(new Date()),
defaultValue: undefined,
},
);

const [startedAtGt, setStartedAtGt] = useLocalStorageState<
string | undefined
>("startedAtGt", {
defaultValue: undefined,
});

const [startedAtLte, setStartedAtLte] = useLocalStorageState<
string | undefined
>("startedAtLte", {
defaultValue: undefined,
});

console.log(
"state: ",
JSON.stringify(
{
responsive: responsive,
cameraId: cameraId,
startedAtGt: startedAtGt,
startedAtLte: startedAtLte,
},
null,
2,
),
);

useEffect(() => {
const handleResize = () => {
setResponsive(window.innerWidth < 992);
Expand All @@ -62,42 +84,47 @@ function App() {
}}
>
<Grid container sx={{ pb: 1 }}>
<Grid xs={1}>
<Typography
level="h4"
component="h4"
sx={{ pt: 0.1, textAlign: "center" }}
color="neutral"
>
{responsive ? "C" : "Camry"}
</Typography>
</Grid>
<Grid
xs={10}
xs={11}
sx={{
display: "flex",
alignItems: "center",
justifyContent: "flex-start",
}}
>
<CameraToggleButtonGroup
<Typography
level="h4"
component="h4"
sx={{ pt: 0.1, pl: 0.75, pr: 1, textAlign: "center" }}
color="neutral"
>
{responsive ? "C" : "Camry"}
</Typography>
<CameraDropdownMenu
responsive={responsive}
cameraId={cameraId}
setCameraId={setCameraId}
/>
<DateDropdownMenu
responsive={responsive}
date={date}
setDate={setDate}
startedAtGt={startedAtGt}
setStartedAtGt={setStartedAtGt}
startedAtLte={startedAtLte}
setStartedAtLte={setStartedAtLte}
/>
<Input size="sm" sx={{ mr: 1.5 }} />
<Input size="sm" sx={{ mr: 1.5, width: "100%", maxWidth: 300 }} />
</Grid>
<Grid xs={1} sx={{ display: "flex", justifyContent: "end", pr: 0.5 }}>
<ModeToggle responsive={responsive} />
</Grid>
</Grid>

<VideoTable responsive={responsive} cameraId={cameraId} date={date} />
<VideoTable
responsive={responsive}
cameraId={cameraId}
startedAtGt={startedAtGt}
startedAtLte={startedAtLte}
/>
</Sheet>
);
}
Expand Down
Loading

0 comments on commit 80ae83a

Please sign in to comment.