diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 6299b48b..d32801ba 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,6 +1,6 @@ FROM mcr.microsoft.com/devcontainers/go:1-bullseye -ENV CHAT_DOWNLOADER_VER=0.2.4 +ENV CHAT_DOWNLOADER_VER=0.2.8 RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ && apt-get -y install --no-install-recommends ffmpeg python3 python3-pip \ @@ -13,7 +13,7 @@ WORKDIR /tmp RUN wget https://github.com/rsms/inter/releases/download/v4.0-beta7/Inter-4.0-beta7.zip && unzip Inter-4.0-beta7.zip && mkdir -p /usr/share/fonts/opentype/inter/ && cp /tmp/Desktop/Inter-*.otf /usr/share/fonts/opentype/inter/ && fc-cache -f -v -RUN wget https://github.com/lay295/TwitchDownloader/releases/download/1.53.2/TwitchDownloaderCLI-1.53.2-Linux-x64.zip && unzip TwitchDownloaderCLI-1.53.2-Linux-x64.zip && mv TwitchDownloaderCLI /usr/local/bin/ && chmod +x /usr/local/bin/TwitchDownloaderCLI && rm TwitchDownloaderCLI-1.53.2-Linux-x64.zip +RUN wget https://github.com/lay295/TwitchDownloader/releases/download/1.53.6/TwitchDownloaderCLI-1.53.6-Linux-x64.zip && unzip TwitchDownloaderCLI-1.53.6-Linux-x64.zip && mv TwitchDownloaderCLI /usr/local/bin/ && chmod +x /usr/local/bin/TwitchDownloaderCLI && rm TwitchDownloaderCLI-1.53.6-Linux-x64.zip #RUN wget https://github.com/xenova/chat-downloader/archive/refs/tags/v${CHAT_DOWNLOADER_VER}.tar.gz #RUN tar -xvf v${CHAT_DOWNLOADER_VER}.tar.gz && cd chat-downloader-${CHAT_DOWNLOADER_VER} && python3 setup.py install && cd .. && rm -f v${CHAT_DOWNLOADER_VER}.tar.gz && rm -rf chat-downloader-${CHAT_DOWNLOADER_VER} diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 58e6000a..b2437c86 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,7 +1,7 @@ { "build": { "dockerfile": "Dockerfile" }, "features": { - "ghcr.io/eitsupi/devcontainer-features/go-task:1": {} + "ghcr.io/jungaretti/features/make:1": {} }, "customizations": { "vscode": { diff --git a/.gitignore b/.gitignore index 29d1f681..88be502d 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,5 @@ go.work /cmd/server/__debug_bin dev -tmp \ No newline at end of file +tmp +**/__debug diff --git a/.vscode/launch.json b/.vscode/launch.json index ddcc63dc..3e28194b 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -3,14 +3,28 @@ // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", + "compounds": [ + { + "name": "server/worker", + "configurations": ["dev-server", "dev-worker"] + } + ], "configurations": [ { - "name": "Launch Package", + "name": "dev-server", "type": "go", "request": "launch", "mode": "auto", "program": "${workspaceFolder}/cmd/server/main.go", "envFile": "${workspaceFolder}/.env.dev" + }, + { + "name": "dev-worker", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${workspaceFolder}/cmd/worker/main.go", + "envFile": "${workspaceFolder}/.env.dev" } ] } diff --git a/Dockerfile b/Dockerfile index 250ee87d..61a476e7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,11 @@ -FROM golang:1.20 AS build-stage-01 +FROM golang:1.21 AS build-stage-01 RUN mkdir /app ADD . /app WORKDIR /app RUN CGO_ENABLED=0 GOOS=linux go build -ldflags "-s -X main.Version=${VERSION} -X main.BuildTime=`TZ=UTC date -u '+%Y-%m-%dT%H:%M:%SZ'` -X main.GitHash=`git rev-parse HEAD`" -o ganymede-api cmd/server/main.go +RUN CGO_ENABLED=0 GOOS=linux go build -ldflags "-s -X main.Version=${VERSION} -X main.BuildTime=`TZ=UTC date -u '+%Y-%m-%dT%H:%M:%SZ'` -X main.GitHash=`git rev-parse HEAD`" -o ganymede-worker cmd/worker/main.go FROM alpine:latest AS build-stage-02 @@ -12,16 +13,15 @@ RUN apk add --update --no-cache unzip git WORKDIR /tmp RUN wget https://github.com/rsms/inter/releases/download/v3.19/Inter-3.19.zip && unzip Inter-3.19.zip -RUN wget https://github.com/lay295/TwitchDownloader/releases/download/1.53.2/TwitchDownloaderCLI-1.53.2-LinuxAlpine-x64.zip && unzip TwitchDownloaderCLI-1.53.2-LinuxAlpine-x64.zip +RUN wget https://github.com/lay295/TwitchDownloader/releases/download/1.53.6/TwitchDownloaderCLI-1.53.6-LinuxAlpine-x64.zip && unzip TwitchDownloaderCLI-1.53.6-LinuxAlpine-x64.zip RUN git clone https://github.com/xenova/chat-downloader.git FROM alpine:latest AS production # install packages -RUN apk add --update --no-cache python3 fontconfig icu-libs python3-dev gcc g++ ffmpeg bash tzdata shadow su-exec && ln -sf python3 /usr/bin/python -RUN python3 -m ensurepip -RUN pip3 install --no-cache --upgrade pip streamlink +RUN apk add --update --no-cache python3 fontconfig icu-libs python3-dev gcc g++ ffmpeg bash tzdata shadow su-exec py3-pip && ln -sf python3 /usr/bin/python +RUN pip3 install --no-cache --upgrade pip streamlink --break-system-packages # setup user RUN groupmod -g 1000 users && \ @@ -50,6 +50,7 @@ RUN chmod +x /usr/local/bin/TwitchDownloaderCLI WORKDIR /opt/app COPY --from=build-stage-01 /app/ganymede-api . +COPY --from=build-stage-01 /app/ganymede-worker . EXPOSE 4000 diff --git a/Dockerfile.aarch64 b/Dockerfile.aarch64 index c2f40bdd..da0334d6 100644 --- a/Dockerfile.aarch64 +++ b/Dockerfile.aarch64 @@ -1,10 +1,11 @@ -FROM arm64v8/golang:1.20 AS build-stage-01 +FROM arm64v8/golang:1.21 AS build-stage-01 RUN mkdir /app ADD . /app WORKDIR /app RUN CGO_ENABLED=0 GOOS=linux go build -ldflags "-s -X main.Version=${VERSION} -X main.BuildTime=`TZ=UTC date -u '+%Y-%m-%dT%H:%M:%SZ'` -X main.GitHash=`git rev-parse HEAD`" -o ganymede-api cmd/server/main.go +RUN CGO_ENABLED=0 GOOS=linux go build -ldflags "-s -X main.Version=${VERSION} -X main.BuildTime=`TZ=UTC date -u '+%Y-%m-%dT%H:%M:%SZ'` -X main.GitHash=`git rev-parse HEAD`" -o ganymede-worker cmd/worker/main.go FROM arm64v8/debian:bullseye AS build-stage-02 @@ -13,7 +14,7 @@ RUN apt-get install unzip wget git -y WORKDIR /tmp RUN wget https://github.com/rsms/inter/releases/download/v3.19/Inter-3.19.zip && unzip Inter-3.19.zip -RUN wget https://github.com/lay295/TwitchDownloader/releases/download/1.53.2/TwitchDownloaderCLI-1.53.2-LinuxArm.zip && unzip TwitchDownloaderCLI-1.53.2-LinuxArm.zip +RUN wget https://github.com/lay295/TwitchDownloader/releases/download/1.53.6/TwitchDownloaderCLI-1.53.6-LinuxArm.zip && unzip TwitchDownloaderCLI-1.53.6-LinuxArm.zip RUN git clone https://github.com/xenova/chat-downloader.git @@ -24,7 +25,7 @@ RUN dpkg --add-architecture armhf RUN apt-get update RUN apt-get install python3 python3-pip fontconfig icu-devtools python3-dev gcc libc-dev curl g++ ffmpeg bash tzdata -y -RUN pip3 install --no-cache --upgrade pip streamlink +RUN pip3 install --no-cache --upgrade pip streamlink --break-system-packages ## Installing su-exec in debain/ubuntu container. RUN set -ex; \ @@ -70,6 +71,7 @@ RUN chmod +x /usr/local/bin/TwitchDownloaderCLI WORKDIR /opt/app COPY --from=build-stage-01 /app/ganymede-api . +COPY --from=build-stage-01 /app/ganymede-worker . EXPOSE 4000 diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..c717c4a8 --- /dev/null +++ b/Makefile @@ -0,0 +1,11 @@ +prereq_command: + set -o allexport; source .env.dev; set +o allexport; + +dev_server: prereq_command + go run cmd/server/main.go + +dev_worker: prereq_command + go run cmd/worker/main.go + +ent_generate: + go run -mod=mod entgo.io/ent/cmd/ent generate --feature sql/upsert ./ent/schema \ No newline at end of file diff --git a/Taskfile.yaml b/Taskfile.yaml deleted file mode 100644 index 6aef73ee..00000000 --- a/Taskfile.yaml +++ /dev/null @@ -1,20 +0,0 @@ -version: "3" - -tasks: - swag: - preconditions: - - go install github.com/swaggo/swag/cmd/swag@latest - cmds: - - swag init -g cmd/server/main.go - init: - cmds: - - mkdir -p ./dev/vods - - mkdir -p ./dev/logs - - mkdir -p ./dev/data - ent_generate: - cmds: - - go run -mod=mod entgo.io/ent/cmd/ent generate --feature sql/upsert ./ent/schema - dev: - cmds: - - set -o allexport; source .env.dev; set +o allexport; - - air diff --git a/cmd/server/main.go b/cmd/server/main.go index ed8d5445..8995ba33 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "os" "strconv" "time" @@ -13,6 +14,7 @@ import ( "github.com/zibbp/ganymede/internal/archive" "github.com/zibbp/ganymede/internal/auth" "github.com/zibbp/ganymede/internal/channel" + "github.com/zibbp/ganymede/internal/chapter" "github.com/zibbp/ganymede/internal/config" "github.com/zibbp/ganymede/internal/database" "github.com/zibbp/ganymede/internal/kv" @@ -24,6 +26,7 @@ import ( "github.com/zibbp/ganymede/internal/queue" "github.com/zibbp/ganymede/internal/scheduler" "github.com/zibbp/ganymede/internal/task" + "github.com/zibbp/ganymede/internal/temporal" transportHttp "github.com/zibbp/ganymede/internal/transport/http" "github.com/zibbp/ganymede/internal/twitch" "github.com/zibbp/ganymede/internal/user" @@ -75,9 +78,12 @@ func Run() error { // log.Error().Err(err).Msg("failed to create database connection") // return err //} - database.InitializeDatabase() + database.InitializeDatabase(false) store := database.DB() + // Initialize temporal client + temporal.InitializeTemporalClient() + authService := auth.NewService(store) channelService := channel.NewService(store) vodService := vod.NewService(store) @@ -93,8 +99,9 @@ func Run() error { metricsService := metrics.NewService(store) playlistService := playlist.NewService(store) taskService := task.NewService(store, liveService, archiveService) + chapterService := chapter.NewService() - httpHandler := transportHttp.NewHandler(authService, channelService, vodService, queueService, twitchService, archiveService, adminService, userService, configService, liveService, schedulerService, playbackService, metricsService, playlistService, taskService) + httpHandler := transportHttp.NewHandler(authService, channelService, vodService, queueService, twitchService, archiveService, adminService, userService, configService, liveService, schedulerService, playbackService, metricsService, playlistService, taskService, chapterService) if err := httpHandler.Serve(); err != nil { return err @@ -104,6 +111,9 @@ func Run() error { } func main() { + if os.Getenv("ENV") == "dev" { + log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) + } kv.DB().Set("version", Version) kv.DB().Set("build_time", BuildTime) kv.DB().Set("git_hash", GitHash) diff --git a/cmd/worker/main.go b/cmd/worker/main.go new file mode 100644 index 00000000..25c81808 --- /dev/null +++ b/cmd/worker/main.go @@ -0,0 +1,206 @@ +package main + +import ( + "os" + + "github.com/kelseyhightower/envconfig" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + "github.com/zibbp/ganymede/internal/activities" + serverConfig "github.com/zibbp/ganymede/internal/config" + "github.com/zibbp/ganymede/internal/database" + "github.com/zibbp/ganymede/internal/twitch" + "github.com/zibbp/ganymede/internal/workflows" + + "go.temporal.io/sdk/client" + "go.temporal.io/sdk/worker" +) + +type Config struct { + MAX_CHAT_DOWNLOAD_EXECUTIONS int `default:"5"` + MAX_CHAT_RENDER_EXECUTIONS int `default:"3"` + MAX_VIDEO_DOWNLOAD_EXECUTIONS int `default:"5"` + MAX_VIDEO_CONVERT_EXECUTIONS int `default:"3"` + TEMPORAL_URL string `default:"temporal:7233"` +} + +type Logger struct { + logger *zerolog.Logger +} + +func (l *Logger) Debug(msg string, keyvals ...interface{}) { + if len(keyvals)%2 != 0 { + l.logger.Debug().Msgf(msg) + return + } + + fields := make(map[string]interface{}) + for i := 0; i < len(keyvals); i += 2 { + if key, ok := keyvals[i].(string); ok { + fields[key] = keyvals[i+1] + } + } + + l.logger.Debug().Fields(fields).Msg(msg) +} + +func (l *Logger) Info(msg string, keyvals ...interface{}) { + if len(keyvals)%2 != 0 { + l.logger.Info().Msgf(msg) + return + } + + fields := make(map[string]interface{}) + for i := 0; i < len(keyvals); i += 2 { + if key, ok := keyvals[i].(string); ok { + fields[key] = keyvals[i+1] + } + } + + l.logger.Info().Fields(fields).Msg(msg) +} + +func (l *Logger) Warn(msg string, keyvals ...interface{}) { + if len(keyvals)%2 != 0 { + l.logger.Warn().Msgf(msg) + return + } + + fields := make(map[string]interface{}) + for i := 0; i < len(keyvals); i += 2 { + if key, ok := keyvals[i].(string); ok { + fields[key] = keyvals[i+1] + } + } + + l.logger.Warn().Fields(fields).Msg(msg) +} + +func (l *Logger) Error(msg string, keyvals ...interface{}) { + if len(keyvals)%2 != 0 { + l.logger.Error().Msgf(msg) + return + } + + fields := make(map[string]interface{}) + for i := 0; i < len(keyvals); i += 2 { + if key, ok := keyvals[i].(string); ok { + fields[key] = keyvals[i+1] + } + } + + l.logger.Error().Fields(fields).Msg(msg) +} + +func main() { + if os.Getenv("ENV") == "dev" { + log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) + } + var config Config + err := envconfig.Process("", &config) + if err != nil { + log.Fatal().Msgf("Unable to process environment variables: %v", err) + } + + log.Info().Msgf("Starting worker with config: %+v", config) + + // initializte main program config + // this needs to be removed in the future to decouple the worker from the server + serverConfig.NewConfig() + + logger := zerolog.New(os.Stdout).With().Timestamp().Logger() + + clientOptions := client.Options{ + HostPort: config.TEMPORAL_URL, + Logger: &Logger{logger: &logger}, + } + + c, err := client.Dial(clientOptions) + if err != nil { + log.Fatal().Msgf("Unable to create client: %v", err) + } + defer c.Close() + + // authenticate to Twitch + err = twitch.Authenticate() + if err != nil { + log.Fatal().Msgf("Unable to authenticate to Twitch: %v", err) + } + + database.InitializeDatabase(true) + + taskQueues := map[string]int{ + "archive": 100, + "chat-download": config.MAX_CHAT_DOWNLOAD_EXECUTIONS, + "chat-render": config.MAX_CHAT_RENDER_EXECUTIONS, + "video-download": config.MAX_VIDEO_DOWNLOAD_EXECUTIONS, + "video-convert": config.MAX_VIDEO_CONVERT_EXECUTIONS, + } + + // create worker interrupt channel + interrupt := make(chan os.Signal, 1) + + for queueName, maxActivites := range taskQueues { + hostname, err := os.Hostname() + if err != nil { + log.Fatal().Msgf("Unable to get hostname: %v", err) + } + // create workers + w := worker.New(c, queueName, worker.Options{ + MaxConcurrentActivityExecutionSize: maxActivites, + Identity: hostname, + OnFatalError: func(err error) { + log.Error().Msgf("Worker encountered fatal error: %v", err) + }, + }) + + w.RegisterWorkflow(workflows.ArchiveVideoWorkflow) + w.RegisterWorkflow(workflows.SaveTwitchVideoInfoWorkflow) + w.RegisterWorkflow(workflows.CreateDirectoryWorkflow) + w.RegisterWorkflow(workflows.DownloadTwitchThumbnailsWorkflow) + w.RegisterWorkflow(workflows.ArchiveTwitchVideoWorkflow) + w.RegisterWorkflow(workflows.DownloadTwitchVideoWorkflow) + w.RegisterWorkflow(workflows.PostprocessVideoWorkflow) + w.RegisterWorkflow(workflows.MoveVideoWorkflow) + w.RegisterWorkflow(workflows.ArchiveTwitchChatWorkflow) + w.RegisterWorkflow(workflows.DownloadTwitchChatWorkflow) + w.RegisterWorkflow(workflows.RenderTwitchChatWorkflow) + w.RegisterWorkflow(workflows.MoveTwitchChatWorkflow) + w.RegisterWorkflow(workflows.ArchiveLiveVideoWorkflow) + w.RegisterWorkflow(workflows.ArchiveTwitchLiveVideoWorkflow) + w.RegisterWorkflow(workflows.DownloadTwitchLiveChatWorkflow) + w.RegisterWorkflow(workflows.DownloadTwitchLiveThumbnailsWorkflow) + w.RegisterWorkflow(workflows.DownloadTwitchLiveVideoWorkflow) + w.RegisterWorkflow(workflows.SaveTwitchLiveVideoInfoWorkflow) + w.RegisterWorkflow(workflows.ArchiveTwitchLiveChatWorkflow) + w.RegisterWorkflow(workflows.ConvertTwitchLiveChatWorkflow) + w.RegisterWorkflow(workflows.SaveTwitchVideoChapters) + + w.RegisterActivity(activities.ArchiveVideoActivity) + w.RegisterActivity(activities.SaveTwitchVideoInfo) + w.RegisterActivity(activities.CreateDirectory) + w.RegisterActivity(activities.DownloadTwitchThumbnails) + w.RegisterActivity(activities.DownloadTwitchVideo) + w.RegisterActivity(activities.PostprocessVideo) + w.RegisterActivity(activities.MoveVideo) + w.RegisterActivity(activities.DownloadTwitchChat) + w.RegisterActivity(activities.RenderTwitchChat) + w.RegisterActivity(activities.MoveChat) + w.RegisterActivity(activities.DownloadTwitchLiveChat) + w.RegisterActivity(activities.DownloadTwitchLiveThumbnails) + w.RegisterActivity(activities.DownloadTwitchLiveVideo) + w.RegisterActivity(activities.SaveTwitchLiveVideoInfo) + w.RegisterActivity(activities.KillTwitchLiveChatDownload) + w.RegisterActivity(activities.ConvertTwitchLiveChat) + w.RegisterActivity(activities.TwitchSaveVideoChapters) + + err = w.Start() + if err != nil { + log.Fatal().Msgf("Unable to start worker: %v", err) + } + + } + + <-interrupt + +} diff --git a/ent/channel/where.go b/ent/channel/where.go index 2a8409a6..5427b0cb 100644 --- a/ent/channel/where.go +++ b/ent/channel/where.go @@ -554,32 +554,15 @@ func HasLiveWith(preds ...predicate.Live) predicate.Channel { // And groups predicates with the AND operator between them. func And(predicates ...predicate.Channel) predicate.Channel { - return predicate.Channel(func(s *sql.Selector) { - s1 := s.Clone().SetP(nil) - for _, p := range predicates { - p(s1) - } - s.Where(s1.P()) - }) + return predicate.Channel(sql.AndPredicates(predicates...)) } // Or groups predicates with the OR operator between them. func Or(predicates ...predicate.Channel) predicate.Channel { - return predicate.Channel(func(s *sql.Selector) { - s1 := s.Clone().SetP(nil) - for i, p := range predicates { - if i > 0 { - s1.Or() - } - p(s1) - } - s.Where(s1.P()) - }) + return predicate.Channel(sql.OrPredicates(predicates...)) } // Not applies the not operator on the given predicate. func Not(p predicate.Channel) predicate.Channel { - return predicate.Channel(func(s *sql.Selector) { - p(s.Not()) - }) + return predicate.Channel(sql.NotPredicates(p)) } diff --git a/ent/channel_create.go b/ent/channel_create.go index 92c4dc3c..49530368 100644 --- a/ent/channel_create.go +++ b/ent/channel_create.go @@ -696,12 +696,16 @@ func (u *ChannelUpsertOne) IDX(ctx context.Context) uuid.UUID { // ChannelCreateBulk is the builder for creating many Channel entities in bulk. type ChannelCreateBulk struct { config + err error builders []*ChannelCreate conflict []sql.ConflictOption } // Save creates the Channel entities in the database. func (ccb *ChannelCreateBulk) Save(ctx context.Context) ([]*Channel, error) { + if ccb.err != nil { + return nil, ccb.err + } specs := make([]*sqlgraph.CreateSpec, len(ccb.builders)) nodes := make([]*Channel, len(ccb.builders)) mutators := make([]Mutator, len(ccb.builders)) @@ -990,6 +994,9 @@ func (u *ChannelUpsertBulk) UpdateUpdatedAt() *ChannelUpsertBulk { // Exec executes the query. func (u *ChannelUpsertBulk) Exec(ctx context.Context) error { + if u.create.err != nil { + return u.create.err + } for i, b := range u.create.builders { if len(b.conflict) != 0 { return fmt.Errorf("ent: OnConflict was set for builder %d. Set it on the ChannelCreateBulk instead", i) diff --git a/ent/chapter.go b/ent/chapter.go new file mode 100644 index 00000000..4db1a96d --- /dev/null +++ b/ent/chapter.go @@ -0,0 +1,180 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "fmt" + "strings" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "github.com/google/uuid" + "github.com/zibbp/ganymede/ent/chapter" + "github.com/zibbp/ganymede/ent/vod" +) + +// Chapter is the model entity for the Chapter schema. +type Chapter struct { + config `json:"-"` + // ID of the ent. + ID uuid.UUID `json:"id,omitempty"` + // Type holds the value of the "type" field. + Type string `json:"type,omitempty"` + // Title holds the value of the "title" field. + Title string `json:"title,omitempty"` + // Start holds the value of the "start" field. + Start int `json:"start,omitempty"` + // End holds the value of the "end" field. + End int `json:"end,omitempty"` + // Edges holds the relations/edges for other nodes in the graph. + // The values are being populated by the ChapterQuery when eager-loading is set. + Edges ChapterEdges `json:"edges"` + vod_chapters *uuid.UUID + selectValues sql.SelectValues +} + +// ChapterEdges holds the relations/edges for other nodes in the graph. +type ChapterEdges struct { + // Vod holds the value of the vod edge. + Vod *Vod `json:"vod,omitempty"` + // loadedTypes holds the information for reporting if a + // type was loaded (or requested) in eager-loading or not. + loadedTypes [1]bool +} + +// VodOrErr returns the Vod value or an error if the edge +// was not loaded in eager-loading, or loaded but was not found. +func (e ChapterEdges) VodOrErr() (*Vod, error) { + if e.loadedTypes[0] { + if e.Vod == nil { + // Edge was loaded but was not found. + return nil, &NotFoundError{label: vod.Label} + } + return e.Vod, nil + } + return nil, &NotLoadedError{edge: "vod"} +} + +// scanValues returns the types for scanning values from sql.Rows. +func (*Chapter) scanValues(columns []string) ([]any, error) { + values := make([]any, len(columns)) + for i := range columns { + switch columns[i] { + case chapter.FieldStart, chapter.FieldEnd: + values[i] = new(sql.NullInt64) + case chapter.FieldType, chapter.FieldTitle: + values[i] = new(sql.NullString) + case chapter.FieldID: + values[i] = new(uuid.UUID) + case chapter.ForeignKeys[0]: // vod_chapters + values[i] = &sql.NullScanner{S: new(uuid.UUID)} + default: + values[i] = new(sql.UnknownType) + } + } + return values, nil +} + +// assignValues assigns the values that were returned from sql.Rows (after scanning) +// to the Chapter fields. +func (c *Chapter) assignValues(columns []string, values []any) error { + if m, n := len(values), len(columns); m < n { + return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) + } + for i := range columns { + switch columns[i] { + case chapter.FieldID: + if value, ok := values[i].(*uuid.UUID); !ok { + return fmt.Errorf("unexpected type %T for field id", values[i]) + } else if value != nil { + c.ID = *value + } + case chapter.FieldType: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field type", values[i]) + } else if value.Valid { + c.Type = value.String + } + case chapter.FieldTitle: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field title", values[i]) + } else if value.Valid { + c.Title = value.String + } + case chapter.FieldStart: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field start", values[i]) + } else if value.Valid { + c.Start = int(value.Int64) + } + case chapter.FieldEnd: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field end", values[i]) + } else if value.Valid { + c.End = int(value.Int64) + } + case chapter.ForeignKeys[0]: + if value, ok := values[i].(*sql.NullScanner); !ok { + return fmt.Errorf("unexpected type %T for field vod_chapters", values[i]) + } else if value.Valid { + c.vod_chapters = new(uuid.UUID) + *c.vod_chapters = *value.S.(*uuid.UUID) + } + default: + c.selectValues.Set(columns[i], values[i]) + } + } + return nil +} + +// Value returns the ent.Value that was dynamically selected and assigned to the Chapter. +// This includes values selected through modifiers, order, etc. +func (c *Chapter) Value(name string) (ent.Value, error) { + return c.selectValues.Get(name) +} + +// QueryVod queries the "vod" edge of the Chapter entity. +func (c *Chapter) QueryVod() *VodQuery { + return NewChapterClient(c.config).QueryVod(c) +} + +// Update returns a builder for updating this Chapter. +// Note that you need to call Chapter.Unwrap() before calling this method if this Chapter +// was returned from a transaction, and the transaction was committed or rolled back. +func (c *Chapter) Update() *ChapterUpdateOne { + return NewChapterClient(c.config).UpdateOne(c) +} + +// Unwrap unwraps the Chapter entity that was returned from a transaction after it was closed, +// so that all future queries will be executed through the driver which created the transaction. +func (c *Chapter) Unwrap() *Chapter { + _tx, ok := c.config.driver.(*txDriver) + if !ok { + panic("ent: Chapter is not a transactional entity") + } + c.config.driver = _tx.drv + return c +} + +// String implements the fmt.Stringer. +func (c *Chapter) String() string { + var builder strings.Builder + builder.WriteString("Chapter(") + builder.WriteString(fmt.Sprintf("id=%v, ", c.ID)) + builder.WriteString("type=") + builder.WriteString(c.Type) + builder.WriteString(", ") + builder.WriteString("title=") + builder.WriteString(c.Title) + builder.WriteString(", ") + builder.WriteString("start=") + builder.WriteString(fmt.Sprintf("%v", c.Start)) + builder.WriteString(", ") + builder.WriteString("end=") + builder.WriteString(fmt.Sprintf("%v", c.End)) + builder.WriteByte(')') + return builder.String() +} + +// Chapters is a parsable slice of Chapter. +type Chapters []*Chapter diff --git a/ent/chapter/chapter.go b/ent/chapter/chapter.go new file mode 100644 index 00000000..0d929828 --- /dev/null +++ b/ent/chapter/chapter.go @@ -0,0 +1,112 @@ +// Code generated by ent, DO NOT EDIT. + +package chapter + +import ( + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/google/uuid" +) + +const ( + // Label holds the string label denoting the chapter type in the database. + Label = "chapter" + // FieldID holds the string denoting the id field in the database. + FieldID = "id" + // FieldType holds the string denoting the type field in the database. + FieldType = "type" + // FieldTitle holds the string denoting the title field in the database. + FieldTitle = "title" + // FieldStart holds the string denoting the start field in the database. + FieldStart = "start" + // FieldEnd holds the string denoting the end field in the database. + FieldEnd = "end" + // EdgeVod holds the string denoting the vod edge name in mutations. + EdgeVod = "vod" + // Table holds the table name of the chapter in the database. + Table = "chapters" + // VodTable is the table that holds the vod relation/edge. + VodTable = "chapters" + // VodInverseTable is the table name for the Vod entity. + // It exists in this package in order to avoid circular dependency with the "vod" package. + VodInverseTable = "vods" + // VodColumn is the table column denoting the vod relation/edge. + VodColumn = "vod_chapters" +) + +// Columns holds all SQL columns for chapter fields. +var Columns = []string{ + FieldID, + FieldType, + FieldTitle, + FieldStart, + FieldEnd, +} + +// ForeignKeys holds the SQL foreign-keys that are owned by the "chapters" +// table and are not defined as standalone fields in the schema. +var ForeignKeys = []string{ + "vod_chapters", +} + +// ValidColumn reports if the column name is valid (part of the table columns). +func ValidColumn(column string) bool { + for i := range Columns { + if column == Columns[i] { + return true + } + } + for i := range ForeignKeys { + if column == ForeignKeys[i] { + return true + } + } + return false +} + +var ( + // DefaultID holds the default value on creation for the "id" field. + DefaultID func() uuid.UUID +) + +// OrderOption defines the ordering options for the Chapter queries. +type OrderOption func(*sql.Selector) + +// ByID orders the results by the id field. +func ByID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldID, opts...).ToFunc() +} + +// ByType orders the results by the type field. +func ByType(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldType, opts...).ToFunc() +} + +// ByTitle orders the results by the title field. +func ByTitle(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldTitle, opts...).ToFunc() +} + +// ByStart orders the results by the start field. +func ByStart(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldStart, opts...).ToFunc() +} + +// ByEnd orders the results by the end field. +func ByEnd(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldEnd, opts...).ToFunc() +} + +// ByVodField orders the results by vod field. +func ByVodField(field string, opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newVodStep(), sql.OrderByField(field, opts...)) + } +} +func newVodStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(VodInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, VodTable, VodColumn), + ) +} diff --git a/ent/chapter/where.go b/ent/chapter/where.go new file mode 100644 index 00000000..73747d81 --- /dev/null +++ b/ent/chapter/where.go @@ -0,0 +1,363 @@ +// Code generated by ent, DO NOT EDIT. + +package chapter + +import ( + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/google/uuid" + "github.com/zibbp/ganymede/ent/predicate" +) + +// ID filters vertices based on their ID field. +func ID(id uuid.UUID) predicate.Chapter { + return predicate.Chapter(sql.FieldEQ(FieldID, id)) +} + +// IDEQ applies the EQ predicate on the ID field. +func IDEQ(id uuid.UUID) predicate.Chapter { + return predicate.Chapter(sql.FieldEQ(FieldID, id)) +} + +// IDNEQ applies the NEQ predicate on the ID field. +func IDNEQ(id uuid.UUID) predicate.Chapter { + return predicate.Chapter(sql.FieldNEQ(FieldID, id)) +} + +// IDIn applies the In predicate on the ID field. +func IDIn(ids ...uuid.UUID) predicate.Chapter { + return predicate.Chapter(sql.FieldIn(FieldID, ids...)) +} + +// IDNotIn applies the NotIn predicate on the ID field. +func IDNotIn(ids ...uuid.UUID) predicate.Chapter { + return predicate.Chapter(sql.FieldNotIn(FieldID, ids...)) +} + +// IDGT applies the GT predicate on the ID field. +func IDGT(id uuid.UUID) predicate.Chapter { + return predicate.Chapter(sql.FieldGT(FieldID, id)) +} + +// IDGTE applies the GTE predicate on the ID field. +func IDGTE(id uuid.UUID) predicate.Chapter { + return predicate.Chapter(sql.FieldGTE(FieldID, id)) +} + +// IDLT applies the LT predicate on the ID field. +func IDLT(id uuid.UUID) predicate.Chapter { + return predicate.Chapter(sql.FieldLT(FieldID, id)) +} + +// IDLTE applies the LTE predicate on the ID field. +func IDLTE(id uuid.UUID) predicate.Chapter { + return predicate.Chapter(sql.FieldLTE(FieldID, id)) +} + +// Type applies equality check predicate on the "type" field. It's identical to TypeEQ. +func Type(v string) predicate.Chapter { + return predicate.Chapter(sql.FieldEQ(FieldType, v)) +} + +// Title applies equality check predicate on the "title" field. It's identical to TitleEQ. +func Title(v string) predicate.Chapter { + return predicate.Chapter(sql.FieldEQ(FieldTitle, v)) +} + +// Start applies equality check predicate on the "start" field. It's identical to StartEQ. +func Start(v int) predicate.Chapter { + return predicate.Chapter(sql.FieldEQ(FieldStart, v)) +} + +// End applies equality check predicate on the "end" field. It's identical to EndEQ. +func End(v int) predicate.Chapter { + return predicate.Chapter(sql.FieldEQ(FieldEnd, v)) +} + +// TypeEQ applies the EQ predicate on the "type" field. +func TypeEQ(v string) predicate.Chapter { + return predicate.Chapter(sql.FieldEQ(FieldType, v)) +} + +// TypeNEQ applies the NEQ predicate on the "type" field. +func TypeNEQ(v string) predicate.Chapter { + return predicate.Chapter(sql.FieldNEQ(FieldType, v)) +} + +// TypeIn applies the In predicate on the "type" field. +func TypeIn(vs ...string) predicate.Chapter { + return predicate.Chapter(sql.FieldIn(FieldType, vs...)) +} + +// TypeNotIn applies the NotIn predicate on the "type" field. +func TypeNotIn(vs ...string) predicate.Chapter { + return predicate.Chapter(sql.FieldNotIn(FieldType, vs...)) +} + +// TypeGT applies the GT predicate on the "type" field. +func TypeGT(v string) predicate.Chapter { + return predicate.Chapter(sql.FieldGT(FieldType, v)) +} + +// TypeGTE applies the GTE predicate on the "type" field. +func TypeGTE(v string) predicate.Chapter { + return predicate.Chapter(sql.FieldGTE(FieldType, v)) +} + +// TypeLT applies the LT predicate on the "type" field. +func TypeLT(v string) predicate.Chapter { + return predicate.Chapter(sql.FieldLT(FieldType, v)) +} + +// TypeLTE applies the LTE predicate on the "type" field. +func TypeLTE(v string) predicate.Chapter { + return predicate.Chapter(sql.FieldLTE(FieldType, v)) +} + +// TypeContains applies the Contains predicate on the "type" field. +func TypeContains(v string) predicate.Chapter { + return predicate.Chapter(sql.FieldContains(FieldType, v)) +} + +// TypeHasPrefix applies the HasPrefix predicate on the "type" field. +func TypeHasPrefix(v string) predicate.Chapter { + return predicate.Chapter(sql.FieldHasPrefix(FieldType, v)) +} + +// TypeHasSuffix applies the HasSuffix predicate on the "type" field. +func TypeHasSuffix(v string) predicate.Chapter { + return predicate.Chapter(sql.FieldHasSuffix(FieldType, v)) +} + +// TypeIsNil applies the IsNil predicate on the "type" field. +func TypeIsNil() predicate.Chapter { + return predicate.Chapter(sql.FieldIsNull(FieldType)) +} + +// TypeNotNil applies the NotNil predicate on the "type" field. +func TypeNotNil() predicate.Chapter { + return predicate.Chapter(sql.FieldNotNull(FieldType)) +} + +// TypeEqualFold applies the EqualFold predicate on the "type" field. +func TypeEqualFold(v string) predicate.Chapter { + return predicate.Chapter(sql.FieldEqualFold(FieldType, v)) +} + +// TypeContainsFold applies the ContainsFold predicate on the "type" field. +func TypeContainsFold(v string) predicate.Chapter { + return predicate.Chapter(sql.FieldContainsFold(FieldType, v)) +} + +// TitleEQ applies the EQ predicate on the "title" field. +func TitleEQ(v string) predicate.Chapter { + return predicate.Chapter(sql.FieldEQ(FieldTitle, v)) +} + +// TitleNEQ applies the NEQ predicate on the "title" field. +func TitleNEQ(v string) predicate.Chapter { + return predicate.Chapter(sql.FieldNEQ(FieldTitle, v)) +} + +// TitleIn applies the In predicate on the "title" field. +func TitleIn(vs ...string) predicate.Chapter { + return predicate.Chapter(sql.FieldIn(FieldTitle, vs...)) +} + +// TitleNotIn applies the NotIn predicate on the "title" field. +func TitleNotIn(vs ...string) predicate.Chapter { + return predicate.Chapter(sql.FieldNotIn(FieldTitle, vs...)) +} + +// TitleGT applies the GT predicate on the "title" field. +func TitleGT(v string) predicate.Chapter { + return predicate.Chapter(sql.FieldGT(FieldTitle, v)) +} + +// TitleGTE applies the GTE predicate on the "title" field. +func TitleGTE(v string) predicate.Chapter { + return predicate.Chapter(sql.FieldGTE(FieldTitle, v)) +} + +// TitleLT applies the LT predicate on the "title" field. +func TitleLT(v string) predicate.Chapter { + return predicate.Chapter(sql.FieldLT(FieldTitle, v)) +} + +// TitleLTE applies the LTE predicate on the "title" field. +func TitleLTE(v string) predicate.Chapter { + return predicate.Chapter(sql.FieldLTE(FieldTitle, v)) +} + +// TitleContains applies the Contains predicate on the "title" field. +func TitleContains(v string) predicate.Chapter { + return predicate.Chapter(sql.FieldContains(FieldTitle, v)) +} + +// TitleHasPrefix applies the HasPrefix predicate on the "title" field. +func TitleHasPrefix(v string) predicate.Chapter { + return predicate.Chapter(sql.FieldHasPrefix(FieldTitle, v)) +} + +// TitleHasSuffix applies the HasSuffix predicate on the "title" field. +func TitleHasSuffix(v string) predicate.Chapter { + return predicate.Chapter(sql.FieldHasSuffix(FieldTitle, v)) +} + +// TitleIsNil applies the IsNil predicate on the "title" field. +func TitleIsNil() predicate.Chapter { + return predicate.Chapter(sql.FieldIsNull(FieldTitle)) +} + +// TitleNotNil applies the NotNil predicate on the "title" field. +func TitleNotNil() predicate.Chapter { + return predicate.Chapter(sql.FieldNotNull(FieldTitle)) +} + +// TitleEqualFold applies the EqualFold predicate on the "title" field. +func TitleEqualFold(v string) predicate.Chapter { + return predicate.Chapter(sql.FieldEqualFold(FieldTitle, v)) +} + +// TitleContainsFold applies the ContainsFold predicate on the "title" field. +func TitleContainsFold(v string) predicate.Chapter { + return predicate.Chapter(sql.FieldContainsFold(FieldTitle, v)) +} + +// StartEQ applies the EQ predicate on the "start" field. +func StartEQ(v int) predicate.Chapter { + return predicate.Chapter(sql.FieldEQ(FieldStart, v)) +} + +// StartNEQ applies the NEQ predicate on the "start" field. +func StartNEQ(v int) predicate.Chapter { + return predicate.Chapter(sql.FieldNEQ(FieldStart, v)) +} + +// StartIn applies the In predicate on the "start" field. +func StartIn(vs ...int) predicate.Chapter { + return predicate.Chapter(sql.FieldIn(FieldStart, vs...)) +} + +// StartNotIn applies the NotIn predicate on the "start" field. +func StartNotIn(vs ...int) predicate.Chapter { + return predicate.Chapter(sql.FieldNotIn(FieldStart, vs...)) +} + +// StartGT applies the GT predicate on the "start" field. +func StartGT(v int) predicate.Chapter { + return predicate.Chapter(sql.FieldGT(FieldStart, v)) +} + +// StartGTE applies the GTE predicate on the "start" field. +func StartGTE(v int) predicate.Chapter { + return predicate.Chapter(sql.FieldGTE(FieldStart, v)) +} + +// StartLT applies the LT predicate on the "start" field. +func StartLT(v int) predicate.Chapter { + return predicate.Chapter(sql.FieldLT(FieldStart, v)) +} + +// StartLTE applies the LTE predicate on the "start" field. +func StartLTE(v int) predicate.Chapter { + return predicate.Chapter(sql.FieldLTE(FieldStart, v)) +} + +// StartIsNil applies the IsNil predicate on the "start" field. +func StartIsNil() predicate.Chapter { + return predicate.Chapter(sql.FieldIsNull(FieldStart)) +} + +// StartNotNil applies the NotNil predicate on the "start" field. +func StartNotNil() predicate.Chapter { + return predicate.Chapter(sql.FieldNotNull(FieldStart)) +} + +// EndEQ applies the EQ predicate on the "end" field. +func EndEQ(v int) predicate.Chapter { + return predicate.Chapter(sql.FieldEQ(FieldEnd, v)) +} + +// EndNEQ applies the NEQ predicate on the "end" field. +func EndNEQ(v int) predicate.Chapter { + return predicate.Chapter(sql.FieldNEQ(FieldEnd, v)) +} + +// EndIn applies the In predicate on the "end" field. +func EndIn(vs ...int) predicate.Chapter { + return predicate.Chapter(sql.FieldIn(FieldEnd, vs...)) +} + +// EndNotIn applies the NotIn predicate on the "end" field. +func EndNotIn(vs ...int) predicate.Chapter { + return predicate.Chapter(sql.FieldNotIn(FieldEnd, vs...)) +} + +// EndGT applies the GT predicate on the "end" field. +func EndGT(v int) predicate.Chapter { + return predicate.Chapter(sql.FieldGT(FieldEnd, v)) +} + +// EndGTE applies the GTE predicate on the "end" field. +func EndGTE(v int) predicate.Chapter { + return predicate.Chapter(sql.FieldGTE(FieldEnd, v)) +} + +// EndLT applies the LT predicate on the "end" field. +func EndLT(v int) predicate.Chapter { + return predicate.Chapter(sql.FieldLT(FieldEnd, v)) +} + +// EndLTE applies the LTE predicate on the "end" field. +func EndLTE(v int) predicate.Chapter { + return predicate.Chapter(sql.FieldLTE(FieldEnd, v)) +} + +// EndIsNil applies the IsNil predicate on the "end" field. +func EndIsNil() predicate.Chapter { + return predicate.Chapter(sql.FieldIsNull(FieldEnd)) +} + +// EndNotNil applies the NotNil predicate on the "end" field. +func EndNotNil() predicate.Chapter { + return predicate.Chapter(sql.FieldNotNull(FieldEnd)) +} + +// HasVod applies the HasEdge predicate on the "vod" edge. +func HasVod() predicate.Chapter { + return predicate.Chapter(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, VodTable, VodColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasVodWith applies the HasEdge predicate on the "vod" edge with a given conditions (other predicates). +func HasVodWith(preds ...predicate.Vod) predicate.Chapter { + return predicate.Chapter(func(s *sql.Selector) { + step := newVodStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// And groups predicates with the AND operator between them. +func And(predicates ...predicate.Chapter) predicate.Chapter { + return predicate.Chapter(sql.AndPredicates(predicates...)) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.Chapter) predicate.Chapter { + return predicate.Chapter(sql.OrPredicates(predicates...)) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.Chapter) predicate.Chapter { + return predicate.Chapter(sql.NotPredicates(p)) +} diff --git a/ent/chapter_create.go b/ent/chapter_create.go new file mode 100644 index 00000000..182a8070 --- /dev/null +++ b/ent/chapter_create.go @@ -0,0 +1,839 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect" + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/google/uuid" + "github.com/zibbp/ganymede/ent/chapter" + "github.com/zibbp/ganymede/ent/vod" +) + +// ChapterCreate is the builder for creating a Chapter entity. +type ChapterCreate struct { + config + mutation *ChapterMutation + hooks []Hook + conflict []sql.ConflictOption +} + +// SetType sets the "type" field. +func (cc *ChapterCreate) SetType(s string) *ChapterCreate { + cc.mutation.SetType(s) + return cc +} + +// SetNillableType sets the "type" field if the given value is not nil. +func (cc *ChapterCreate) SetNillableType(s *string) *ChapterCreate { + if s != nil { + cc.SetType(*s) + } + return cc +} + +// SetTitle sets the "title" field. +func (cc *ChapterCreate) SetTitle(s string) *ChapterCreate { + cc.mutation.SetTitle(s) + return cc +} + +// SetNillableTitle sets the "title" field if the given value is not nil. +func (cc *ChapterCreate) SetNillableTitle(s *string) *ChapterCreate { + if s != nil { + cc.SetTitle(*s) + } + return cc +} + +// SetStart sets the "start" field. +func (cc *ChapterCreate) SetStart(i int) *ChapterCreate { + cc.mutation.SetStart(i) + return cc +} + +// SetNillableStart sets the "start" field if the given value is not nil. +func (cc *ChapterCreate) SetNillableStart(i *int) *ChapterCreate { + if i != nil { + cc.SetStart(*i) + } + return cc +} + +// SetEnd sets the "end" field. +func (cc *ChapterCreate) SetEnd(i int) *ChapterCreate { + cc.mutation.SetEnd(i) + return cc +} + +// SetNillableEnd sets the "end" field if the given value is not nil. +func (cc *ChapterCreate) SetNillableEnd(i *int) *ChapterCreate { + if i != nil { + cc.SetEnd(*i) + } + return cc +} + +// SetID sets the "id" field. +func (cc *ChapterCreate) SetID(u uuid.UUID) *ChapterCreate { + cc.mutation.SetID(u) + return cc +} + +// SetNillableID sets the "id" field if the given value is not nil. +func (cc *ChapterCreate) SetNillableID(u *uuid.UUID) *ChapterCreate { + if u != nil { + cc.SetID(*u) + } + return cc +} + +// SetVodID sets the "vod" edge to the Vod entity by ID. +func (cc *ChapterCreate) SetVodID(id uuid.UUID) *ChapterCreate { + cc.mutation.SetVodID(id) + return cc +} + +// SetVod sets the "vod" edge to the Vod entity. +func (cc *ChapterCreate) SetVod(v *Vod) *ChapterCreate { + return cc.SetVodID(v.ID) +} + +// Mutation returns the ChapterMutation object of the builder. +func (cc *ChapterCreate) Mutation() *ChapterMutation { + return cc.mutation +} + +// Save creates the Chapter in the database. +func (cc *ChapterCreate) Save(ctx context.Context) (*Chapter, error) { + cc.defaults() + return withHooks(ctx, cc.sqlSave, cc.mutation, cc.hooks) +} + +// SaveX calls Save and panics if Save returns an error. +func (cc *ChapterCreate) SaveX(ctx context.Context) *Chapter { + v, err := cc.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (cc *ChapterCreate) Exec(ctx context.Context) error { + _, err := cc.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (cc *ChapterCreate) ExecX(ctx context.Context) { + if err := cc.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (cc *ChapterCreate) defaults() { + if _, ok := cc.mutation.ID(); !ok { + v := chapter.DefaultID() + cc.mutation.SetID(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (cc *ChapterCreate) check() error { + if _, ok := cc.mutation.VodID(); !ok { + return &ValidationError{Name: "vod", err: errors.New(`ent: missing required edge "Chapter.vod"`)} + } + return nil +} + +func (cc *ChapterCreate) sqlSave(ctx context.Context) (*Chapter, error) { + if err := cc.check(); err != nil { + return nil, err + } + _node, _spec := cc.createSpec() + if err := sqlgraph.CreateNode(ctx, cc.driver, _spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + if _spec.ID.Value != nil { + if id, ok := _spec.ID.Value.(*uuid.UUID); ok { + _node.ID = *id + } else if err := _node.ID.Scan(_spec.ID.Value); err != nil { + return nil, err + } + } + cc.mutation.id = &_node.ID + cc.mutation.done = true + return _node, nil +} + +func (cc *ChapterCreate) createSpec() (*Chapter, *sqlgraph.CreateSpec) { + var ( + _node = &Chapter{config: cc.config} + _spec = sqlgraph.NewCreateSpec(chapter.Table, sqlgraph.NewFieldSpec(chapter.FieldID, field.TypeUUID)) + ) + _spec.OnConflict = cc.conflict + if id, ok := cc.mutation.ID(); ok { + _node.ID = id + _spec.ID.Value = &id + } + if value, ok := cc.mutation.GetType(); ok { + _spec.SetField(chapter.FieldType, field.TypeString, value) + _node.Type = value + } + if value, ok := cc.mutation.Title(); ok { + _spec.SetField(chapter.FieldTitle, field.TypeString, value) + _node.Title = value + } + if value, ok := cc.mutation.Start(); ok { + _spec.SetField(chapter.FieldStart, field.TypeInt, value) + _node.Start = value + } + if value, ok := cc.mutation.End(); ok { + _spec.SetField(chapter.FieldEnd, field.TypeInt, value) + _node.End = value + } + if nodes := cc.mutation.VodIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: chapter.VodTable, + Columns: []string{chapter.VodColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(vod.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _node.vod_chapters = &nodes[0] + _spec.Edges = append(_spec.Edges, edge) + } + return _node, _spec +} + +// OnConflict allows configuring the `ON CONFLICT` / `ON DUPLICATE KEY` clause +// of the `INSERT` statement. For example: +// +// client.Chapter.Create(). +// SetType(v). +// OnConflict( +// // Update the row with the new values +// // the was proposed for insertion. +// sql.ResolveWithNewValues(), +// ). +// // Override some of the fields with custom +// // update values. +// Update(func(u *ent.ChapterUpsert) { +// SetType(v+v). +// }). +// Exec(ctx) +func (cc *ChapterCreate) OnConflict(opts ...sql.ConflictOption) *ChapterUpsertOne { + cc.conflict = opts + return &ChapterUpsertOne{ + create: cc, + } +} + +// OnConflictColumns calls `OnConflict` and configures the columns +// as conflict target. Using this option is equivalent to using: +// +// client.Chapter.Create(). +// OnConflict(sql.ConflictColumns(columns...)). +// Exec(ctx) +func (cc *ChapterCreate) OnConflictColumns(columns ...string) *ChapterUpsertOne { + cc.conflict = append(cc.conflict, sql.ConflictColumns(columns...)) + return &ChapterUpsertOne{ + create: cc, + } +} + +type ( + // ChapterUpsertOne is the builder for "upsert"-ing + // one Chapter node. + ChapterUpsertOne struct { + create *ChapterCreate + } + + // ChapterUpsert is the "OnConflict" setter. + ChapterUpsert struct { + *sql.UpdateSet + } +) + +// SetType sets the "type" field. +func (u *ChapterUpsert) SetType(v string) *ChapterUpsert { + u.Set(chapter.FieldType, v) + return u +} + +// UpdateType sets the "type" field to the value that was provided on create. +func (u *ChapterUpsert) UpdateType() *ChapterUpsert { + u.SetExcluded(chapter.FieldType) + return u +} + +// ClearType clears the value of the "type" field. +func (u *ChapterUpsert) ClearType() *ChapterUpsert { + u.SetNull(chapter.FieldType) + return u +} + +// SetTitle sets the "title" field. +func (u *ChapterUpsert) SetTitle(v string) *ChapterUpsert { + u.Set(chapter.FieldTitle, v) + return u +} + +// UpdateTitle sets the "title" field to the value that was provided on create. +func (u *ChapterUpsert) UpdateTitle() *ChapterUpsert { + u.SetExcluded(chapter.FieldTitle) + return u +} + +// ClearTitle clears the value of the "title" field. +func (u *ChapterUpsert) ClearTitle() *ChapterUpsert { + u.SetNull(chapter.FieldTitle) + return u +} + +// SetStart sets the "start" field. +func (u *ChapterUpsert) SetStart(v int) *ChapterUpsert { + u.Set(chapter.FieldStart, v) + return u +} + +// UpdateStart sets the "start" field to the value that was provided on create. +func (u *ChapterUpsert) UpdateStart() *ChapterUpsert { + u.SetExcluded(chapter.FieldStart) + return u +} + +// AddStart adds v to the "start" field. +func (u *ChapterUpsert) AddStart(v int) *ChapterUpsert { + u.Add(chapter.FieldStart, v) + return u +} + +// ClearStart clears the value of the "start" field. +func (u *ChapterUpsert) ClearStart() *ChapterUpsert { + u.SetNull(chapter.FieldStart) + return u +} + +// SetEnd sets the "end" field. +func (u *ChapterUpsert) SetEnd(v int) *ChapterUpsert { + u.Set(chapter.FieldEnd, v) + return u +} + +// UpdateEnd sets the "end" field to the value that was provided on create. +func (u *ChapterUpsert) UpdateEnd() *ChapterUpsert { + u.SetExcluded(chapter.FieldEnd) + return u +} + +// AddEnd adds v to the "end" field. +func (u *ChapterUpsert) AddEnd(v int) *ChapterUpsert { + u.Add(chapter.FieldEnd, v) + return u +} + +// ClearEnd clears the value of the "end" field. +func (u *ChapterUpsert) ClearEnd() *ChapterUpsert { + u.SetNull(chapter.FieldEnd) + return u +} + +// UpdateNewValues updates the mutable fields using the new values that were set on create except the ID field. +// Using this option is equivalent to using: +// +// client.Chapter.Create(). +// OnConflict( +// sql.ResolveWithNewValues(), +// sql.ResolveWith(func(u *sql.UpdateSet) { +// u.SetIgnore(chapter.FieldID) +// }), +// ). +// Exec(ctx) +func (u *ChapterUpsertOne) UpdateNewValues() *ChapterUpsertOne { + u.create.conflict = append(u.create.conflict, sql.ResolveWithNewValues()) + u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(s *sql.UpdateSet) { + if _, exists := u.create.mutation.ID(); exists { + s.SetIgnore(chapter.FieldID) + } + })) + return u +} + +// Ignore sets each column to itself in case of conflict. +// Using this option is equivalent to using: +// +// client.Chapter.Create(). +// OnConflict(sql.ResolveWithIgnore()). +// Exec(ctx) +func (u *ChapterUpsertOne) Ignore() *ChapterUpsertOne { + u.create.conflict = append(u.create.conflict, sql.ResolveWithIgnore()) + return u +} + +// DoNothing configures the conflict_action to `DO NOTHING`. +// Supported only by SQLite and PostgreSQL. +func (u *ChapterUpsertOne) DoNothing() *ChapterUpsertOne { + u.create.conflict = append(u.create.conflict, sql.DoNothing()) + return u +} + +// Update allows overriding fields `UPDATE` values. See the ChapterCreate.OnConflict +// documentation for more info. +func (u *ChapterUpsertOne) Update(set func(*ChapterUpsert)) *ChapterUpsertOne { + u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) { + set(&ChapterUpsert{UpdateSet: update}) + })) + return u +} + +// SetType sets the "type" field. +func (u *ChapterUpsertOne) SetType(v string) *ChapterUpsertOne { + return u.Update(func(s *ChapterUpsert) { + s.SetType(v) + }) +} + +// UpdateType sets the "type" field to the value that was provided on create. +func (u *ChapterUpsertOne) UpdateType() *ChapterUpsertOne { + return u.Update(func(s *ChapterUpsert) { + s.UpdateType() + }) +} + +// ClearType clears the value of the "type" field. +func (u *ChapterUpsertOne) ClearType() *ChapterUpsertOne { + return u.Update(func(s *ChapterUpsert) { + s.ClearType() + }) +} + +// SetTitle sets the "title" field. +func (u *ChapterUpsertOne) SetTitle(v string) *ChapterUpsertOne { + return u.Update(func(s *ChapterUpsert) { + s.SetTitle(v) + }) +} + +// UpdateTitle sets the "title" field to the value that was provided on create. +func (u *ChapterUpsertOne) UpdateTitle() *ChapterUpsertOne { + return u.Update(func(s *ChapterUpsert) { + s.UpdateTitle() + }) +} + +// ClearTitle clears the value of the "title" field. +func (u *ChapterUpsertOne) ClearTitle() *ChapterUpsertOne { + return u.Update(func(s *ChapterUpsert) { + s.ClearTitle() + }) +} + +// SetStart sets the "start" field. +func (u *ChapterUpsertOne) SetStart(v int) *ChapterUpsertOne { + return u.Update(func(s *ChapterUpsert) { + s.SetStart(v) + }) +} + +// AddStart adds v to the "start" field. +func (u *ChapterUpsertOne) AddStart(v int) *ChapterUpsertOne { + return u.Update(func(s *ChapterUpsert) { + s.AddStart(v) + }) +} + +// UpdateStart sets the "start" field to the value that was provided on create. +func (u *ChapterUpsertOne) UpdateStart() *ChapterUpsertOne { + return u.Update(func(s *ChapterUpsert) { + s.UpdateStart() + }) +} + +// ClearStart clears the value of the "start" field. +func (u *ChapterUpsertOne) ClearStart() *ChapterUpsertOne { + return u.Update(func(s *ChapterUpsert) { + s.ClearStart() + }) +} + +// SetEnd sets the "end" field. +func (u *ChapterUpsertOne) SetEnd(v int) *ChapterUpsertOne { + return u.Update(func(s *ChapterUpsert) { + s.SetEnd(v) + }) +} + +// AddEnd adds v to the "end" field. +func (u *ChapterUpsertOne) AddEnd(v int) *ChapterUpsertOne { + return u.Update(func(s *ChapterUpsert) { + s.AddEnd(v) + }) +} + +// UpdateEnd sets the "end" field to the value that was provided on create. +func (u *ChapterUpsertOne) UpdateEnd() *ChapterUpsertOne { + return u.Update(func(s *ChapterUpsert) { + s.UpdateEnd() + }) +} + +// ClearEnd clears the value of the "end" field. +func (u *ChapterUpsertOne) ClearEnd() *ChapterUpsertOne { + return u.Update(func(s *ChapterUpsert) { + s.ClearEnd() + }) +} + +// Exec executes the query. +func (u *ChapterUpsertOne) Exec(ctx context.Context) error { + if len(u.create.conflict) == 0 { + return errors.New("ent: missing options for ChapterCreate.OnConflict") + } + return u.create.Exec(ctx) +} + +// ExecX is like Exec, but panics if an error occurs. +func (u *ChapterUpsertOne) ExecX(ctx context.Context) { + if err := u.create.Exec(ctx); err != nil { + panic(err) + } +} + +// Exec executes the UPSERT query and returns the inserted/updated ID. +func (u *ChapterUpsertOne) ID(ctx context.Context) (id uuid.UUID, err error) { + if u.create.driver.Dialect() == dialect.MySQL { + // In case of "ON CONFLICT", there is no way to get back non-numeric ID + // fields from the database since MySQL does not support the RETURNING clause. + return id, errors.New("ent: ChapterUpsertOne.ID is not supported by MySQL driver. Use ChapterUpsertOne.Exec instead") + } + node, err := u.create.Save(ctx) + if err != nil { + return id, err + } + return node.ID, nil +} + +// IDX is like ID, but panics if an error occurs. +func (u *ChapterUpsertOne) IDX(ctx context.Context) uuid.UUID { + id, err := u.ID(ctx) + if err != nil { + panic(err) + } + return id +} + +// ChapterCreateBulk is the builder for creating many Chapter entities in bulk. +type ChapterCreateBulk struct { + config + err error + builders []*ChapterCreate + conflict []sql.ConflictOption +} + +// Save creates the Chapter entities in the database. +func (ccb *ChapterCreateBulk) Save(ctx context.Context) ([]*Chapter, error) { + if ccb.err != nil { + return nil, ccb.err + } + specs := make([]*sqlgraph.CreateSpec, len(ccb.builders)) + nodes := make([]*Chapter, len(ccb.builders)) + mutators := make([]Mutator, len(ccb.builders)) + for i := range ccb.builders { + func(i int, root context.Context) { + builder := ccb.builders[i] + builder.defaults() + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*ChapterMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + if err := builder.check(); err != nil { + return nil, err + } + builder.mutation = mutation + var err error + nodes[i], specs[i] = builder.createSpec() + if i < len(mutators)-1 { + _, err = mutators[i+1].Mutate(root, ccb.builders[i+1].mutation) + } else { + spec := &sqlgraph.BatchCreateSpec{Nodes: specs} + spec.OnConflict = ccb.conflict + // Invoke the actual operation on the latest mutation in the chain. + if err = sqlgraph.BatchCreate(ctx, ccb.driver, spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + } + } + if err != nil { + return nil, err + } + mutation.id = &nodes[i].ID + mutation.done = true + return nodes[i], nil + }) + for i := len(builder.hooks) - 1; i >= 0; i-- { + mut = builder.hooks[i](mut) + } + mutators[i] = mut + }(i, ctx) + } + if len(mutators) > 0 { + if _, err := mutators[0].Mutate(ctx, ccb.builders[0].mutation); err != nil { + return nil, err + } + } + return nodes, nil +} + +// SaveX is like Save, but panics if an error occurs. +func (ccb *ChapterCreateBulk) SaveX(ctx context.Context) []*Chapter { + v, err := ccb.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (ccb *ChapterCreateBulk) Exec(ctx context.Context) error { + _, err := ccb.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (ccb *ChapterCreateBulk) ExecX(ctx context.Context) { + if err := ccb.Exec(ctx); err != nil { + panic(err) + } +} + +// OnConflict allows configuring the `ON CONFLICT` / `ON DUPLICATE KEY` clause +// of the `INSERT` statement. For example: +// +// client.Chapter.CreateBulk(builders...). +// OnConflict( +// // Update the row with the new values +// // the was proposed for insertion. +// sql.ResolveWithNewValues(), +// ). +// // Override some of the fields with custom +// // update values. +// Update(func(u *ent.ChapterUpsert) { +// SetType(v+v). +// }). +// Exec(ctx) +func (ccb *ChapterCreateBulk) OnConflict(opts ...sql.ConflictOption) *ChapterUpsertBulk { + ccb.conflict = opts + return &ChapterUpsertBulk{ + create: ccb, + } +} + +// OnConflictColumns calls `OnConflict` and configures the columns +// as conflict target. Using this option is equivalent to using: +// +// client.Chapter.Create(). +// OnConflict(sql.ConflictColumns(columns...)). +// Exec(ctx) +func (ccb *ChapterCreateBulk) OnConflictColumns(columns ...string) *ChapterUpsertBulk { + ccb.conflict = append(ccb.conflict, sql.ConflictColumns(columns...)) + return &ChapterUpsertBulk{ + create: ccb, + } +} + +// ChapterUpsertBulk is the builder for "upsert"-ing +// a bulk of Chapter nodes. +type ChapterUpsertBulk struct { + create *ChapterCreateBulk +} + +// UpdateNewValues updates the mutable fields using the new values that +// were set on create. Using this option is equivalent to using: +// +// client.Chapter.Create(). +// OnConflict( +// sql.ResolveWithNewValues(), +// sql.ResolveWith(func(u *sql.UpdateSet) { +// u.SetIgnore(chapter.FieldID) +// }), +// ). +// Exec(ctx) +func (u *ChapterUpsertBulk) UpdateNewValues() *ChapterUpsertBulk { + u.create.conflict = append(u.create.conflict, sql.ResolveWithNewValues()) + u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(s *sql.UpdateSet) { + for _, b := range u.create.builders { + if _, exists := b.mutation.ID(); exists { + s.SetIgnore(chapter.FieldID) + } + } + })) + return u +} + +// Ignore sets each column to itself in case of conflict. +// Using this option is equivalent to using: +// +// client.Chapter.Create(). +// OnConflict(sql.ResolveWithIgnore()). +// Exec(ctx) +func (u *ChapterUpsertBulk) Ignore() *ChapterUpsertBulk { + u.create.conflict = append(u.create.conflict, sql.ResolveWithIgnore()) + return u +} + +// DoNothing configures the conflict_action to `DO NOTHING`. +// Supported only by SQLite and PostgreSQL. +func (u *ChapterUpsertBulk) DoNothing() *ChapterUpsertBulk { + u.create.conflict = append(u.create.conflict, sql.DoNothing()) + return u +} + +// Update allows overriding fields `UPDATE` values. See the ChapterCreateBulk.OnConflict +// documentation for more info. +func (u *ChapterUpsertBulk) Update(set func(*ChapterUpsert)) *ChapterUpsertBulk { + u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) { + set(&ChapterUpsert{UpdateSet: update}) + })) + return u +} + +// SetType sets the "type" field. +func (u *ChapterUpsertBulk) SetType(v string) *ChapterUpsertBulk { + return u.Update(func(s *ChapterUpsert) { + s.SetType(v) + }) +} + +// UpdateType sets the "type" field to the value that was provided on create. +func (u *ChapterUpsertBulk) UpdateType() *ChapterUpsertBulk { + return u.Update(func(s *ChapterUpsert) { + s.UpdateType() + }) +} + +// ClearType clears the value of the "type" field. +func (u *ChapterUpsertBulk) ClearType() *ChapterUpsertBulk { + return u.Update(func(s *ChapterUpsert) { + s.ClearType() + }) +} + +// SetTitle sets the "title" field. +func (u *ChapterUpsertBulk) SetTitle(v string) *ChapterUpsertBulk { + return u.Update(func(s *ChapterUpsert) { + s.SetTitle(v) + }) +} + +// UpdateTitle sets the "title" field to the value that was provided on create. +func (u *ChapterUpsertBulk) UpdateTitle() *ChapterUpsertBulk { + return u.Update(func(s *ChapterUpsert) { + s.UpdateTitle() + }) +} + +// ClearTitle clears the value of the "title" field. +func (u *ChapterUpsertBulk) ClearTitle() *ChapterUpsertBulk { + return u.Update(func(s *ChapterUpsert) { + s.ClearTitle() + }) +} + +// SetStart sets the "start" field. +func (u *ChapterUpsertBulk) SetStart(v int) *ChapterUpsertBulk { + return u.Update(func(s *ChapterUpsert) { + s.SetStart(v) + }) +} + +// AddStart adds v to the "start" field. +func (u *ChapterUpsertBulk) AddStart(v int) *ChapterUpsertBulk { + return u.Update(func(s *ChapterUpsert) { + s.AddStart(v) + }) +} + +// UpdateStart sets the "start" field to the value that was provided on create. +func (u *ChapterUpsertBulk) UpdateStart() *ChapterUpsertBulk { + return u.Update(func(s *ChapterUpsert) { + s.UpdateStart() + }) +} + +// ClearStart clears the value of the "start" field. +func (u *ChapterUpsertBulk) ClearStart() *ChapterUpsertBulk { + return u.Update(func(s *ChapterUpsert) { + s.ClearStart() + }) +} + +// SetEnd sets the "end" field. +func (u *ChapterUpsertBulk) SetEnd(v int) *ChapterUpsertBulk { + return u.Update(func(s *ChapterUpsert) { + s.SetEnd(v) + }) +} + +// AddEnd adds v to the "end" field. +func (u *ChapterUpsertBulk) AddEnd(v int) *ChapterUpsertBulk { + return u.Update(func(s *ChapterUpsert) { + s.AddEnd(v) + }) +} + +// UpdateEnd sets the "end" field to the value that was provided on create. +func (u *ChapterUpsertBulk) UpdateEnd() *ChapterUpsertBulk { + return u.Update(func(s *ChapterUpsert) { + s.UpdateEnd() + }) +} + +// ClearEnd clears the value of the "end" field. +func (u *ChapterUpsertBulk) ClearEnd() *ChapterUpsertBulk { + return u.Update(func(s *ChapterUpsert) { + s.ClearEnd() + }) +} + +// Exec executes the query. +func (u *ChapterUpsertBulk) Exec(ctx context.Context) error { + if u.create.err != nil { + return u.create.err + } + for i, b := range u.create.builders { + if len(b.conflict) != 0 { + return fmt.Errorf("ent: OnConflict was set for builder %d. Set it on the ChapterCreateBulk instead", i) + } + } + if len(u.create.conflict) == 0 { + return errors.New("ent: missing options for ChapterCreateBulk.OnConflict") + } + return u.create.Exec(ctx) +} + +// ExecX is like Exec, but panics if an error occurs. +func (u *ChapterUpsertBulk) ExecX(ctx context.Context) { + if err := u.create.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/ent/chapter_delete.go b/ent/chapter_delete.go new file mode 100644 index 00000000..d647e7ed --- /dev/null +++ b/ent/chapter_delete.go @@ -0,0 +1,88 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/zibbp/ganymede/ent/chapter" + "github.com/zibbp/ganymede/ent/predicate" +) + +// ChapterDelete is the builder for deleting a Chapter entity. +type ChapterDelete struct { + config + hooks []Hook + mutation *ChapterMutation +} + +// Where appends a list predicates to the ChapterDelete builder. +func (cd *ChapterDelete) Where(ps ...predicate.Chapter) *ChapterDelete { + cd.mutation.Where(ps...) + return cd +} + +// Exec executes the deletion query and returns how many vertices were deleted. +func (cd *ChapterDelete) Exec(ctx context.Context) (int, error) { + return withHooks(ctx, cd.sqlExec, cd.mutation, cd.hooks) +} + +// ExecX is like Exec, but panics if an error occurs. +func (cd *ChapterDelete) ExecX(ctx context.Context) int { + n, err := cd.Exec(ctx) + if err != nil { + panic(err) + } + return n +} + +func (cd *ChapterDelete) sqlExec(ctx context.Context) (int, error) { + _spec := sqlgraph.NewDeleteSpec(chapter.Table, sqlgraph.NewFieldSpec(chapter.FieldID, field.TypeUUID)) + if ps := cd.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + affected, err := sqlgraph.DeleteNodes(ctx, cd.driver, _spec) + if err != nil && sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + cd.mutation.done = true + return affected, err +} + +// ChapterDeleteOne is the builder for deleting a single Chapter entity. +type ChapterDeleteOne struct { + cd *ChapterDelete +} + +// Where appends a list predicates to the ChapterDelete builder. +func (cdo *ChapterDeleteOne) Where(ps ...predicate.Chapter) *ChapterDeleteOne { + cdo.cd.mutation.Where(ps...) + return cdo +} + +// Exec executes the deletion query. +func (cdo *ChapterDeleteOne) Exec(ctx context.Context) error { + n, err := cdo.cd.Exec(ctx) + switch { + case err != nil: + return err + case n == 0: + return &NotFoundError{chapter.Label} + default: + return nil + } +} + +// ExecX is like Exec, but panics if an error occurs. +func (cdo *ChapterDeleteOne) ExecX(ctx context.Context) { + if err := cdo.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/ent/chapter_query.go b/ent/chapter_query.go new file mode 100644 index 00000000..67be8bb6 --- /dev/null +++ b/ent/chapter_query.go @@ -0,0 +1,614 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "fmt" + "math" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/google/uuid" + "github.com/zibbp/ganymede/ent/chapter" + "github.com/zibbp/ganymede/ent/predicate" + "github.com/zibbp/ganymede/ent/vod" +) + +// ChapterQuery is the builder for querying Chapter entities. +type ChapterQuery struct { + config + ctx *QueryContext + order []chapter.OrderOption + inters []Interceptor + predicates []predicate.Chapter + withVod *VodQuery + withFKs bool + // intermediate query (i.e. traversal path). + sql *sql.Selector + path func(context.Context) (*sql.Selector, error) +} + +// Where adds a new predicate for the ChapterQuery builder. +func (cq *ChapterQuery) Where(ps ...predicate.Chapter) *ChapterQuery { + cq.predicates = append(cq.predicates, ps...) + return cq +} + +// Limit the number of records to be returned by this query. +func (cq *ChapterQuery) Limit(limit int) *ChapterQuery { + cq.ctx.Limit = &limit + return cq +} + +// Offset to start from. +func (cq *ChapterQuery) Offset(offset int) *ChapterQuery { + cq.ctx.Offset = &offset + return cq +} + +// Unique configures the query builder to filter duplicate records on query. +// By default, unique is set to true, and can be disabled using this method. +func (cq *ChapterQuery) Unique(unique bool) *ChapterQuery { + cq.ctx.Unique = &unique + return cq +} + +// Order specifies how the records should be ordered. +func (cq *ChapterQuery) Order(o ...chapter.OrderOption) *ChapterQuery { + cq.order = append(cq.order, o...) + return cq +} + +// QueryVod chains the current query on the "vod" edge. +func (cq *ChapterQuery) QueryVod() *VodQuery { + query := (&VodClient{config: cq.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := cq.prepareQuery(ctx); err != nil { + return nil, err + } + selector := cq.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(chapter.Table, chapter.FieldID, selector), + sqlgraph.To(vod.Table, vod.FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, chapter.VodTable, chapter.VodColumn), + ) + fromU = sqlgraph.SetNeighbors(cq.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// First returns the first Chapter entity from the query. +// Returns a *NotFoundError when no Chapter was found. +func (cq *ChapterQuery) First(ctx context.Context) (*Chapter, error) { + nodes, err := cq.Limit(1).All(setContextOp(ctx, cq.ctx, "First")) + if err != nil { + return nil, err + } + if len(nodes) == 0 { + return nil, &NotFoundError{chapter.Label} + } + return nodes[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (cq *ChapterQuery) FirstX(ctx context.Context) *Chapter { + node, err := cq.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return node +} + +// FirstID returns the first Chapter ID from the query. +// Returns a *NotFoundError when no Chapter ID was found. +func (cq *ChapterQuery) FirstID(ctx context.Context) (id uuid.UUID, err error) { + var ids []uuid.UUID + if ids, err = cq.Limit(1).IDs(setContextOp(ctx, cq.ctx, "FirstID")); err != nil { + return + } + if len(ids) == 0 { + err = &NotFoundError{chapter.Label} + return + } + return ids[0], nil +} + +// FirstIDX is like FirstID, but panics if an error occurs. +func (cq *ChapterQuery) FirstIDX(ctx context.Context) uuid.UUID { + id, err := cq.FirstID(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return id +} + +// Only returns a single Chapter entity found by the query, ensuring it only returns one. +// Returns a *NotSingularError when more than one Chapter entity is found. +// Returns a *NotFoundError when no Chapter entities are found. +func (cq *ChapterQuery) Only(ctx context.Context) (*Chapter, error) { + nodes, err := cq.Limit(2).All(setContextOp(ctx, cq.ctx, "Only")) + if err != nil { + return nil, err + } + switch len(nodes) { + case 1: + return nodes[0], nil + case 0: + return nil, &NotFoundError{chapter.Label} + default: + return nil, &NotSingularError{chapter.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (cq *ChapterQuery) OnlyX(ctx context.Context) *Chapter { + node, err := cq.Only(ctx) + if err != nil { + panic(err) + } + return node +} + +// OnlyID is like Only, but returns the only Chapter ID in the query. +// Returns a *NotSingularError when more than one Chapter ID is found. +// Returns a *NotFoundError when no entities are found. +func (cq *ChapterQuery) OnlyID(ctx context.Context) (id uuid.UUID, err error) { + var ids []uuid.UUID + if ids, err = cq.Limit(2).IDs(setContextOp(ctx, cq.ctx, "OnlyID")); err != nil { + return + } + switch len(ids) { + case 1: + id = ids[0] + case 0: + err = &NotFoundError{chapter.Label} + default: + err = &NotSingularError{chapter.Label} + } + return +} + +// OnlyIDX is like OnlyID, but panics if an error occurs. +func (cq *ChapterQuery) OnlyIDX(ctx context.Context) uuid.UUID { + id, err := cq.OnlyID(ctx) + if err != nil { + panic(err) + } + return id +} + +// All executes the query and returns a list of Chapters. +func (cq *ChapterQuery) All(ctx context.Context) ([]*Chapter, error) { + ctx = setContextOp(ctx, cq.ctx, "All") + if err := cq.prepareQuery(ctx); err != nil { + return nil, err + } + qr := querierAll[[]*Chapter, *ChapterQuery]() + return withInterceptors[[]*Chapter](ctx, cq, qr, cq.inters) +} + +// AllX is like All, but panics if an error occurs. +func (cq *ChapterQuery) AllX(ctx context.Context) []*Chapter { + nodes, err := cq.All(ctx) + if err != nil { + panic(err) + } + return nodes +} + +// IDs executes the query and returns a list of Chapter IDs. +func (cq *ChapterQuery) IDs(ctx context.Context) (ids []uuid.UUID, err error) { + if cq.ctx.Unique == nil && cq.path != nil { + cq.Unique(true) + } + ctx = setContextOp(ctx, cq.ctx, "IDs") + if err = cq.Select(chapter.FieldID).Scan(ctx, &ids); err != nil { + return nil, err + } + return ids, nil +} + +// IDsX is like IDs, but panics if an error occurs. +func (cq *ChapterQuery) IDsX(ctx context.Context) []uuid.UUID { + ids, err := cq.IDs(ctx) + if err != nil { + panic(err) + } + return ids +} + +// Count returns the count of the given query. +func (cq *ChapterQuery) Count(ctx context.Context) (int, error) { + ctx = setContextOp(ctx, cq.ctx, "Count") + if err := cq.prepareQuery(ctx); err != nil { + return 0, err + } + return withInterceptors[int](ctx, cq, querierCount[*ChapterQuery](), cq.inters) +} + +// CountX is like Count, but panics if an error occurs. +func (cq *ChapterQuery) CountX(ctx context.Context) int { + count, err := cq.Count(ctx) + if err != nil { + panic(err) + } + return count +} + +// Exist returns true if the query has elements in the graph. +func (cq *ChapterQuery) Exist(ctx context.Context) (bool, error) { + ctx = setContextOp(ctx, cq.ctx, "Exist") + switch _, err := cq.FirstID(ctx); { + case IsNotFound(err): + return false, nil + case err != nil: + return false, fmt.Errorf("ent: check existence: %w", err) + default: + return true, nil + } +} + +// ExistX is like Exist, but panics if an error occurs. +func (cq *ChapterQuery) ExistX(ctx context.Context) bool { + exist, err := cq.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the ChapterQuery builder, including all associated steps. It can be +// used to prepare common query builders and use them differently after the clone is made. +func (cq *ChapterQuery) Clone() *ChapterQuery { + if cq == nil { + return nil + } + return &ChapterQuery{ + config: cq.config, + ctx: cq.ctx.Clone(), + order: append([]chapter.OrderOption{}, cq.order...), + inters: append([]Interceptor{}, cq.inters...), + predicates: append([]predicate.Chapter{}, cq.predicates...), + withVod: cq.withVod.Clone(), + // clone intermediate query. + sql: cq.sql.Clone(), + path: cq.path, + } +} + +// WithVod tells the query-builder to eager-load the nodes that are connected to +// the "vod" edge. The optional arguments are used to configure the query builder of the edge. +func (cq *ChapterQuery) WithVod(opts ...func(*VodQuery)) *ChapterQuery { + query := (&VodClient{config: cq.config}).Query() + for _, opt := range opts { + opt(query) + } + cq.withVod = query + return cq +} + +// GroupBy is used to group vertices by one or more fields/columns. +// It is often used with aggregate functions, like: count, max, mean, min, sum. +// +// Example: +// +// var v []struct { +// Type string `json:"type,omitempty"` +// Count int `json:"count,omitempty"` +// } +// +// client.Chapter.Query(). +// GroupBy(chapter.FieldType). +// Aggregate(ent.Count()). +// Scan(ctx, &v) +func (cq *ChapterQuery) GroupBy(field string, fields ...string) *ChapterGroupBy { + cq.ctx.Fields = append([]string{field}, fields...) + grbuild := &ChapterGroupBy{build: cq} + grbuild.flds = &cq.ctx.Fields + grbuild.label = chapter.Label + grbuild.scan = grbuild.Scan + return grbuild +} + +// Select allows the selection one or more fields/columns for the given query, +// instead of selecting all fields in the entity. +// +// Example: +// +// var v []struct { +// Type string `json:"type,omitempty"` +// } +// +// client.Chapter.Query(). +// Select(chapter.FieldType). +// Scan(ctx, &v) +func (cq *ChapterQuery) Select(fields ...string) *ChapterSelect { + cq.ctx.Fields = append(cq.ctx.Fields, fields...) + sbuild := &ChapterSelect{ChapterQuery: cq} + sbuild.label = chapter.Label + sbuild.flds, sbuild.scan = &cq.ctx.Fields, sbuild.Scan + return sbuild +} + +// Aggregate returns a ChapterSelect configured with the given aggregations. +func (cq *ChapterQuery) Aggregate(fns ...AggregateFunc) *ChapterSelect { + return cq.Select().Aggregate(fns...) +} + +func (cq *ChapterQuery) prepareQuery(ctx context.Context) error { + for _, inter := range cq.inters { + if inter == nil { + return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)") + } + if trv, ok := inter.(Traverser); ok { + if err := trv.Traverse(ctx, cq); err != nil { + return err + } + } + } + for _, f := range cq.ctx.Fields { + if !chapter.ValidColumn(f) { + return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + } + if cq.path != nil { + prev, err := cq.path(ctx) + if err != nil { + return err + } + cq.sql = prev + } + return nil +} + +func (cq *ChapterQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Chapter, error) { + var ( + nodes = []*Chapter{} + withFKs = cq.withFKs + _spec = cq.querySpec() + loadedTypes = [1]bool{ + cq.withVod != nil, + } + ) + if cq.withVod != nil { + withFKs = true + } + if withFKs { + _spec.Node.Columns = append(_spec.Node.Columns, chapter.ForeignKeys...) + } + _spec.ScanValues = func(columns []string) ([]any, error) { + return (*Chapter).scanValues(nil, columns) + } + _spec.Assign = func(columns []string, values []any) error { + node := &Chapter{config: cq.config} + nodes = append(nodes, node) + node.Edges.loadedTypes = loadedTypes + return node.assignValues(columns, values) + } + for i := range hooks { + hooks[i](ctx, _spec) + } + if err := sqlgraph.QueryNodes(ctx, cq.driver, _spec); err != nil { + return nil, err + } + if len(nodes) == 0 { + return nodes, nil + } + if query := cq.withVod; query != nil { + if err := cq.loadVod(ctx, query, nodes, nil, + func(n *Chapter, e *Vod) { n.Edges.Vod = e }); err != nil { + return nil, err + } + } + return nodes, nil +} + +func (cq *ChapterQuery) loadVod(ctx context.Context, query *VodQuery, nodes []*Chapter, init func(*Chapter), assign func(*Chapter, *Vod)) error { + ids := make([]uuid.UUID, 0, len(nodes)) + nodeids := make(map[uuid.UUID][]*Chapter) + for i := range nodes { + if nodes[i].vod_chapters == nil { + continue + } + fk := *nodes[i].vod_chapters + if _, ok := nodeids[fk]; !ok { + ids = append(ids, fk) + } + nodeids[fk] = append(nodeids[fk], nodes[i]) + } + if len(ids) == 0 { + return nil + } + query.Where(vod.IDIn(ids...)) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + nodes, ok := nodeids[n.ID] + if !ok { + return fmt.Errorf(`unexpected foreign-key "vod_chapters" returned %v`, n.ID) + } + for i := range nodes { + assign(nodes[i], n) + } + } + return nil +} + +func (cq *ChapterQuery) sqlCount(ctx context.Context) (int, error) { + _spec := cq.querySpec() + _spec.Node.Columns = cq.ctx.Fields + if len(cq.ctx.Fields) > 0 { + _spec.Unique = cq.ctx.Unique != nil && *cq.ctx.Unique + } + return sqlgraph.CountNodes(ctx, cq.driver, _spec) +} + +func (cq *ChapterQuery) querySpec() *sqlgraph.QuerySpec { + _spec := sqlgraph.NewQuerySpec(chapter.Table, chapter.Columns, sqlgraph.NewFieldSpec(chapter.FieldID, field.TypeUUID)) + _spec.From = cq.sql + if unique := cq.ctx.Unique; unique != nil { + _spec.Unique = *unique + } else if cq.path != nil { + _spec.Unique = true + } + if fields := cq.ctx.Fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, chapter.FieldID) + for i := range fields { + if fields[i] != chapter.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) + } + } + } + if ps := cq.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if limit := cq.ctx.Limit; limit != nil { + _spec.Limit = *limit + } + if offset := cq.ctx.Offset; offset != nil { + _spec.Offset = *offset + } + if ps := cq.order; len(ps) > 0 { + _spec.Order = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + return _spec +} + +func (cq *ChapterQuery) sqlQuery(ctx context.Context) *sql.Selector { + builder := sql.Dialect(cq.driver.Dialect()) + t1 := builder.Table(chapter.Table) + columns := cq.ctx.Fields + if len(columns) == 0 { + columns = chapter.Columns + } + selector := builder.Select(t1.Columns(columns...)...).From(t1) + if cq.sql != nil { + selector = cq.sql + selector.Select(selector.Columns(columns...)...) + } + if cq.ctx.Unique != nil && *cq.ctx.Unique { + selector.Distinct() + } + for _, p := range cq.predicates { + p(selector) + } + for _, p := range cq.order { + p(selector) + } + if offset := cq.ctx.Offset; offset != nil { + // limit is mandatory for offset clause. We start + // with default value, and override it below if needed. + selector.Offset(*offset).Limit(math.MaxInt32) + } + if limit := cq.ctx.Limit; limit != nil { + selector.Limit(*limit) + } + return selector +} + +// ChapterGroupBy is the group-by builder for Chapter entities. +type ChapterGroupBy struct { + selector + build *ChapterQuery +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (cgb *ChapterGroupBy) Aggregate(fns ...AggregateFunc) *ChapterGroupBy { + cgb.fns = append(cgb.fns, fns...) + return cgb +} + +// Scan applies the selector query and scans the result into the given value. +func (cgb *ChapterGroupBy) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, cgb.build.ctx, "GroupBy") + if err := cgb.build.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*ChapterQuery, *ChapterGroupBy](ctx, cgb.build, cgb, cgb.build.inters, v) +} + +func (cgb *ChapterGroupBy) sqlScan(ctx context.Context, root *ChapterQuery, v any) error { + selector := root.sqlQuery(ctx).Select() + aggregation := make([]string, 0, len(cgb.fns)) + for _, fn := range cgb.fns { + aggregation = append(aggregation, fn(selector)) + } + if len(selector.SelectedColumns()) == 0 { + columns := make([]string, 0, len(*cgb.flds)+len(cgb.fns)) + for _, f := range *cgb.flds { + columns = append(columns, selector.C(f)) + } + columns = append(columns, aggregation...) + selector.Select(columns...) + } + selector.GroupBy(selector.Columns(*cgb.flds...)...) + if err := selector.Err(); err != nil { + return err + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := cgb.build.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} + +// ChapterSelect is the builder for selecting fields of Chapter entities. +type ChapterSelect struct { + *ChapterQuery + selector +} + +// Aggregate adds the given aggregation functions to the selector query. +func (cs *ChapterSelect) Aggregate(fns ...AggregateFunc) *ChapterSelect { + cs.fns = append(cs.fns, fns...) + return cs +} + +// Scan applies the selector query and scans the result into the given value. +func (cs *ChapterSelect) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, cs.ctx, "Select") + if err := cs.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*ChapterQuery, *ChapterSelect](ctx, cs.ChapterQuery, cs, cs.inters, v) +} + +func (cs *ChapterSelect) sqlScan(ctx context.Context, root *ChapterQuery, v any) error { + selector := root.sqlQuery(ctx) + aggregation := make([]string, 0, len(cs.fns)) + for _, fn := range cs.fns { + aggregation = append(aggregation, fn(selector)) + } + switch n := len(*cs.selector.flds); { + case n == 0 && len(aggregation) > 0: + selector.Select(aggregation...) + case n != 0 && len(aggregation) > 0: + selector.AppendSelect(aggregation...) + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := cs.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} diff --git a/ent/chapter_update.go b/ent/chapter_update.go new file mode 100644 index 00000000..22252bd3 --- /dev/null +++ b/ent/chapter_update.go @@ -0,0 +1,539 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/google/uuid" + "github.com/zibbp/ganymede/ent/chapter" + "github.com/zibbp/ganymede/ent/predicate" + "github.com/zibbp/ganymede/ent/vod" +) + +// ChapterUpdate is the builder for updating Chapter entities. +type ChapterUpdate struct { + config + hooks []Hook + mutation *ChapterMutation +} + +// Where appends a list predicates to the ChapterUpdate builder. +func (cu *ChapterUpdate) Where(ps ...predicate.Chapter) *ChapterUpdate { + cu.mutation.Where(ps...) + return cu +} + +// SetType sets the "type" field. +func (cu *ChapterUpdate) SetType(s string) *ChapterUpdate { + cu.mutation.SetType(s) + return cu +} + +// SetNillableType sets the "type" field if the given value is not nil. +func (cu *ChapterUpdate) SetNillableType(s *string) *ChapterUpdate { + if s != nil { + cu.SetType(*s) + } + return cu +} + +// ClearType clears the value of the "type" field. +func (cu *ChapterUpdate) ClearType() *ChapterUpdate { + cu.mutation.ClearType() + return cu +} + +// SetTitle sets the "title" field. +func (cu *ChapterUpdate) SetTitle(s string) *ChapterUpdate { + cu.mutation.SetTitle(s) + return cu +} + +// SetNillableTitle sets the "title" field if the given value is not nil. +func (cu *ChapterUpdate) SetNillableTitle(s *string) *ChapterUpdate { + if s != nil { + cu.SetTitle(*s) + } + return cu +} + +// ClearTitle clears the value of the "title" field. +func (cu *ChapterUpdate) ClearTitle() *ChapterUpdate { + cu.mutation.ClearTitle() + return cu +} + +// SetStart sets the "start" field. +func (cu *ChapterUpdate) SetStart(i int) *ChapterUpdate { + cu.mutation.ResetStart() + cu.mutation.SetStart(i) + return cu +} + +// SetNillableStart sets the "start" field if the given value is not nil. +func (cu *ChapterUpdate) SetNillableStart(i *int) *ChapterUpdate { + if i != nil { + cu.SetStart(*i) + } + return cu +} + +// AddStart adds i to the "start" field. +func (cu *ChapterUpdate) AddStart(i int) *ChapterUpdate { + cu.mutation.AddStart(i) + return cu +} + +// ClearStart clears the value of the "start" field. +func (cu *ChapterUpdate) ClearStart() *ChapterUpdate { + cu.mutation.ClearStart() + return cu +} + +// SetEnd sets the "end" field. +func (cu *ChapterUpdate) SetEnd(i int) *ChapterUpdate { + cu.mutation.ResetEnd() + cu.mutation.SetEnd(i) + return cu +} + +// SetNillableEnd sets the "end" field if the given value is not nil. +func (cu *ChapterUpdate) SetNillableEnd(i *int) *ChapterUpdate { + if i != nil { + cu.SetEnd(*i) + } + return cu +} + +// AddEnd adds i to the "end" field. +func (cu *ChapterUpdate) AddEnd(i int) *ChapterUpdate { + cu.mutation.AddEnd(i) + return cu +} + +// ClearEnd clears the value of the "end" field. +func (cu *ChapterUpdate) ClearEnd() *ChapterUpdate { + cu.mutation.ClearEnd() + return cu +} + +// SetVodID sets the "vod" edge to the Vod entity by ID. +func (cu *ChapterUpdate) SetVodID(id uuid.UUID) *ChapterUpdate { + cu.mutation.SetVodID(id) + return cu +} + +// SetVod sets the "vod" edge to the Vod entity. +func (cu *ChapterUpdate) SetVod(v *Vod) *ChapterUpdate { + return cu.SetVodID(v.ID) +} + +// Mutation returns the ChapterMutation object of the builder. +func (cu *ChapterUpdate) Mutation() *ChapterMutation { + return cu.mutation +} + +// ClearVod clears the "vod" edge to the Vod entity. +func (cu *ChapterUpdate) ClearVod() *ChapterUpdate { + cu.mutation.ClearVod() + return cu +} + +// Save executes the query and returns the number of nodes affected by the update operation. +func (cu *ChapterUpdate) Save(ctx context.Context) (int, error) { + return withHooks(ctx, cu.sqlSave, cu.mutation, cu.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (cu *ChapterUpdate) SaveX(ctx context.Context) int { + affected, err := cu.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (cu *ChapterUpdate) Exec(ctx context.Context) error { + _, err := cu.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (cu *ChapterUpdate) ExecX(ctx context.Context) { + if err := cu.Exec(ctx); err != nil { + panic(err) + } +} + +// check runs all checks and user-defined validators on the builder. +func (cu *ChapterUpdate) check() error { + if _, ok := cu.mutation.VodID(); cu.mutation.VodCleared() && !ok { + return errors.New(`ent: clearing a required unique edge "Chapter.vod"`) + } + return nil +} + +func (cu *ChapterUpdate) sqlSave(ctx context.Context) (n int, err error) { + if err := cu.check(); err != nil { + return n, err + } + _spec := sqlgraph.NewUpdateSpec(chapter.Table, chapter.Columns, sqlgraph.NewFieldSpec(chapter.FieldID, field.TypeUUID)) + if ps := cu.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := cu.mutation.GetType(); ok { + _spec.SetField(chapter.FieldType, field.TypeString, value) + } + if cu.mutation.TypeCleared() { + _spec.ClearField(chapter.FieldType, field.TypeString) + } + if value, ok := cu.mutation.Title(); ok { + _spec.SetField(chapter.FieldTitle, field.TypeString, value) + } + if cu.mutation.TitleCleared() { + _spec.ClearField(chapter.FieldTitle, field.TypeString) + } + if value, ok := cu.mutation.Start(); ok { + _spec.SetField(chapter.FieldStart, field.TypeInt, value) + } + if value, ok := cu.mutation.AddedStart(); ok { + _spec.AddField(chapter.FieldStart, field.TypeInt, value) + } + if cu.mutation.StartCleared() { + _spec.ClearField(chapter.FieldStart, field.TypeInt) + } + if value, ok := cu.mutation.End(); ok { + _spec.SetField(chapter.FieldEnd, field.TypeInt, value) + } + if value, ok := cu.mutation.AddedEnd(); ok { + _spec.AddField(chapter.FieldEnd, field.TypeInt, value) + } + if cu.mutation.EndCleared() { + _spec.ClearField(chapter.FieldEnd, field.TypeInt) + } + if cu.mutation.VodCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: chapter.VodTable, + Columns: []string{chapter.VodColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(vod.FieldID, field.TypeUUID), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := cu.mutation.VodIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: chapter.VodTable, + Columns: []string{chapter.VodColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(vod.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if n, err = sqlgraph.UpdateNodes(ctx, cu.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{chapter.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return 0, err + } + cu.mutation.done = true + return n, nil +} + +// ChapterUpdateOne is the builder for updating a single Chapter entity. +type ChapterUpdateOne struct { + config + fields []string + hooks []Hook + mutation *ChapterMutation +} + +// SetType sets the "type" field. +func (cuo *ChapterUpdateOne) SetType(s string) *ChapterUpdateOne { + cuo.mutation.SetType(s) + return cuo +} + +// SetNillableType sets the "type" field if the given value is not nil. +func (cuo *ChapterUpdateOne) SetNillableType(s *string) *ChapterUpdateOne { + if s != nil { + cuo.SetType(*s) + } + return cuo +} + +// ClearType clears the value of the "type" field. +func (cuo *ChapterUpdateOne) ClearType() *ChapterUpdateOne { + cuo.mutation.ClearType() + return cuo +} + +// SetTitle sets the "title" field. +func (cuo *ChapterUpdateOne) SetTitle(s string) *ChapterUpdateOne { + cuo.mutation.SetTitle(s) + return cuo +} + +// SetNillableTitle sets the "title" field if the given value is not nil. +func (cuo *ChapterUpdateOne) SetNillableTitle(s *string) *ChapterUpdateOne { + if s != nil { + cuo.SetTitle(*s) + } + return cuo +} + +// ClearTitle clears the value of the "title" field. +func (cuo *ChapterUpdateOne) ClearTitle() *ChapterUpdateOne { + cuo.mutation.ClearTitle() + return cuo +} + +// SetStart sets the "start" field. +func (cuo *ChapterUpdateOne) SetStart(i int) *ChapterUpdateOne { + cuo.mutation.ResetStart() + cuo.mutation.SetStart(i) + return cuo +} + +// SetNillableStart sets the "start" field if the given value is not nil. +func (cuo *ChapterUpdateOne) SetNillableStart(i *int) *ChapterUpdateOne { + if i != nil { + cuo.SetStart(*i) + } + return cuo +} + +// AddStart adds i to the "start" field. +func (cuo *ChapterUpdateOne) AddStart(i int) *ChapterUpdateOne { + cuo.mutation.AddStart(i) + return cuo +} + +// ClearStart clears the value of the "start" field. +func (cuo *ChapterUpdateOne) ClearStart() *ChapterUpdateOne { + cuo.mutation.ClearStart() + return cuo +} + +// SetEnd sets the "end" field. +func (cuo *ChapterUpdateOne) SetEnd(i int) *ChapterUpdateOne { + cuo.mutation.ResetEnd() + cuo.mutation.SetEnd(i) + return cuo +} + +// SetNillableEnd sets the "end" field if the given value is not nil. +func (cuo *ChapterUpdateOne) SetNillableEnd(i *int) *ChapterUpdateOne { + if i != nil { + cuo.SetEnd(*i) + } + return cuo +} + +// AddEnd adds i to the "end" field. +func (cuo *ChapterUpdateOne) AddEnd(i int) *ChapterUpdateOne { + cuo.mutation.AddEnd(i) + return cuo +} + +// ClearEnd clears the value of the "end" field. +func (cuo *ChapterUpdateOne) ClearEnd() *ChapterUpdateOne { + cuo.mutation.ClearEnd() + return cuo +} + +// SetVodID sets the "vod" edge to the Vod entity by ID. +func (cuo *ChapterUpdateOne) SetVodID(id uuid.UUID) *ChapterUpdateOne { + cuo.mutation.SetVodID(id) + return cuo +} + +// SetVod sets the "vod" edge to the Vod entity. +func (cuo *ChapterUpdateOne) SetVod(v *Vod) *ChapterUpdateOne { + return cuo.SetVodID(v.ID) +} + +// Mutation returns the ChapterMutation object of the builder. +func (cuo *ChapterUpdateOne) Mutation() *ChapterMutation { + return cuo.mutation +} + +// ClearVod clears the "vod" edge to the Vod entity. +func (cuo *ChapterUpdateOne) ClearVod() *ChapterUpdateOne { + cuo.mutation.ClearVod() + return cuo +} + +// Where appends a list predicates to the ChapterUpdate builder. +func (cuo *ChapterUpdateOne) Where(ps ...predicate.Chapter) *ChapterUpdateOne { + cuo.mutation.Where(ps...) + return cuo +} + +// Select allows selecting one or more fields (columns) of the returned entity. +// The default is selecting all fields defined in the entity schema. +func (cuo *ChapterUpdateOne) Select(field string, fields ...string) *ChapterUpdateOne { + cuo.fields = append([]string{field}, fields...) + return cuo +} + +// Save executes the query and returns the updated Chapter entity. +func (cuo *ChapterUpdateOne) Save(ctx context.Context) (*Chapter, error) { + return withHooks(ctx, cuo.sqlSave, cuo.mutation, cuo.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (cuo *ChapterUpdateOne) SaveX(ctx context.Context) *Chapter { + node, err := cuo.Save(ctx) + if err != nil { + panic(err) + } + return node +} + +// Exec executes the query on the entity. +func (cuo *ChapterUpdateOne) Exec(ctx context.Context) error { + _, err := cuo.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (cuo *ChapterUpdateOne) ExecX(ctx context.Context) { + if err := cuo.Exec(ctx); err != nil { + panic(err) + } +} + +// check runs all checks and user-defined validators on the builder. +func (cuo *ChapterUpdateOne) check() error { + if _, ok := cuo.mutation.VodID(); cuo.mutation.VodCleared() && !ok { + return errors.New(`ent: clearing a required unique edge "Chapter.vod"`) + } + return nil +} + +func (cuo *ChapterUpdateOne) sqlSave(ctx context.Context) (_node *Chapter, err error) { + if err := cuo.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(chapter.Table, chapter.Columns, sqlgraph.NewFieldSpec(chapter.FieldID, field.TypeUUID)) + id, ok := cuo.mutation.ID() + if !ok { + return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "Chapter.id" for update`)} + } + _spec.Node.ID.Value = id + if fields := cuo.fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, chapter.FieldID) + for _, f := range fields { + if !chapter.ValidColumn(f) { + return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + if f != chapter.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, f) + } + } + } + if ps := cuo.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := cuo.mutation.GetType(); ok { + _spec.SetField(chapter.FieldType, field.TypeString, value) + } + if cuo.mutation.TypeCleared() { + _spec.ClearField(chapter.FieldType, field.TypeString) + } + if value, ok := cuo.mutation.Title(); ok { + _spec.SetField(chapter.FieldTitle, field.TypeString, value) + } + if cuo.mutation.TitleCleared() { + _spec.ClearField(chapter.FieldTitle, field.TypeString) + } + if value, ok := cuo.mutation.Start(); ok { + _spec.SetField(chapter.FieldStart, field.TypeInt, value) + } + if value, ok := cuo.mutation.AddedStart(); ok { + _spec.AddField(chapter.FieldStart, field.TypeInt, value) + } + if cuo.mutation.StartCleared() { + _spec.ClearField(chapter.FieldStart, field.TypeInt) + } + if value, ok := cuo.mutation.End(); ok { + _spec.SetField(chapter.FieldEnd, field.TypeInt, value) + } + if value, ok := cuo.mutation.AddedEnd(); ok { + _spec.AddField(chapter.FieldEnd, field.TypeInt, value) + } + if cuo.mutation.EndCleared() { + _spec.ClearField(chapter.FieldEnd, field.TypeInt) + } + if cuo.mutation.VodCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: chapter.VodTable, + Columns: []string{chapter.VodColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(vod.FieldID, field.TypeUUID), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := cuo.mutation.VodIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: chapter.VodTable, + Columns: []string{chapter.VodColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(vod.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + _node = &Chapter{config: cuo.config} + _spec.Assign = _node.assignValues + _spec.ScanValues = _node.scanValues + if err = sqlgraph.UpdateNode(ctx, cuo.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{chapter.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + cuo.mutation.done = true + return _node, nil +} diff --git a/ent/client.go b/ent/client.go index 8f9143c7..7dc74c0a 100644 --- a/ent/client.go +++ b/ent/client.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "log" + "reflect" "github.com/google/uuid" "github.com/zibbp/ganymede/ent/migrate" @@ -16,6 +17,7 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "github.com/zibbp/ganymede/ent/channel" + "github.com/zibbp/ganymede/ent/chapter" "github.com/zibbp/ganymede/ent/live" "github.com/zibbp/ganymede/ent/livecategory" "github.com/zibbp/ganymede/ent/playback" @@ -33,6 +35,8 @@ type Client struct { Schema *migrate.Schema // Channel is the client for interacting with the Channel builders. Channel *ChannelClient + // Chapter is the client for interacting with the Chapter builders. + Chapter *ChapterClient // Live is the client for interacting with the Live builders. Live *LiveClient // LiveCategory is the client for interacting with the LiveCategory builders. @@ -63,6 +67,7 @@ func NewClient(opts ...Option) *Client { func (c *Client) init() { c.Schema = migrate.NewSchema(c.driver) c.Channel = NewChannelClient(c.config) + c.Chapter = NewChapterClient(c.config) c.Live = NewLiveClient(c.config) c.LiveCategory = NewLiveCategoryClient(c.config) c.Playback = NewPlaybackClient(c.config) @@ -138,11 +143,14 @@ func Open(driverName, dataSourceName string, options ...Option) (*Client, error) } } +// ErrTxStarted is returned when trying to start a new transaction from a transactional client. +var ErrTxStarted = errors.New("ent: cannot start a transaction within a transaction") + // Tx returns a new transactional client. The provided context // is used until the transaction is committed or rolled back. func (c *Client) Tx(ctx context.Context) (*Tx, error) { if _, ok := c.driver.(*txDriver); ok { - return nil, errors.New("ent: cannot start a transaction within a transaction") + return nil, ErrTxStarted } tx, err := newTx(ctx, c.driver) if err != nil { @@ -154,6 +162,7 @@ func (c *Client) Tx(ctx context.Context) (*Tx, error) { ctx: ctx, config: cfg, Channel: NewChannelClient(cfg), + Chapter: NewChapterClient(cfg), Live: NewLiveClient(cfg), LiveCategory: NewLiveCategoryClient(cfg), Playback: NewPlaybackClient(cfg), @@ -182,6 +191,7 @@ func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) ctx: ctx, config: cfg, Channel: NewChannelClient(cfg), + Chapter: NewChapterClient(cfg), Live: NewLiveClient(cfg), LiveCategory: NewLiveCategoryClient(cfg), Playback: NewPlaybackClient(cfg), @@ -219,7 +229,7 @@ func (c *Client) Close() error { // In order to add hooks to a specific client, call: `client.Node.Use(...)`. func (c *Client) Use(hooks ...Hook) { for _, n := range []interface{ Use(...Hook) }{ - c.Channel, c.Live, c.LiveCategory, c.Playback, c.Playlist, c.Queue, + c.Channel, c.Chapter, c.Live, c.LiveCategory, c.Playback, c.Playlist, c.Queue, c.TwitchCategory, c.User, c.Vod, } { n.Use(hooks...) @@ -230,7 +240,7 @@ func (c *Client) Use(hooks ...Hook) { // In order to add interceptors to a specific client, call: `client.Node.Intercept(...)`. func (c *Client) Intercept(interceptors ...Interceptor) { for _, n := range []interface{ Intercept(...Interceptor) }{ - c.Channel, c.Live, c.LiveCategory, c.Playback, c.Playlist, c.Queue, + c.Channel, c.Chapter, c.Live, c.LiveCategory, c.Playback, c.Playlist, c.Queue, c.TwitchCategory, c.User, c.Vod, } { n.Intercept(interceptors...) @@ -242,6 +252,8 @@ func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) { switch m := m.(type) { case *ChannelMutation: return c.Channel.mutate(ctx, m) + case *ChapterMutation: + return c.Chapter.mutate(ctx, m) case *LiveMutation: return c.Live.mutate(ctx, m) case *LiveCategoryMutation: @@ -296,6 +308,21 @@ func (c *ChannelClient) CreateBulk(builders ...*ChannelCreate) *ChannelCreateBul return &ChannelCreateBulk{config: c.config, builders: builders} } +// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates +// a builder and applies setFunc on it. +func (c *ChannelClient) MapCreateBulk(slice any, setFunc func(*ChannelCreate, int)) *ChannelCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &ChannelCreateBulk{err: fmt.Errorf("calling to ChannelClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*ChannelCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &ChannelCreateBulk{config: c.config, builders: builders} +} + // Update returns an update builder for Channel. func (c *ChannelClient) Update() *ChannelUpdate { mutation := newChannelMutation(c.config, OpUpdate) @@ -413,6 +440,155 @@ func (c *ChannelClient) mutate(ctx context.Context, m *ChannelMutation) (Value, } } +// ChapterClient is a client for the Chapter schema. +type ChapterClient struct { + config +} + +// NewChapterClient returns a client for the Chapter from the given config. +func NewChapterClient(c config) *ChapterClient { + return &ChapterClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `chapter.Hooks(f(g(h())))`. +func (c *ChapterClient) Use(hooks ...Hook) { + c.hooks.Chapter = append(c.hooks.Chapter, hooks...) +} + +// Intercept adds a list of query interceptors to the interceptors stack. +// A call to `Intercept(f, g, h)` equals to `chapter.Intercept(f(g(h())))`. +func (c *ChapterClient) Intercept(interceptors ...Interceptor) { + c.inters.Chapter = append(c.inters.Chapter, interceptors...) +} + +// Create returns a builder for creating a Chapter entity. +func (c *ChapterClient) Create() *ChapterCreate { + mutation := newChapterMutation(c.config, OpCreate) + return &ChapterCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of Chapter entities. +func (c *ChapterClient) CreateBulk(builders ...*ChapterCreate) *ChapterCreateBulk { + return &ChapterCreateBulk{config: c.config, builders: builders} +} + +// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates +// a builder and applies setFunc on it. +func (c *ChapterClient) MapCreateBulk(slice any, setFunc func(*ChapterCreate, int)) *ChapterCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &ChapterCreateBulk{err: fmt.Errorf("calling to ChapterClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*ChapterCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &ChapterCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for Chapter. +func (c *ChapterClient) Update() *ChapterUpdate { + mutation := newChapterMutation(c.config, OpUpdate) + return &ChapterUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *ChapterClient) UpdateOne(ch *Chapter) *ChapterUpdateOne { + mutation := newChapterMutation(c.config, OpUpdateOne, withChapter(ch)) + return &ChapterUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOneID returns an update builder for the given id. +func (c *ChapterClient) UpdateOneID(id uuid.UUID) *ChapterUpdateOne { + mutation := newChapterMutation(c.config, OpUpdateOne, withChapterID(id)) + return &ChapterUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for Chapter. +func (c *ChapterClient) Delete() *ChapterDelete { + mutation := newChapterMutation(c.config, OpDelete) + return &ChapterDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// DeleteOne returns a builder for deleting the given entity. +func (c *ChapterClient) DeleteOne(ch *Chapter) *ChapterDeleteOne { + return c.DeleteOneID(ch.ID) +} + +// DeleteOneID returns a builder for deleting the given entity by its id. +func (c *ChapterClient) DeleteOneID(id uuid.UUID) *ChapterDeleteOne { + builder := c.Delete().Where(chapter.ID(id)) + builder.mutation.id = &id + builder.mutation.op = OpDeleteOne + return &ChapterDeleteOne{builder} +} + +// Query returns a query builder for Chapter. +func (c *ChapterClient) Query() *ChapterQuery { + return &ChapterQuery{ + config: c.config, + ctx: &QueryContext{Type: TypeChapter}, + inters: c.Interceptors(), + } +} + +// Get returns a Chapter entity by its id. +func (c *ChapterClient) Get(ctx context.Context, id uuid.UUID) (*Chapter, error) { + return c.Query().Where(chapter.ID(id)).Only(ctx) +} + +// GetX is like Get, but panics if an error occurs. +func (c *ChapterClient) GetX(ctx context.Context, id uuid.UUID) *Chapter { + obj, err := c.Get(ctx, id) + if err != nil { + panic(err) + } + return obj +} + +// QueryVod queries the vod edge of a Chapter. +func (c *ChapterClient) QueryVod(ch *Chapter) *VodQuery { + query := (&VodClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := ch.ID + step := sqlgraph.NewStep( + sqlgraph.From(chapter.Table, chapter.FieldID, id), + sqlgraph.To(vod.Table, vod.FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, chapter.VodTable, chapter.VodColumn), + ) + fromV = sqlgraph.Neighbors(ch.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// Hooks returns the client hooks. +func (c *ChapterClient) Hooks() []Hook { + return c.hooks.Chapter +} + +// Interceptors returns the client interceptors. +func (c *ChapterClient) Interceptors() []Interceptor { + return c.inters.Chapter +} + +func (c *ChapterClient) mutate(ctx context.Context, m *ChapterMutation) (Value, error) { + switch m.Op() { + case OpCreate: + return (&ChapterCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdate: + return (&ChapterUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdateOne: + return (&ChapterUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpDelete, OpDeleteOne: + return (&ChapterDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) + default: + return nil, fmt.Errorf("ent: unknown Chapter mutation op: %q", m.Op()) + } +} + // LiveClient is a client for the Live schema. type LiveClient struct { config @@ -446,6 +622,21 @@ func (c *LiveClient) CreateBulk(builders ...*LiveCreate) *LiveCreateBulk { return &LiveCreateBulk{config: c.config, builders: builders} } +// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates +// a builder and applies setFunc on it. +func (c *LiveClient) MapCreateBulk(slice any, setFunc func(*LiveCreate, int)) *LiveCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &LiveCreateBulk{err: fmt.Errorf("calling to LiveClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*LiveCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &LiveCreateBulk{config: c.config, builders: builders} +} + // Update returns an update builder for Live. func (c *LiveClient) Update() *LiveUpdate { mutation := newLiveMutation(c.config, OpUpdate) @@ -596,6 +787,21 @@ func (c *LiveCategoryClient) CreateBulk(builders ...*LiveCategoryCreate) *LiveCa return &LiveCategoryCreateBulk{config: c.config, builders: builders} } +// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates +// a builder and applies setFunc on it. +func (c *LiveCategoryClient) MapCreateBulk(slice any, setFunc func(*LiveCategoryCreate, int)) *LiveCategoryCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &LiveCategoryCreateBulk{err: fmt.Errorf("calling to LiveCategoryClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*LiveCategoryCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &LiveCategoryCreateBulk{config: c.config, builders: builders} +} + // Update returns an update builder for LiveCategory. func (c *LiveCategoryClient) Update() *LiveCategoryUpdate { mutation := newLiveCategoryMutation(c.config, OpUpdate) @@ -730,6 +936,21 @@ func (c *PlaybackClient) CreateBulk(builders ...*PlaybackCreate) *PlaybackCreate return &PlaybackCreateBulk{config: c.config, builders: builders} } +// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates +// a builder and applies setFunc on it. +func (c *PlaybackClient) MapCreateBulk(slice any, setFunc func(*PlaybackCreate, int)) *PlaybackCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &PlaybackCreateBulk{err: fmt.Errorf("calling to PlaybackClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*PlaybackCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &PlaybackCreateBulk{config: c.config, builders: builders} +} + // Update returns an update builder for Playback. func (c *PlaybackClient) Update() *PlaybackUpdate { mutation := newPlaybackMutation(c.config, OpUpdate) @@ -848,6 +1069,21 @@ func (c *PlaylistClient) CreateBulk(builders ...*PlaylistCreate) *PlaylistCreate return &PlaylistCreateBulk{config: c.config, builders: builders} } +// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates +// a builder and applies setFunc on it. +func (c *PlaylistClient) MapCreateBulk(slice any, setFunc func(*PlaylistCreate, int)) *PlaylistCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &PlaylistCreateBulk{err: fmt.Errorf("calling to PlaylistClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*PlaylistCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &PlaylistCreateBulk{config: c.config, builders: builders} +} + // Update returns an update builder for Playlist. func (c *PlaylistClient) Update() *PlaylistUpdate { mutation := newPlaylistMutation(c.config, OpUpdate) @@ -982,6 +1218,21 @@ func (c *QueueClient) CreateBulk(builders ...*QueueCreate) *QueueCreateBulk { return &QueueCreateBulk{config: c.config, builders: builders} } +// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates +// a builder and applies setFunc on it. +func (c *QueueClient) MapCreateBulk(slice any, setFunc func(*QueueCreate, int)) *QueueCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &QueueCreateBulk{err: fmt.Errorf("calling to QueueClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*QueueCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &QueueCreateBulk{config: c.config, builders: builders} +} + // Update returns an update builder for Queue. func (c *QueueClient) Update() *QueueUpdate { mutation := newQueueMutation(c.config, OpUpdate) @@ -1116,6 +1367,21 @@ func (c *TwitchCategoryClient) CreateBulk(builders ...*TwitchCategoryCreate) *Tw return &TwitchCategoryCreateBulk{config: c.config, builders: builders} } +// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates +// a builder and applies setFunc on it. +func (c *TwitchCategoryClient) MapCreateBulk(slice any, setFunc func(*TwitchCategoryCreate, int)) *TwitchCategoryCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &TwitchCategoryCreateBulk{err: fmt.Errorf("calling to TwitchCategoryClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*TwitchCategoryCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &TwitchCategoryCreateBulk{config: c.config, builders: builders} +} + // Update returns an update builder for TwitchCategory. func (c *TwitchCategoryClient) Update() *TwitchCategoryUpdate { mutation := newTwitchCategoryMutation(c.config, OpUpdate) @@ -1234,6 +1500,21 @@ func (c *UserClient) CreateBulk(builders ...*UserCreate) *UserCreateBulk { return &UserCreateBulk{config: c.config, builders: builders} } +// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates +// a builder and applies setFunc on it. +func (c *UserClient) MapCreateBulk(slice any, setFunc func(*UserCreate, int)) *UserCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &UserCreateBulk{err: fmt.Errorf("calling to UserClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*UserCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &UserCreateBulk{config: c.config, builders: builders} +} + // Update returns an update builder for User. func (c *UserClient) Update() *UserUpdate { mutation := newUserMutation(c.config, OpUpdate) @@ -1352,6 +1633,21 @@ func (c *VodClient) CreateBulk(builders ...*VodCreate) *VodCreateBulk { return &VodCreateBulk{config: c.config, builders: builders} } +// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates +// a builder and applies setFunc on it. +func (c *VodClient) MapCreateBulk(slice any, setFunc func(*VodCreate, int)) *VodCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &VodCreateBulk{err: fmt.Errorf("calling to VodClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*VodCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &VodCreateBulk{config: c.config, builders: builders} +} + // Update returns an update builder for Vod. func (c *VodClient) Update() *VodUpdate { mutation := newVodMutation(c.config, OpUpdate) @@ -1460,6 +1756,22 @@ func (c *VodClient) QueryPlaylists(v *Vod) *PlaylistQuery { return query } +// QueryChapters queries the chapters edge of a Vod. +func (c *VodClient) QueryChapters(v *Vod) *ChapterQuery { + query := (&ChapterClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := v.ID + step := sqlgraph.NewStep( + sqlgraph.From(vod.Table, vod.FieldID, id), + sqlgraph.To(chapter.Table, chapter.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, vod.ChaptersTable, vod.ChaptersColumn), + ) + fromV = sqlgraph.Neighbors(v.driver.Dialect(), step) + return fromV, nil + } + return query +} + // Hooks returns the client hooks. func (c *VodClient) Hooks() []Hook { return c.hooks.Vod @@ -1488,11 +1800,11 @@ func (c *VodClient) mutate(ctx context.Context, m *VodMutation) (Value, error) { // hooks and interceptors per client, for fast access. type ( hooks struct { - Channel, Live, LiveCategory, Playback, Playlist, Queue, TwitchCategory, User, - Vod []ent.Hook + Channel, Chapter, Live, LiveCategory, Playback, Playlist, Queue, TwitchCategory, + User, Vod []ent.Hook } inters struct { - Channel, Live, LiveCategory, Playback, Playlist, Queue, TwitchCategory, User, - Vod []ent.Interceptor + Channel, Chapter, Live, LiveCategory, Playback, Playlist, Queue, TwitchCategory, + User, Vod []ent.Interceptor } ) diff --git a/ent/ent.go b/ent/ent.go index 924e610d..cff52f04 100644 --- a/ent/ent.go +++ b/ent/ent.go @@ -13,6 +13,7 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "github.com/zibbp/ganymede/ent/channel" + "github.com/zibbp/ganymede/ent/chapter" "github.com/zibbp/ganymede/ent/live" "github.com/zibbp/ganymede/ent/livecategory" "github.com/zibbp/ganymede/ent/playback" @@ -82,6 +83,7 @@ func checkColumn(table, column string) error { initCheck.Do(func() { columnCheck = sql.NewColumnCheck(map[string]func(string) bool{ channel.Table: channel.ValidColumn, + chapter.Table: chapter.ValidColumn, live.Table: live.ValidColumn, livecategory.Table: livecategory.ValidColumn, playback.Table: playback.ValidColumn, diff --git a/ent/hook/hook.go b/ent/hook/hook.go index 285e57c5..ec02d921 100644 --- a/ent/hook/hook.go +++ b/ent/hook/hook.go @@ -21,6 +21,18 @@ func (f ChannelFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, err return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.ChannelMutation", m) } +// The ChapterFunc type is an adapter to allow the use of ordinary +// function as Chapter mutator. +type ChapterFunc func(context.Context, *ent.ChapterMutation) (ent.Value, error) + +// Mutate calls f(ctx, m). +func (f ChapterFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) { + if mv, ok := m.(*ent.ChapterMutation); ok { + return f(ctx, mv) + } + return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.ChapterMutation", m) +} + // The LiveFunc type is an adapter to allow the use of ordinary // function as Live mutator. type LiveFunc func(context.Context, *ent.LiveMutation) (ent.Value, error) diff --git a/ent/live/where.go b/ent/live/where.go index 0eea0e3b..0965ea6b 100644 --- a/ent/live/where.go +++ b/ent/live/where.go @@ -454,32 +454,15 @@ func HasCategoriesWith(preds ...predicate.LiveCategory) predicate.Live { // And groups predicates with the AND operator between them. func And(predicates ...predicate.Live) predicate.Live { - return predicate.Live(func(s *sql.Selector) { - s1 := s.Clone().SetP(nil) - for _, p := range predicates { - p(s1) - } - s.Where(s1.P()) - }) + return predicate.Live(sql.AndPredicates(predicates...)) } // Or groups predicates with the OR operator between them. func Or(predicates ...predicate.Live) predicate.Live { - return predicate.Live(func(s *sql.Selector) { - s1 := s.Clone().SetP(nil) - for i, p := range predicates { - if i > 0 { - s1.Or() - } - p(s1) - } - s.Where(s1.P()) - }) + return predicate.Live(sql.OrPredicates(predicates...)) } // Not applies the not operator on the given predicate. func Not(p predicate.Live) predicate.Live { - return predicate.Live(func(s *sql.Selector) { - p(s.Not()) - }) + return predicate.Live(sql.NotPredicates(p)) } diff --git a/ent/live_create.go b/ent/live_create.go index 1665e60c..42ac15a4 100644 --- a/ent/live_create.go +++ b/ent/live_create.go @@ -972,12 +972,16 @@ func (u *LiveUpsertOne) IDX(ctx context.Context) uuid.UUID { // LiveCreateBulk is the builder for creating many Live entities in bulk. type LiveCreateBulk struct { config + err error builders []*LiveCreate conflict []sql.ConflictOption } // Save creates the Live entities in the database. func (lcb *LiveCreateBulk) Save(ctx context.Context) ([]*Live, error) { + if lcb.err != nil { + return nil, lcb.err + } specs := make([]*sqlgraph.CreateSpec, len(lcb.builders)) nodes := make([]*Live, len(lcb.builders)) mutators := make([]Mutator, len(lcb.builders)) @@ -1322,6 +1326,9 @@ func (u *LiveUpsertBulk) UpdateUpdatedAt() *LiveUpsertBulk { // Exec executes the query. func (u *LiveUpsertBulk) Exec(ctx context.Context) error { + if u.create.err != nil { + return u.create.err + } for i, b := range u.create.builders { if len(b.conflict) != 0 { return fmt.Errorf("ent: OnConflict was set for builder %d. Set it on the LiveCreateBulk instead", i) diff --git a/ent/livecategory/where.go b/ent/livecategory/where.go index 2038e3cd..84fcdb89 100644 --- a/ent/livecategory/where.go +++ b/ent/livecategory/where.go @@ -149,32 +149,15 @@ func HasLiveWith(preds ...predicate.Live) predicate.LiveCategory { // And groups predicates with the AND operator between them. func And(predicates ...predicate.LiveCategory) predicate.LiveCategory { - return predicate.LiveCategory(func(s *sql.Selector) { - s1 := s.Clone().SetP(nil) - for _, p := range predicates { - p(s1) - } - s.Where(s1.P()) - }) + return predicate.LiveCategory(sql.AndPredicates(predicates...)) } // Or groups predicates with the OR operator between them. func Or(predicates ...predicate.LiveCategory) predicate.LiveCategory { - return predicate.LiveCategory(func(s *sql.Selector) { - s1 := s.Clone().SetP(nil) - for i, p := range predicates { - if i > 0 { - s1.Or() - } - p(s1) - } - s.Where(s1.P()) - }) + return predicate.LiveCategory(sql.OrPredicates(predicates...)) } // Not applies the not operator on the given predicate. func Not(p predicate.LiveCategory) predicate.LiveCategory { - return predicate.LiveCategory(func(s *sql.Selector) { - p(s.Not()) - }) + return predicate.LiveCategory(sql.NotPredicates(p)) } diff --git a/ent/livecategory_create.go b/ent/livecategory_create.go index 976d16da..06da505d 100644 --- a/ent/livecategory_create.go +++ b/ent/livecategory_create.go @@ -328,12 +328,16 @@ func (u *LiveCategoryUpsertOne) IDX(ctx context.Context) uuid.UUID { // LiveCategoryCreateBulk is the builder for creating many LiveCategory entities in bulk. type LiveCategoryCreateBulk struct { config + err error builders []*LiveCategoryCreate conflict []sql.ConflictOption } // Save creates the LiveCategory entities in the database. func (lccb *LiveCategoryCreateBulk) Save(ctx context.Context) ([]*LiveCategory, error) { + if lccb.err != nil { + return nil, lccb.err + } specs := make([]*sqlgraph.CreateSpec, len(lccb.builders)) nodes := make([]*LiveCategory, len(lccb.builders)) mutators := make([]Mutator, len(lccb.builders)) @@ -514,6 +518,9 @@ func (u *LiveCategoryUpsertBulk) UpdateName() *LiveCategoryUpsertBulk { // Exec executes the query. func (u *LiveCategoryUpsertBulk) Exec(ctx context.Context) error { + if u.create.err != nil { + return u.create.err + } for i, b := range u.create.builders { if len(b.conflict) != 0 { return fmt.Errorf("ent: OnConflict was set for builder %d. Set it on the LiveCategoryCreateBulk instead", i) diff --git a/ent/migrate/schema.go b/ent/migrate/schema.go index bf40c760..02d0122d 100644 --- a/ent/migrate/schema.go +++ b/ent/migrate/schema.go @@ -26,6 +26,29 @@ var ( Columns: ChannelsColumns, PrimaryKey: []*schema.Column{ChannelsColumns[0]}, } + // ChaptersColumns holds the columns for the "chapters" table. + ChaptersColumns = []*schema.Column{ + {Name: "id", Type: field.TypeUUID}, + {Name: "type", Type: field.TypeString, Nullable: true}, + {Name: "title", Type: field.TypeString, Nullable: true}, + {Name: "start", Type: field.TypeInt, Nullable: true}, + {Name: "end", Type: field.TypeInt, Nullable: true}, + {Name: "vod_chapters", Type: field.TypeUUID}, + } + // ChaptersTable holds the schema information for the "chapters" table. + ChaptersTable = &schema.Table{ + Name: "chapters", + Columns: ChaptersColumns, + PrimaryKey: []*schema.Column{ChaptersColumns[0]}, + ForeignKeys: []*schema.ForeignKey{ + { + Symbol: "chapters_vods_chapters", + Columns: []*schema.Column{ChaptersColumns[5]}, + RefColumns: []*schema.Column{VodsColumns[0]}, + OnDelete: schema.NoAction, + }, + }, + } // LivesColumns holds the columns for the "lives" table. LivesColumns = []*schema.Column{ {Name: "id", Type: field.TypeUUID}, @@ -249,6 +272,7 @@ var ( // Tables holds all the tables in the schema. Tables = []*schema.Table{ ChannelsTable, + ChaptersTable, LivesTable, LiveCategoriesTable, PlaybacksTable, @@ -262,6 +286,7 @@ var ( ) func init() { + ChaptersTable.ForeignKeys[0].RefTable = VodsTable LivesTable.ForeignKeys[0].RefTable = ChannelsTable LiveCategoriesTable.ForeignKeys[0].RefTable = LivesTable QueuesTable.ForeignKeys[0].RefTable = VodsTable diff --git a/ent/mutation.go b/ent/mutation.go index cbd0dfa5..62cb4df9 100644 --- a/ent/mutation.go +++ b/ent/mutation.go @@ -13,6 +13,7 @@ import ( "entgo.io/ent/dialect/sql" "github.com/google/uuid" "github.com/zibbp/ganymede/ent/channel" + "github.com/zibbp/ganymede/ent/chapter" "github.com/zibbp/ganymede/ent/live" "github.com/zibbp/ganymede/ent/livecategory" "github.com/zibbp/ganymede/ent/playback" @@ -35,6 +36,7 @@ const ( // Node types. TypeChannel = "Channel" + TypeChapter = "Chapter" TypeLive = "Live" TypeLiveCategory = "LiveCategory" TypePlayback = "Playback" @@ -1009,6 +1011,717 @@ func (m *ChannelMutation) ResetEdge(name string) error { return fmt.Errorf("unknown Channel edge %s", name) } +// ChapterMutation represents an operation that mutates the Chapter nodes in the graph. +type ChapterMutation struct { + config + op Op + typ string + id *uuid.UUID + _type *string + title *string + start *int + addstart *int + end *int + addend *int + clearedFields map[string]struct{} + vod *uuid.UUID + clearedvod bool + done bool + oldValue func(context.Context) (*Chapter, error) + predicates []predicate.Chapter +} + +var _ ent.Mutation = (*ChapterMutation)(nil) + +// chapterOption allows management of the mutation configuration using functional options. +type chapterOption func(*ChapterMutation) + +// newChapterMutation creates new mutation for the Chapter entity. +func newChapterMutation(c config, op Op, opts ...chapterOption) *ChapterMutation { + m := &ChapterMutation{ + config: c, + op: op, + typ: TypeChapter, + clearedFields: make(map[string]struct{}), + } + for _, opt := range opts { + opt(m) + } + return m +} + +// withChapterID sets the ID field of the mutation. +func withChapterID(id uuid.UUID) chapterOption { + return func(m *ChapterMutation) { + var ( + err error + once sync.Once + value *Chapter + ) + m.oldValue = func(ctx context.Context) (*Chapter, error) { + once.Do(func() { + if m.done { + err = errors.New("querying old values post mutation is not allowed") + } else { + value, err = m.Client().Chapter.Get(ctx, id) + } + }) + return value, err + } + m.id = &id + } +} + +// withChapter sets the old Chapter of the mutation. +func withChapter(node *Chapter) chapterOption { + return func(m *ChapterMutation) { + m.oldValue = func(context.Context) (*Chapter, error) { + return node, nil + } + m.id = &node.ID + } +} + +// Client returns a new `ent.Client` from the mutation. If the mutation was +// executed in a transaction (ent.Tx), a transactional client is returned. +func (m ChapterMutation) Client() *Client { + client := &Client{config: m.config} + client.init() + return client +} + +// Tx returns an `ent.Tx` for mutations that were executed in transactions; +// it returns an error otherwise. +func (m ChapterMutation) Tx() (*Tx, error) { + if _, ok := m.driver.(*txDriver); !ok { + return nil, errors.New("ent: mutation is not running in a transaction") + } + tx := &Tx{config: m.config} + tx.init() + return tx, nil +} + +// SetID sets the value of the id field. Note that this +// operation is only accepted on creation of Chapter entities. +func (m *ChapterMutation) SetID(id uuid.UUID) { + m.id = &id +} + +// ID returns the ID value in the mutation. Note that the ID is only available +// if it was provided to the builder or after it was returned from the database. +func (m *ChapterMutation) ID() (id uuid.UUID, exists bool) { + if m.id == nil { + return + } + return *m.id, true +} + +// IDs queries the database and returns the entity ids that match the mutation's predicate. +// That means, if the mutation is applied within a transaction with an isolation level such +// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated +// or updated by the mutation. +func (m *ChapterMutation) IDs(ctx context.Context) ([]uuid.UUID, error) { + switch { + case m.op.Is(OpUpdateOne | OpDeleteOne): + id, exists := m.ID() + if exists { + return []uuid.UUID{id}, nil + } + fallthrough + case m.op.Is(OpUpdate | OpDelete): + return m.Client().Chapter.Query().Where(m.predicates...).IDs(ctx) + default: + return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) + } +} + +// SetType sets the "type" field. +func (m *ChapterMutation) SetType(s string) { + m._type = &s +} + +// GetType returns the value of the "type" field in the mutation. +func (m *ChapterMutation) GetType() (r string, exists bool) { + v := m._type + if v == nil { + return + } + return *v, true +} + +// OldType returns the old "type" field's value of the Chapter entity. +// If the Chapter object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *ChapterMutation) OldType(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldType is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldType requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldType: %w", err) + } + return oldValue.Type, nil +} + +// ClearType clears the value of the "type" field. +func (m *ChapterMutation) ClearType() { + m._type = nil + m.clearedFields[chapter.FieldType] = struct{}{} +} + +// TypeCleared returns if the "type" field was cleared in this mutation. +func (m *ChapterMutation) TypeCleared() bool { + _, ok := m.clearedFields[chapter.FieldType] + return ok +} + +// ResetType resets all changes to the "type" field. +func (m *ChapterMutation) ResetType() { + m._type = nil + delete(m.clearedFields, chapter.FieldType) +} + +// SetTitle sets the "title" field. +func (m *ChapterMutation) SetTitle(s string) { + m.title = &s +} + +// Title returns the value of the "title" field in the mutation. +func (m *ChapterMutation) Title() (r string, exists bool) { + v := m.title + if v == nil { + return + } + return *v, true +} + +// OldTitle returns the old "title" field's value of the Chapter entity. +// If the Chapter object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *ChapterMutation) OldTitle(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldTitle is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldTitle requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldTitle: %w", err) + } + return oldValue.Title, nil +} + +// ClearTitle clears the value of the "title" field. +func (m *ChapterMutation) ClearTitle() { + m.title = nil + m.clearedFields[chapter.FieldTitle] = struct{}{} +} + +// TitleCleared returns if the "title" field was cleared in this mutation. +func (m *ChapterMutation) TitleCleared() bool { + _, ok := m.clearedFields[chapter.FieldTitle] + return ok +} + +// ResetTitle resets all changes to the "title" field. +func (m *ChapterMutation) ResetTitle() { + m.title = nil + delete(m.clearedFields, chapter.FieldTitle) +} + +// SetStart sets the "start" field. +func (m *ChapterMutation) SetStart(i int) { + m.start = &i + m.addstart = nil +} + +// Start returns the value of the "start" field in the mutation. +func (m *ChapterMutation) Start() (r int, exists bool) { + v := m.start + if v == nil { + return + } + return *v, true +} + +// OldStart returns the old "start" field's value of the Chapter entity. +// If the Chapter object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *ChapterMutation) OldStart(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldStart is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldStart requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldStart: %w", err) + } + return oldValue.Start, nil +} + +// AddStart adds i to the "start" field. +func (m *ChapterMutation) AddStart(i int) { + if m.addstart != nil { + *m.addstart += i + } else { + m.addstart = &i + } +} + +// AddedStart returns the value that was added to the "start" field in this mutation. +func (m *ChapterMutation) AddedStart() (r int, exists bool) { + v := m.addstart + if v == nil { + return + } + return *v, true +} + +// ClearStart clears the value of the "start" field. +func (m *ChapterMutation) ClearStart() { + m.start = nil + m.addstart = nil + m.clearedFields[chapter.FieldStart] = struct{}{} +} + +// StartCleared returns if the "start" field was cleared in this mutation. +func (m *ChapterMutation) StartCleared() bool { + _, ok := m.clearedFields[chapter.FieldStart] + return ok +} + +// ResetStart resets all changes to the "start" field. +func (m *ChapterMutation) ResetStart() { + m.start = nil + m.addstart = nil + delete(m.clearedFields, chapter.FieldStart) +} + +// SetEnd sets the "end" field. +func (m *ChapterMutation) SetEnd(i int) { + m.end = &i + m.addend = nil +} + +// End returns the value of the "end" field in the mutation. +func (m *ChapterMutation) End() (r int, exists bool) { + v := m.end + if v == nil { + return + } + return *v, true +} + +// OldEnd returns the old "end" field's value of the Chapter entity. +// If the Chapter object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *ChapterMutation) OldEnd(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldEnd is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldEnd requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldEnd: %w", err) + } + return oldValue.End, nil +} + +// AddEnd adds i to the "end" field. +func (m *ChapterMutation) AddEnd(i int) { + if m.addend != nil { + *m.addend += i + } else { + m.addend = &i + } +} + +// AddedEnd returns the value that was added to the "end" field in this mutation. +func (m *ChapterMutation) AddedEnd() (r int, exists bool) { + v := m.addend + if v == nil { + return + } + return *v, true +} + +// ClearEnd clears the value of the "end" field. +func (m *ChapterMutation) ClearEnd() { + m.end = nil + m.addend = nil + m.clearedFields[chapter.FieldEnd] = struct{}{} +} + +// EndCleared returns if the "end" field was cleared in this mutation. +func (m *ChapterMutation) EndCleared() bool { + _, ok := m.clearedFields[chapter.FieldEnd] + return ok +} + +// ResetEnd resets all changes to the "end" field. +func (m *ChapterMutation) ResetEnd() { + m.end = nil + m.addend = nil + delete(m.clearedFields, chapter.FieldEnd) +} + +// SetVodID sets the "vod" edge to the Vod entity by id. +func (m *ChapterMutation) SetVodID(id uuid.UUID) { + m.vod = &id +} + +// ClearVod clears the "vod" edge to the Vod entity. +func (m *ChapterMutation) ClearVod() { + m.clearedvod = true +} + +// VodCleared reports if the "vod" edge to the Vod entity was cleared. +func (m *ChapterMutation) VodCleared() bool { + return m.clearedvod +} + +// VodID returns the "vod" edge ID in the mutation. +func (m *ChapterMutation) VodID() (id uuid.UUID, exists bool) { + if m.vod != nil { + return *m.vod, true + } + return +} + +// VodIDs returns the "vod" edge IDs in the mutation. +// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use +// VodID instead. It exists only for internal usage by the builders. +func (m *ChapterMutation) VodIDs() (ids []uuid.UUID) { + if id := m.vod; id != nil { + ids = append(ids, *id) + } + return +} + +// ResetVod resets all changes to the "vod" edge. +func (m *ChapterMutation) ResetVod() { + m.vod = nil + m.clearedvod = false +} + +// Where appends a list predicates to the ChapterMutation builder. +func (m *ChapterMutation) Where(ps ...predicate.Chapter) { + m.predicates = append(m.predicates, ps...) +} + +// WhereP appends storage-level predicates to the ChapterMutation builder. Using this method, +// users can use type-assertion to append predicates that do not depend on any generated package. +func (m *ChapterMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.Chapter, len(ps)) + for i := range ps { + p[i] = ps[i] + } + m.Where(p...) +} + +// Op returns the operation name. +func (m *ChapterMutation) Op() Op { + return m.op +} + +// SetOp allows setting the mutation operation. +func (m *ChapterMutation) SetOp(op Op) { + m.op = op +} + +// Type returns the node type of this mutation (Chapter). +func (m *ChapterMutation) Type() string { + return m.typ +} + +// Fields returns all fields that were changed during this mutation. Note that in +// order to get all numeric fields that were incremented/decremented, call +// AddedFields(). +func (m *ChapterMutation) Fields() []string { + fields := make([]string, 0, 4) + if m._type != nil { + fields = append(fields, chapter.FieldType) + } + if m.title != nil { + fields = append(fields, chapter.FieldTitle) + } + if m.start != nil { + fields = append(fields, chapter.FieldStart) + } + if m.end != nil { + fields = append(fields, chapter.FieldEnd) + } + return fields +} + +// Field returns the value of a field with the given name. The second boolean +// return value indicates that this field was not set, or was not defined in the +// schema. +func (m *ChapterMutation) Field(name string) (ent.Value, bool) { + switch name { + case chapter.FieldType: + return m.GetType() + case chapter.FieldTitle: + return m.Title() + case chapter.FieldStart: + return m.Start() + case chapter.FieldEnd: + return m.End() + } + return nil, false +} + +// OldField returns the old value of the field from the database. An error is +// returned if the mutation operation is not UpdateOne, or the query to the +// database failed. +func (m *ChapterMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + switch name { + case chapter.FieldType: + return m.OldType(ctx) + case chapter.FieldTitle: + return m.OldTitle(ctx) + case chapter.FieldStart: + return m.OldStart(ctx) + case chapter.FieldEnd: + return m.OldEnd(ctx) + } + return nil, fmt.Errorf("unknown Chapter field %s", name) +} + +// SetField sets the value of a field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *ChapterMutation) SetField(name string, value ent.Value) error { + switch name { + case chapter.FieldType: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetType(v) + return nil + case chapter.FieldTitle: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetTitle(v) + return nil + case chapter.FieldStart: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetStart(v) + return nil + case chapter.FieldEnd: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetEnd(v) + return nil + } + return fmt.Errorf("unknown Chapter field %s", name) +} + +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *ChapterMutation) AddedFields() []string { + var fields []string + if m.addstart != nil { + fields = append(fields, chapter.FieldStart) + } + if m.addend != nil { + fields = append(fields, chapter.FieldEnd) + } + return fields +} + +// AddedField returns the numeric value that was incremented/decremented on a field +// with the given name. The second boolean return value indicates that this field +// was not set, or was not defined in the schema. +func (m *ChapterMutation) AddedField(name string) (ent.Value, bool) { + switch name { + case chapter.FieldStart: + return m.AddedStart() + case chapter.FieldEnd: + return m.AddedEnd() + } + return nil, false +} + +// AddField adds the value to the field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *ChapterMutation) AddField(name string, value ent.Value) error { + switch name { + case chapter.FieldStart: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddStart(v) + return nil + case chapter.FieldEnd: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddEnd(v) + return nil + } + return fmt.Errorf("unknown Chapter numeric field %s", name) +} + +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *ChapterMutation) ClearedFields() []string { + var fields []string + if m.FieldCleared(chapter.FieldType) { + fields = append(fields, chapter.FieldType) + } + if m.FieldCleared(chapter.FieldTitle) { + fields = append(fields, chapter.FieldTitle) + } + if m.FieldCleared(chapter.FieldStart) { + fields = append(fields, chapter.FieldStart) + } + if m.FieldCleared(chapter.FieldEnd) { + fields = append(fields, chapter.FieldEnd) + } + return fields +} + +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *ChapterMutation) FieldCleared(name string) bool { + _, ok := m.clearedFields[name] + return ok +} + +// ClearField clears the value of the field with the given name. It returns an +// error if the field is not defined in the schema. +func (m *ChapterMutation) ClearField(name string) error { + switch name { + case chapter.FieldType: + m.ClearType() + return nil + case chapter.FieldTitle: + m.ClearTitle() + return nil + case chapter.FieldStart: + m.ClearStart() + return nil + case chapter.FieldEnd: + m.ClearEnd() + return nil + } + return fmt.Errorf("unknown Chapter nullable field %s", name) +} + +// ResetField resets all changes in the mutation for the field with the given name. +// It returns an error if the field is not defined in the schema. +func (m *ChapterMutation) ResetField(name string) error { + switch name { + case chapter.FieldType: + m.ResetType() + return nil + case chapter.FieldTitle: + m.ResetTitle() + return nil + case chapter.FieldStart: + m.ResetStart() + return nil + case chapter.FieldEnd: + m.ResetEnd() + return nil + } + return fmt.Errorf("unknown Chapter field %s", name) +} + +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *ChapterMutation) AddedEdges() []string { + edges := make([]string, 0, 1) + if m.vod != nil { + edges = append(edges, chapter.EdgeVod) + } + return edges +} + +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *ChapterMutation) AddedIDs(name string) []ent.Value { + switch name { + case chapter.EdgeVod: + if id := m.vod; id != nil { + return []ent.Value{*id} + } + } + return nil +} + +// RemovedEdges returns all edge names that were removed in this mutation. +func (m *ChapterMutation) RemovedEdges() []string { + edges := make([]string, 0, 1) + return edges +} + +// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with +// the given name in this mutation. +func (m *ChapterMutation) RemovedIDs(name string) []ent.Value { + return nil +} + +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *ChapterMutation) ClearedEdges() []string { + edges := make([]string, 0, 1) + if m.clearedvod { + edges = append(edges, chapter.EdgeVod) + } + return edges +} + +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *ChapterMutation) EdgeCleared(name string) bool { + switch name { + case chapter.EdgeVod: + return m.clearedvod + } + return false +} + +// ClearEdge clears the value of the edge with the given name. It returns an error +// if that edge is not defined in the schema. +func (m *ChapterMutation) ClearEdge(name string) error { + switch name { + case chapter.EdgeVod: + m.ClearVod() + return nil + } + return fmt.Errorf("unknown Chapter unique edge %s", name) +} + +// ResetEdge resets all changes to the edge with the given name in this mutation. +// It returns an error if the edge is not defined in the schema. +func (m *ChapterMutation) ResetEdge(name string) error { + switch name { + case chapter.EdgeVod: + m.ResetVod() + return nil + } + return fmt.Errorf("unknown Chapter edge %s", name) +} + // LiveMutation represents an operation that mutates the Live nodes in the graph. type LiveMutation struct { config @@ -6904,6 +7617,9 @@ type VodMutation struct { playlists map[uuid.UUID]struct{} removedplaylists map[uuid.UUID]struct{} clearedplaylists bool + chapters map[uuid.UUID]struct{} + removedchapters map[uuid.UUID]struct{} + clearedchapters bool done bool oldValue func(context.Context) (*Vod, error) predicates []predicate.Vod @@ -8101,6 +8817,60 @@ func (m *VodMutation) ResetPlaylists() { m.removedplaylists = nil } +// AddChapterIDs adds the "chapters" edge to the Chapter entity by ids. +func (m *VodMutation) AddChapterIDs(ids ...uuid.UUID) { + if m.chapters == nil { + m.chapters = make(map[uuid.UUID]struct{}) + } + for i := range ids { + m.chapters[ids[i]] = struct{}{} + } +} + +// ClearChapters clears the "chapters" edge to the Chapter entity. +func (m *VodMutation) ClearChapters() { + m.clearedchapters = true +} + +// ChaptersCleared reports if the "chapters" edge to the Chapter entity was cleared. +func (m *VodMutation) ChaptersCleared() bool { + return m.clearedchapters +} + +// RemoveChapterIDs removes the "chapters" edge to the Chapter entity by IDs. +func (m *VodMutation) RemoveChapterIDs(ids ...uuid.UUID) { + if m.removedchapters == nil { + m.removedchapters = make(map[uuid.UUID]struct{}) + } + for i := range ids { + delete(m.chapters, ids[i]) + m.removedchapters[ids[i]] = struct{}{} + } +} + +// RemovedChapters returns the removed IDs of the "chapters" edge to the Chapter entity. +func (m *VodMutation) RemovedChaptersIDs() (ids []uuid.UUID) { + for id := range m.removedchapters { + ids = append(ids, id) + } + return +} + +// ChaptersIDs returns the "chapters" edge IDs in the mutation. +func (m *VodMutation) ChaptersIDs() (ids []uuid.UUID) { + for id := range m.chapters { + ids = append(ids, id) + } + return +} + +// ResetChapters resets all changes to the "chapters" edge. +func (m *VodMutation) ResetChapters() { + m.chapters = nil + m.clearedchapters = false + m.removedchapters = nil +} + // Where appends a list predicates to the VodMutation builder. func (m *VodMutation) Where(ps ...predicate.Vod) { m.predicates = append(m.predicates, ps...) @@ -8681,7 +9451,7 @@ func (m *VodMutation) ResetField(name string) error { // AddedEdges returns all edge names that were set/added in this mutation. func (m *VodMutation) AddedEdges() []string { - edges := make([]string, 0, 3) + edges := make([]string, 0, 4) if m.channel != nil { edges = append(edges, vod.EdgeChannel) } @@ -8691,6 +9461,9 @@ func (m *VodMutation) AddedEdges() []string { if m.playlists != nil { edges = append(edges, vod.EdgePlaylists) } + if m.chapters != nil { + edges = append(edges, vod.EdgeChapters) + } return edges } @@ -8712,16 +9485,25 @@ func (m *VodMutation) AddedIDs(name string) []ent.Value { ids = append(ids, id) } return ids + case vod.EdgeChapters: + ids := make([]ent.Value, 0, len(m.chapters)) + for id := range m.chapters { + ids = append(ids, id) + } + return ids } return nil } // RemovedEdges returns all edge names that were removed in this mutation. func (m *VodMutation) RemovedEdges() []string { - edges := make([]string, 0, 3) + edges := make([]string, 0, 4) if m.removedplaylists != nil { edges = append(edges, vod.EdgePlaylists) } + if m.removedchapters != nil { + edges = append(edges, vod.EdgeChapters) + } return edges } @@ -8735,13 +9517,19 @@ func (m *VodMutation) RemovedIDs(name string) []ent.Value { ids = append(ids, id) } return ids + case vod.EdgeChapters: + ids := make([]ent.Value, 0, len(m.removedchapters)) + for id := range m.removedchapters { + ids = append(ids, id) + } + return ids } return nil } // ClearedEdges returns all edge names that were cleared in this mutation. func (m *VodMutation) ClearedEdges() []string { - edges := make([]string, 0, 3) + edges := make([]string, 0, 4) if m.clearedchannel { edges = append(edges, vod.EdgeChannel) } @@ -8751,6 +9539,9 @@ func (m *VodMutation) ClearedEdges() []string { if m.clearedplaylists { edges = append(edges, vod.EdgePlaylists) } + if m.clearedchapters { + edges = append(edges, vod.EdgeChapters) + } return edges } @@ -8764,6 +9555,8 @@ func (m *VodMutation) EdgeCleared(name string) bool { return m.clearedqueue case vod.EdgePlaylists: return m.clearedplaylists + case vod.EdgeChapters: + return m.clearedchapters } return false } @@ -8795,6 +9588,9 @@ func (m *VodMutation) ResetEdge(name string) error { case vod.EdgePlaylists: m.ResetPlaylists() return nil + case vod.EdgeChapters: + m.ResetChapters() + return nil } return fmt.Errorf("unknown Vod edge %s", name) } diff --git a/ent/playback/where.go b/ent/playback/where.go index 0b05a54e..5759d7cb 100644 --- a/ent/playback/where.go +++ b/ent/playback/where.go @@ -323,32 +323,15 @@ func CreatedAtLTE(v time.Time) predicate.Playback { // And groups predicates with the AND operator between them. func And(predicates ...predicate.Playback) predicate.Playback { - return predicate.Playback(func(s *sql.Selector) { - s1 := s.Clone().SetP(nil) - for _, p := range predicates { - p(s1) - } - s.Where(s1.P()) - }) + return predicate.Playback(sql.AndPredicates(predicates...)) } // Or groups predicates with the OR operator between them. func Or(predicates ...predicate.Playback) predicate.Playback { - return predicate.Playback(func(s *sql.Selector) { - s1 := s.Clone().SetP(nil) - for i, p := range predicates { - if i > 0 { - s1.Or() - } - p(s1) - } - s.Where(s1.P()) - }) + return predicate.Playback(sql.OrPredicates(predicates...)) } // Not applies the not operator on the given predicate. func Not(p predicate.Playback) predicate.Playback { - return predicate.Playback(func(s *sql.Selector) { - p(s.Not()) - }) + return predicate.Playback(sql.NotPredicates(p)) } diff --git a/ent/playback_create.go b/ent/playback_create.go index e12ead34..b71312f9 100644 --- a/ent/playback_create.go +++ b/ent/playback_create.go @@ -546,12 +546,16 @@ func (u *PlaybackUpsertOne) IDX(ctx context.Context) uuid.UUID { // PlaybackCreateBulk is the builder for creating many Playback entities in bulk. type PlaybackCreateBulk struct { config + err error builders []*PlaybackCreate conflict []sql.ConflictOption } // Save creates the Playback entities in the database. func (pcb *PlaybackCreateBulk) Save(ctx context.Context) ([]*Playback, error) { + if pcb.err != nil { + return nil, pcb.err + } specs := make([]*sqlgraph.CreateSpec, len(pcb.builders)) nodes := make([]*Playback, len(pcb.builders)) mutators := make([]Mutator, len(pcb.builders)) @@ -805,6 +809,9 @@ func (u *PlaybackUpsertBulk) UpdateUpdatedAt() *PlaybackUpsertBulk { // Exec executes the query. func (u *PlaybackUpsertBulk) Exec(ctx context.Context) error { + if u.create.err != nil { + return u.create.err + } for i, b := range u.create.builders { if len(b.conflict) != 0 { return fmt.Errorf("ent: OnConflict was set for builder %d. Set it on the PlaybackCreateBulk instead", i) diff --git a/ent/playlist/where.go b/ent/playlist/where.go index 3df5fe32..919ea087 100644 --- a/ent/playlist/where.go +++ b/ent/playlist/where.go @@ -401,32 +401,15 @@ func HasVodsWith(preds ...predicate.Vod) predicate.Playlist { // And groups predicates with the AND operator between them. func And(predicates ...predicate.Playlist) predicate.Playlist { - return predicate.Playlist(func(s *sql.Selector) { - s1 := s.Clone().SetP(nil) - for _, p := range predicates { - p(s1) - } - s.Where(s1.P()) - }) + return predicate.Playlist(sql.AndPredicates(predicates...)) } // Or groups predicates with the OR operator between them. func Or(predicates ...predicate.Playlist) predicate.Playlist { - return predicate.Playlist(func(s *sql.Selector) { - s1 := s.Clone().SetP(nil) - for i, p := range predicates { - if i > 0 { - s1.Or() - } - p(s1) - } - s.Where(s1.P()) - }) + return predicate.Playlist(sql.OrPredicates(predicates...)) } // Not applies the not operator on the given predicate. func Not(p predicate.Playlist) predicate.Playlist { - return predicate.Playlist(func(s *sql.Selector) { - p(s.Not()) - }) + return predicate.Playlist(sql.NotPredicates(p)) } diff --git a/ent/playlist_create.go b/ent/playlist_create.go index 886b0e71..57adc33a 100644 --- a/ent/playlist_create.go +++ b/ent/playlist_create.go @@ -522,12 +522,16 @@ func (u *PlaylistUpsertOne) IDX(ctx context.Context) uuid.UUID { // PlaylistCreateBulk is the builder for creating many Playlist entities in bulk. type PlaylistCreateBulk struct { config + err error builders []*PlaylistCreate conflict []sql.ConflictOption } // Save creates the Playlist entities in the database. func (pcb *PlaylistCreateBulk) Save(ctx context.Context) ([]*Playlist, error) { + if pcb.err != nil { + return nil, pcb.err + } specs := make([]*sqlgraph.CreateSpec, len(pcb.builders)) nodes := make([]*Playlist, len(pcb.builders)) mutators := make([]Mutator, len(pcb.builders)) @@ -767,6 +771,9 @@ func (u *PlaylistUpsertBulk) UpdateUpdatedAt() *PlaylistUpsertBulk { // Exec executes the query. func (u *PlaylistUpsertBulk) Exec(ctx context.Context) error { + if u.create.err != nil { + return u.create.err + } for i, b := range u.create.builders { if len(b.conflict) != 0 { return fmt.Errorf("ent: OnConflict was set for builder %d. Set it on the PlaylistCreateBulk instead", i) diff --git a/ent/predicate/predicate.go b/ent/predicate/predicate.go index fff81cf5..d3ae00e5 100644 --- a/ent/predicate/predicate.go +++ b/ent/predicate/predicate.go @@ -9,6 +9,9 @@ import ( // Channel is the predicate function for channel builders. type Channel func(*sql.Selector) +// Chapter is the predicate function for chapter builders. +type Chapter func(*sql.Selector) + // Live is the predicate function for live builders. type Live func(*sql.Selector) diff --git a/ent/queue/where.go b/ent/queue/where.go index 7bac02b7..6cd9e462 100644 --- a/ent/queue/where.go +++ b/ent/queue/where.go @@ -727,32 +727,15 @@ func HasVodWith(preds ...predicate.Vod) predicate.Queue { // And groups predicates with the AND operator between them. func And(predicates ...predicate.Queue) predicate.Queue { - return predicate.Queue(func(s *sql.Selector) { - s1 := s.Clone().SetP(nil) - for _, p := range predicates { - p(s1) - } - s.Where(s1.P()) - }) + return predicate.Queue(sql.AndPredicates(predicates...)) } // Or groups predicates with the OR operator between them. func Or(predicates ...predicate.Queue) predicate.Queue { - return predicate.Queue(func(s *sql.Selector) { - s1 := s.Clone().SetP(nil) - for i, p := range predicates { - if i > 0 { - s1.Or() - } - p(s1) - } - s.Where(s1.P()) - }) + return predicate.Queue(sql.OrPredicates(predicates...)) } // Not applies the not operator on the given predicate. func Not(p predicate.Queue) predicate.Queue { - return predicate.Queue(func(s *sql.Selector) { - p(s.Not()) - }) + return predicate.Queue(sql.NotPredicates(p)) } diff --git a/ent/queue_create.go b/ent/queue_create.go index f0c8e5ed..b08ad995 100644 --- a/ent/queue_create.go +++ b/ent/queue_create.go @@ -1403,12 +1403,16 @@ func (u *QueueUpsertOne) IDX(ctx context.Context) uuid.UUID { // QueueCreateBulk is the builder for creating many Queue entities in bulk. type QueueCreateBulk struct { config + err error builders []*QueueCreate conflict []sql.ConflictOption } // Save creates the Queue entities in the database. func (qcb *QueueCreateBulk) Save(ctx context.Context) ([]*Queue, error) { + if qcb.err != nil { + return nil, qcb.err + } specs := make([]*sqlgraph.CreateSpec, len(qcb.builders)) nodes := make([]*Queue, len(qcb.builders)) mutators := make([]Mutator, len(qcb.builders)) @@ -1914,6 +1918,9 @@ func (u *QueueUpsertBulk) UpdateUpdatedAt() *QueueUpsertBulk { // Exec executes the query. func (u *QueueUpsertBulk) Exec(ctx context.Context) error { + if u.create.err != nil { + return u.create.err + } for i, b := range u.create.builders { if len(b.conflict) != 0 { return fmt.Errorf("ent: OnConflict was set for builder %d. Set it on the QueueCreateBulk instead", i) diff --git a/ent/runtime.go b/ent/runtime.go index dd594239..a127d5f4 100644 --- a/ent/runtime.go +++ b/ent/runtime.go @@ -7,6 +7,7 @@ import ( "github.com/google/uuid" "github.com/zibbp/ganymede/ent/channel" + "github.com/zibbp/ganymede/ent/chapter" "github.com/zibbp/ganymede/ent/live" "github.com/zibbp/ganymede/ent/livecategory" "github.com/zibbp/ganymede/ent/playback" @@ -42,6 +43,12 @@ func init() { channelDescID := channelFields[0].Descriptor() // channel.DefaultID holds the default value on creation for the id field. channel.DefaultID = channelDescID.Default.(func() uuid.UUID) + chapterFields := schema.Chapter{}.Fields() + _ = chapterFields + // chapterDescID is the schema descriptor for id field. + chapterDescID := chapterFields[0].Descriptor() + // chapter.DefaultID holds the default value on creation for the id field. + chapter.DefaultID = chapterDescID.Default.(func() uuid.UUID) liveFields := schema.Live{}.Fields() _ = liveFields // liveDescWatchLive is the schema descriptor for watch_live field. diff --git a/ent/runtime/runtime.go b/ent/runtime/runtime.go index 35692a78..fcd88920 100644 --- a/ent/runtime/runtime.go +++ b/ent/runtime/runtime.go @@ -5,6 +5,6 @@ package runtime // The schema-stitching logic is generated in github.com/zibbp/ganymede/ent/runtime.go const ( - Version = "v0.12.3" // Version of ent codegen. - Sum = "h1:N5lO2EOrHpCH5HYfiMOCHYbo+oh5M8GjT0/cx5x6xkk=" // Sum of ent codegen. + Version = "v0.12.4" // Version of ent codegen. + Sum = "h1:LddPnAyxls/O7DTXZvUGDj0NZIdGSu317+aoNLJWbD8=" // Sum of ent codegen. ) diff --git a/ent/schema/chapter.go b/ent/schema/chapter.go new file mode 100644 index 00000000..af4249d4 --- /dev/null +++ b/ent/schema/chapter.go @@ -0,0 +1,31 @@ +package schema + +import ( + "entgo.io/ent" + "entgo.io/ent/schema/edge" + "entgo.io/ent/schema/field" + "github.com/google/uuid" +) + +// Chapter holds the schema definition for the Chapter entity. +type Chapter struct { + ent.Schema +} + +// Fields of the Chapter. +func (Chapter) Fields() []ent.Field { + return []ent.Field{ + field.UUID("id", uuid.UUID{}).Default(uuid.New), + field.String("type").Optional(), + field.String("title").Optional(), + field.Int("start").Optional(), + field.Int("end").Optional(), + } +} + +// Edges of the Chapter. +func (Chapter) Edges() []ent.Edge { + return []ent.Edge{ + edge.From("vod", Vod.Type).Ref("chapters").Unique().Required(), + } +} diff --git a/ent/schema/vod.go b/ent/schema/vod.go index 94fdbc65..91fe9f95 100644 --- a/ent/schema/vod.go +++ b/ent/schema/vod.go @@ -50,5 +50,6 @@ func (Vod) Edges() []ent.Edge { edge.From("channel", Channel.Type).Ref("vods").Unique().Required(), edge.To("queue", Queue.Type).Unique(), edge.From("playlists", Playlist.Type).Ref("vods"), + edge.To("chapters", Chapter.Type), } } diff --git a/ent/twitchcategory/where.go b/ent/twitchcategory/where.go index 102c6f05..f7bea1aa 100644 --- a/ent/twitchcategory/where.go +++ b/ent/twitchcategory/where.go @@ -386,32 +386,15 @@ func CreatedAtLTE(v time.Time) predicate.TwitchCategory { // And groups predicates with the AND operator between them. func And(predicates ...predicate.TwitchCategory) predicate.TwitchCategory { - return predicate.TwitchCategory(func(s *sql.Selector) { - s1 := s.Clone().SetP(nil) - for _, p := range predicates { - p(s1) - } - s.Where(s1.P()) - }) + return predicate.TwitchCategory(sql.AndPredicates(predicates...)) } // Or groups predicates with the OR operator between them. func Or(predicates ...predicate.TwitchCategory) predicate.TwitchCategory { - return predicate.TwitchCategory(func(s *sql.Selector) { - s1 := s.Clone().SetP(nil) - for i, p := range predicates { - if i > 0 { - s1.Or() - } - p(s1) - } - s.Where(s1.P()) - }) + return predicate.TwitchCategory(sql.OrPredicates(predicates...)) } // Not applies the not operator on the given predicate. func Not(p predicate.TwitchCategory) predicate.TwitchCategory { - return predicate.TwitchCategory(func(s *sql.Selector) { - p(s.Not()) - }) + return predicate.TwitchCategory(sql.NotPredicates(p)) } diff --git a/ent/twitchcategory_create.go b/ent/twitchcategory_create.go index f8feb9b9..fcb2e404 100644 --- a/ent/twitchcategory_create.go +++ b/ent/twitchcategory_create.go @@ -477,12 +477,16 @@ func (u *TwitchCategoryUpsertOne) IDX(ctx context.Context) string { // TwitchCategoryCreateBulk is the builder for creating many TwitchCategory entities in bulk. type TwitchCategoryCreateBulk struct { config + err error builders []*TwitchCategoryCreate conflict []sql.ConflictOption } // Save creates the TwitchCategory entities in the database. func (tccb *TwitchCategoryCreateBulk) Save(ctx context.Context) ([]*TwitchCategory, error) { + if tccb.err != nil { + return nil, tccb.err + } specs := make([]*sqlgraph.CreateSpec, len(tccb.builders)) nodes := make([]*TwitchCategory, len(tccb.builders)) mutators := make([]Mutator, len(tccb.builders)) @@ -722,6 +726,9 @@ func (u *TwitchCategoryUpsertBulk) UpdateUpdatedAt() *TwitchCategoryUpsertBulk { // Exec executes the query. func (u *TwitchCategoryUpsertBulk) Exec(ctx context.Context) error { + if u.create.err != nil { + return u.create.err + } for i, b := range u.create.builders { if len(b.conflict) != 0 { return fmt.Errorf("ent: OnConflict was set for builder %d. Set it on the TwitchCategoryCreateBulk instead", i) diff --git a/ent/tx.go b/ent/tx.go index e60cf77c..2c392f9d 100644 --- a/ent/tx.go +++ b/ent/tx.go @@ -14,6 +14,8 @@ type Tx struct { config // Channel is the client for interacting with the Channel builders. Channel *ChannelClient + // Chapter is the client for interacting with the Chapter builders. + Chapter *ChapterClient // Live is the client for interacting with the Live builders. Live *LiveClient // LiveCategory is the client for interacting with the LiveCategory builders. @@ -162,6 +164,7 @@ func (tx *Tx) Client() *Client { func (tx *Tx) init() { tx.Channel = NewChannelClient(tx.config) + tx.Chapter = NewChapterClient(tx.config) tx.Live = NewLiveClient(tx.config) tx.LiveCategory = NewLiveCategoryClient(tx.config) tx.Playback = NewPlaybackClient(tx.config) diff --git a/ent/user/where.go b/ent/user/where.go index 065544de..c37bd85e 100644 --- a/ent/user/where.go +++ b/ent/user/where.go @@ -503,32 +503,15 @@ func CreatedAtLTE(v time.Time) predicate.User { // And groups predicates with the AND operator between them. func And(predicates ...predicate.User) predicate.User { - return predicate.User(func(s *sql.Selector) { - s1 := s.Clone().SetP(nil) - for _, p := range predicates { - p(s1) - } - s.Where(s1.P()) - }) + return predicate.User(sql.AndPredicates(predicates...)) } // Or groups predicates with the OR operator between them. func Or(predicates ...predicate.User) predicate.User { - return predicate.User(func(s *sql.Selector) { - s1 := s.Clone().SetP(nil) - for i, p := range predicates { - if i > 0 { - s1.Or() - } - p(s1) - } - s.Where(s1.P()) - }) + return predicate.User(sql.OrPredicates(predicates...)) } // Not applies the not operator on the given predicate. func Not(p predicate.User) predicate.User { - return predicate.User(func(s *sql.Selector) { - p(s.Not()) - }) + return predicate.User(sql.NotPredicates(p)) } diff --git a/ent/user_create.go b/ent/user_create.go index 0c39d4b7..694f0701 100644 --- a/ent/user_create.go +++ b/ent/user_create.go @@ -655,12 +655,16 @@ func (u *UserUpsertOne) IDX(ctx context.Context) uuid.UUID { // UserCreateBulk is the builder for creating many User entities in bulk. type UserCreateBulk struct { config + err error builders []*UserCreate conflict []sql.ConflictOption } // Save creates the User entities in the database. func (ucb *UserCreateBulk) Save(ctx context.Context) ([]*User, error) { + if ucb.err != nil { + return nil, ucb.err + } specs := make([]*sqlgraph.CreateSpec, len(ucb.builders)) nodes := make([]*User, len(ucb.builders)) mutators := make([]Mutator, len(ucb.builders)) @@ -949,6 +953,9 @@ func (u *UserUpsertBulk) UpdateUpdatedAt() *UserUpsertBulk { // Exec executes the query. func (u *UserUpsertBulk) Exec(ctx context.Context) error { + if u.create.err != nil { + return u.create.err + } for i, b := range u.create.builders { if len(b.conflict) != 0 { return fmt.Errorf("ent: OnConflict was set for builder %d. Set it on the UserCreateBulk instead", i) diff --git a/ent/vod.go b/ent/vod.go index 80c02b4b..1e0a57ff 100644 --- a/ent/vod.go +++ b/ent/vod.go @@ -80,9 +80,11 @@ type VodEdges struct { Queue *Queue `json:"queue,omitempty"` // Playlists holds the value of the playlists edge. Playlists []*Playlist `json:"playlists,omitempty"` + // Chapters holds the value of the chapters edge. + Chapters []*Chapter `json:"chapters,omitempty"` // loadedTypes holds the information for reporting if a // type was loaded (or requested) in eager-loading or not. - loadedTypes [3]bool + loadedTypes [4]bool } // ChannelOrErr returns the Channel value or an error if the edge @@ -120,6 +122,15 @@ func (e VodEdges) PlaylistsOrErr() ([]*Playlist, error) { return nil, &NotLoadedError{edge: "playlists"} } +// ChaptersOrErr returns the Chapters value or an error if the edge +// was not loaded in eager-loading. +func (e VodEdges) ChaptersOrErr() ([]*Chapter, error) { + if e.loadedTypes[3] { + return e.Chapters, nil + } + return nil, &NotLoadedError{edge: "chapters"} +} + // scanValues returns the types for scanning values from sql.Rows. func (*Vod) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) @@ -325,6 +336,11 @@ func (v *Vod) QueryPlaylists() *PlaylistQuery { return NewVodClient(v.config).QueryPlaylists(v) } +// QueryChapters queries the "chapters" edge of the Vod entity. +func (v *Vod) QueryChapters() *ChapterQuery { + return NewVodClient(v.config).QueryChapters(v) +} + // Update returns a builder for updating this Vod. // Note that you need to call Vod.Unwrap() before calling this method if this Vod // was returned from a transaction, and the transaction was committed or rolled back. diff --git a/ent/vod/vod.go b/ent/vod/vod.go index 96a712c4..0f8266fe 100644 --- a/ent/vod/vod.go +++ b/ent/vod/vod.go @@ -67,6 +67,8 @@ const ( EdgeQueue = "queue" // EdgePlaylists holds the string denoting the playlists edge name in mutations. EdgePlaylists = "playlists" + // EdgeChapters holds the string denoting the chapters edge name in mutations. + EdgeChapters = "chapters" // Table holds the table name of the vod in the database. Table = "vods" // ChannelTable is the table that holds the channel relation/edge. @@ -88,6 +90,13 @@ const ( // PlaylistsInverseTable is the table name for the Playlist entity. // It exists in this package in order to avoid circular dependency with the "playlist" package. PlaylistsInverseTable = "playlists" + // ChaptersTable is the table that holds the chapters relation/edge. + ChaptersTable = "chapters" + // ChaptersInverseTable is the table name for the Chapter entity. + // It exists in this package in order to avoid circular dependency with the "chapter" package. + ChaptersInverseTable = "chapters" + // ChaptersColumn is the table column denoting the chapters relation/edge. + ChaptersColumn = "vod_chapters" ) // Columns holds all SQL columns for vod fields. @@ -336,6 +345,20 @@ func ByPlaylists(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { sqlgraph.OrderByNeighborTerms(s, newPlaylistsStep(), append([]sql.OrderTerm{term}, terms...)...) } } + +// ByChaptersCount orders the results by chapters count. +func ByChaptersCount(opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborsCount(s, newChaptersStep(), opts...) + } +} + +// ByChapters orders the results by chapters terms. +func ByChapters(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newChaptersStep(), append([]sql.OrderTerm{term}, terms...)...) + } +} func newChannelStep() *sqlgraph.Step { return sqlgraph.NewStep( sqlgraph.From(Table, FieldID), @@ -357,3 +380,10 @@ func newPlaylistsStep() *sqlgraph.Step { sqlgraph.Edge(sqlgraph.M2M, true, PlaylistsTable, PlaylistsPrimaryKey...), ) } +func newChaptersStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(ChaptersInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, ChaptersTable, ChaptersColumn), + ) +} diff --git a/ent/vod/where.go b/ent/vod/where.go index 3be42911..f295d678 100644 --- a/ent/vod/where.go +++ b/ent/vod/where.go @@ -1406,34 +1406,40 @@ func HasPlaylistsWith(preds ...predicate.Playlist) predicate.Vod { }) } -// And groups predicates with the AND operator between them. -func And(predicates ...predicate.Vod) predicate.Vod { +// HasChapters applies the HasEdge predicate on the "chapters" edge. +func HasChapters() predicate.Vod { return predicate.Vod(func(s *sql.Selector) { - s1 := s.Clone().SetP(nil) - for _, p := range predicates { - p(s1) - } - s.Where(s1.P()) + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, ChaptersTable, ChaptersColumn), + ) + sqlgraph.HasNeighbors(s, step) }) } -// Or groups predicates with the OR operator between them. -func Or(predicates ...predicate.Vod) predicate.Vod { +// HasChaptersWith applies the HasEdge predicate on the "chapters" edge with a given conditions (other predicates). +func HasChaptersWith(preds ...predicate.Chapter) predicate.Vod { return predicate.Vod(func(s *sql.Selector) { - s1 := s.Clone().SetP(nil) - for i, p := range predicates { - if i > 0 { - s1.Or() + step := newChaptersStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) } - p(s1) - } - s.Where(s1.P()) + }) }) } +// And groups predicates with the AND operator between them. +func And(predicates ...predicate.Vod) predicate.Vod { + return predicate.Vod(sql.AndPredicates(predicates...)) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.Vod) predicate.Vod { + return predicate.Vod(sql.OrPredicates(predicates...)) +} + // Not applies the not operator on the given predicate. func Not(p predicate.Vod) predicate.Vod { - return predicate.Vod(func(s *sql.Selector) { - p(s.Not()) - }) + return predicate.Vod(sql.NotPredicates(p)) } diff --git a/ent/vod_create.go b/ent/vod_create.go index aac9ed4a..f2ab8c06 100644 --- a/ent/vod_create.go +++ b/ent/vod_create.go @@ -14,6 +14,7 @@ import ( "entgo.io/ent/schema/field" "github.com/google/uuid" "github.com/zibbp/ganymede/ent/channel" + "github.com/zibbp/ganymede/ent/chapter" "github.com/zibbp/ganymede/ent/playlist" "github.com/zibbp/ganymede/ent/queue" "github.com/zibbp/ganymede/ent/vod" @@ -363,6 +364,21 @@ func (vc *VodCreate) AddPlaylists(p ...*Playlist) *VodCreate { return vc.AddPlaylistIDs(ids...) } +// AddChapterIDs adds the "chapters" edge to the Chapter entity by IDs. +func (vc *VodCreate) AddChapterIDs(ids ...uuid.UUID) *VodCreate { + vc.mutation.AddChapterIDs(ids...) + return vc +} + +// AddChapters adds the "chapters" edges to the Chapter entity. +func (vc *VodCreate) AddChapters(c ...*Chapter) *VodCreate { + ids := make([]uuid.UUID, len(c)) + for i := range c { + ids[i] = c[i].ID + } + return vc.AddChapterIDs(ids...) +} + // Mutation returns the VodMutation object of the builder. func (vc *VodCreate) Mutation() *VodMutation { return vc.mutation @@ -674,6 +690,22 @@ func (vc *VodCreate) createSpec() (*Vod, *sqlgraph.CreateSpec) { } _spec.Edges = append(_spec.Edges, edge) } + if nodes := vc.mutation.ChaptersIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: vod.ChaptersTable, + Columns: []string{vod.ChaptersColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(chapter.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges = append(_spec.Edges, edge) + } return _node, _spec } @@ -1507,12 +1539,16 @@ func (u *VodUpsertOne) IDX(ctx context.Context) uuid.UUID { // VodCreateBulk is the builder for creating many Vod entities in bulk. type VodCreateBulk struct { config + err error builders []*VodCreate conflict []sql.ConflictOption } // Save creates the Vod entities in the database. func (vcb *VodCreateBulk) Save(ctx context.Context) ([]*Vod, error) { + if vcb.err != nil { + return nil, vcb.err + } specs := make([]*sqlgraph.CreateSpec, len(vcb.builders)) nodes := make([]*Vod, len(vcb.builders)) mutators := make([]Mutator, len(vcb.builders)) @@ -2053,6 +2089,9 @@ func (u *VodUpsertBulk) UpdateUpdatedAt() *VodUpsertBulk { // Exec executes the query. func (u *VodUpsertBulk) Exec(ctx context.Context) error { + if u.create.err != nil { + return u.create.err + } for i, b := range u.create.builders { if len(b.conflict) != 0 { return fmt.Errorf("ent: OnConflict was set for builder %d. Set it on the VodCreateBulk instead", i) diff --git a/ent/vod_query.go b/ent/vod_query.go index 235d777a..72715f8d 100644 --- a/ent/vod_query.go +++ b/ent/vod_query.go @@ -13,6 +13,7 @@ import ( "entgo.io/ent/schema/field" "github.com/google/uuid" "github.com/zibbp/ganymede/ent/channel" + "github.com/zibbp/ganymede/ent/chapter" "github.com/zibbp/ganymede/ent/playlist" "github.com/zibbp/ganymede/ent/predicate" "github.com/zibbp/ganymede/ent/queue" @@ -29,6 +30,7 @@ type VodQuery struct { withChannel *ChannelQuery withQueue *QueueQuery withPlaylists *PlaylistQuery + withChapters *ChapterQuery withFKs bool // intermediate query (i.e. traversal path). sql *sql.Selector @@ -132,6 +134,28 @@ func (vq *VodQuery) QueryPlaylists() *PlaylistQuery { return query } +// QueryChapters chains the current query on the "chapters" edge. +func (vq *VodQuery) QueryChapters() *ChapterQuery { + query := (&ChapterClient{config: vq.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := vq.prepareQuery(ctx); err != nil { + return nil, err + } + selector := vq.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(vod.Table, vod.FieldID, selector), + sqlgraph.To(chapter.Table, chapter.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, vod.ChaptersTable, vod.ChaptersColumn), + ) + fromU = sqlgraph.SetNeighbors(vq.driver.Dialect(), step) + return fromU, nil + } + return query +} + // First returns the first Vod entity from the query. // Returns a *NotFoundError when no Vod was found. func (vq *VodQuery) First(ctx context.Context) (*Vod, error) { @@ -327,6 +351,7 @@ func (vq *VodQuery) Clone() *VodQuery { withChannel: vq.withChannel.Clone(), withQueue: vq.withQueue.Clone(), withPlaylists: vq.withPlaylists.Clone(), + withChapters: vq.withChapters.Clone(), // clone intermediate query. sql: vq.sql.Clone(), path: vq.path, @@ -366,6 +391,17 @@ func (vq *VodQuery) WithPlaylists(opts ...func(*PlaylistQuery)) *VodQuery { return vq } +// WithChapters tells the query-builder to eager-load the nodes that are connected to +// the "chapters" edge. The optional arguments are used to configure the query builder of the edge. +func (vq *VodQuery) WithChapters(opts ...func(*ChapterQuery)) *VodQuery { + query := (&ChapterClient{config: vq.config}).Query() + for _, opt := range opts { + opt(query) + } + vq.withChapters = query + return vq +} + // GroupBy is used to group vertices by one or more fields/columns. // It is often used with aggregate functions, like: count, max, mean, min, sum. // @@ -445,10 +481,11 @@ func (vq *VodQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Vod, err nodes = []*Vod{} withFKs = vq.withFKs _spec = vq.querySpec() - loadedTypes = [3]bool{ + loadedTypes = [4]bool{ vq.withChannel != nil, vq.withQueue != nil, vq.withPlaylists != nil, + vq.withChapters != nil, } ) if vq.withChannel != nil { @@ -494,6 +531,13 @@ func (vq *VodQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Vod, err return nil, err } } + if query := vq.withChapters; query != nil { + if err := vq.loadChapters(ctx, query, nodes, + func(n *Vod) { n.Edges.Chapters = []*Chapter{} }, + func(n *Vod, e *Chapter) { n.Edges.Chapters = append(n.Edges.Chapters, e) }); err != nil { + return nil, err + } + } return nodes, nil } @@ -618,6 +662,37 @@ func (vq *VodQuery) loadPlaylists(ctx context.Context, query *PlaylistQuery, nod } return nil } +func (vq *VodQuery) loadChapters(ctx context.Context, query *ChapterQuery, nodes []*Vod, init func(*Vod), assign func(*Vod, *Chapter)) error { + fks := make([]driver.Value, 0, len(nodes)) + nodeids := make(map[uuid.UUID]*Vod) + for i := range nodes { + fks = append(fks, nodes[i].ID) + nodeids[nodes[i].ID] = nodes[i] + if init != nil { + init(nodes[i]) + } + } + query.withFKs = true + query.Where(predicate.Chapter(func(s *sql.Selector) { + s.Where(sql.InValues(s.C(vod.ChaptersColumn), fks...)) + })) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + fk := n.vod_chapters + if fk == nil { + return fmt.Errorf(`foreign-key "vod_chapters" is nil for node %v`, n.ID) + } + node, ok := nodeids[*fk] + if !ok { + return fmt.Errorf(`unexpected referenced foreign-key "vod_chapters" returned %v for node %v`, *fk, n.ID) + } + assign(node, n) + } + return nil +} func (vq *VodQuery) sqlCount(ctx context.Context) (int, error) { _spec := vq.querySpec() diff --git a/ent/vod_update.go b/ent/vod_update.go index e0a9dd33..4cbda4d3 100644 --- a/ent/vod_update.go +++ b/ent/vod_update.go @@ -13,6 +13,7 @@ import ( "entgo.io/ent/schema/field" "github.com/google/uuid" "github.com/zibbp/ganymede/ent/channel" + "github.com/zibbp/ganymede/ent/chapter" "github.com/zibbp/ganymede/ent/playlist" "github.com/zibbp/ganymede/ent/predicate" "github.com/zibbp/ganymede/ent/queue" @@ -401,6 +402,21 @@ func (vu *VodUpdate) AddPlaylists(p ...*Playlist) *VodUpdate { return vu.AddPlaylistIDs(ids...) } +// AddChapterIDs adds the "chapters" edge to the Chapter entity by IDs. +func (vu *VodUpdate) AddChapterIDs(ids ...uuid.UUID) *VodUpdate { + vu.mutation.AddChapterIDs(ids...) + return vu +} + +// AddChapters adds the "chapters" edges to the Chapter entity. +func (vu *VodUpdate) AddChapters(c ...*Chapter) *VodUpdate { + ids := make([]uuid.UUID, len(c)) + for i := range c { + ids[i] = c[i].ID + } + return vu.AddChapterIDs(ids...) +} + // Mutation returns the VodMutation object of the builder. func (vu *VodUpdate) Mutation() *VodMutation { return vu.mutation @@ -439,6 +455,27 @@ func (vu *VodUpdate) RemovePlaylists(p ...*Playlist) *VodUpdate { return vu.RemovePlaylistIDs(ids...) } +// ClearChapters clears all "chapters" edges to the Chapter entity. +func (vu *VodUpdate) ClearChapters() *VodUpdate { + vu.mutation.ClearChapters() + return vu +} + +// RemoveChapterIDs removes the "chapters" edge to Chapter entities by IDs. +func (vu *VodUpdate) RemoveChapterIDs(ids ...uuid.UUID) *VodUpdate { + vu.mutation.RemoveChapterIDs(ids...) + return vu +} + +// RemoveChapters removes "chapters" edges to Chapter entities. +func (vu *VodUpdate) RemoveChapters(c ...*Chapter) *VodUpdate { + ids := make([]uuid.UUID, len(c)) + for i := range c { + ids[i] = c[i].ID + } + return vu.RemoveChapterIDs(ids...) +} + // Save executes the query and returns the number of nodes affected by the update operation. func (vu *VodUpdate) Save(ctx context.Context) (int, error) { vu.defaults() @@ -704,6 +741,51 @@ func (vu *VodUpdate) sqlSave(ctx context.Context) (n int, err error) { } _spec.Edges.Add = append(_spec.Edges.Add, edge) } + if vu.mutation.ChaptersCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: vod.ChaptersTable, + Columns: []string{vod.ChaptersColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(chapter.FieldID, field.TypeUUID), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := vu.mutation.RemovedChaptersIDs(); len(nodes) > 0 && !vu.mutation.ChaptersCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: vod.ChaptersTable, + Columns: []string{vod.ChaptersColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(chapter.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := vu.mutation.ChaptersIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: vod.ChaptersTable, + Columns: []string{vod.ChaptersColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(chapter.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } if n, err = sqlgraph.UpdateNodes(ctx, vu.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { err = &NotFoundError{vod.Label} @@ -1092,6 +1174,21 @@ func (vuo *VodUpdateOne) AddPlaylists(p ...*Playlist) *VodUpdateOne { return vuo.AddPlaylistIDs(ids...) } +// AddChapterIDs adds the "chapters" edge to the Chapter entity by IDs. +func (vuo *VodUpdateOne) AddChapterIDs(ids ...uuid.UUID) *VodUpdateOne { + vuo.mutation.AddChapterIDs(ids...) + return vuo +} + +// AddChapters adds the "chapters" edges to the Chapter entity. +func (vuo *VodUpdateOne) AddChapters(c ...*Chapter) *VodUpdateOne { + ids := make([]uuid.UUID, len(c)) + for i := range c { + ids[i] = c[i].ID + } + return vuo.AddChapterIDs(ids...) +} + // Mutation returns the VodMutation object of the builder. func (vuo *VodUpdateOne) Mutation() *VodMutation { return vuo.mutation @@ -1130,6 +1227,27 @@ func (vuo *VodUpdateOne) RemovePlaylists(p ...*Playlist) *VodUpdateOne { return vuo.RemovePlaylistIDs(ids...) } +// ClearChapters clears all "chapters" edges to the Chapter entity. +func (vuo *VodUpdateOne) ClearChapters() *VodUpdateOne { + vuo.mutation.ClearChapters() + return vuo +} + +// RemoveChapterIDs removes the "chapters" edge to Chapter entities by IDs. +func (vuo *VodUpdateOne) RemoveChapterIDs(ids ...uuid.UUID) *VodUpdateOne { + vuo.mutation.RemoveChapterIDs(ids...) + return vuo +} + +// RemoveChapters removes "chapters" edges to Chapter entities. +func (vuo *VodUpdateOne) RemoveChapters(c ...*Chapter) *VodUpdateOne { + ids := make([]uuid.UUID, len(c)) + for i := range c { + ids[i] = c[i].ID + } + return vuo.RemoveChapterIDs(ids...) +} + // Where appends a list predicates to the VodUpdate builder. func (vuo *VodUpdateOne) Where(ps ...predicate.Vod) *VodUpdateOne { vuo.mutation.Where(ps...) @@ -1425,6 +1543,51 @@ func (vuo *VodUpdateOne) sqlSave(ctx context.Context) (_node *Vod, err error) { } _spec.Edges.Add = append(_spec.Edges.Add, edge) } + if vuo.mutation.ChaptersCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: vod.ChaptersTable, + Columns: []string{vod.ChaptersColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(chapter.FieldID, field.TypeUUID), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := vuo.mutation.RemovedChaptersIDs(); len(nodes) > 0 && !vuo.mutation.ChaptersCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: vod.ChaptersTable, + Columns: []string{vod.ChaptersColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(chapter.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := vuo.mutation.ChaptersIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: vod.ChaptersTable, + Columns: []string{vod.ChaptersColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(chapter.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } _node = &Vod{config: vuo.config} _spec.Assign = _node.assignValues _spec.ScanValues = _node.scanValues diff --git a/entrypoint.sh b/entrypoint.sh index 38ff8522..51a785cd 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -27,4 +27,10 @@ su-exec abc fc-cache -f export DOTNET_BUNDLE_EXTRACT_BASE_DIR=/tmp export FONTCONFIG_CACHE=/var/cache/fontconfig -su-exec abc /opt/app/ganymede-api \ No newline at end of file +su-exec abc /opt/app/ganymede-api & +api_pid=$! +su-exec abc /opt/app/ganymede-worker & +worker_pid=$! + +# wait +wait $api_pid $worker_pid diff --git a/go.mod b/go.mod index 57b66149..bb8f0aef 100644 --- a/go.mod +++ b/go.mod @@ -3,90 +3,111 @@ module github.com/zibbp/ganymede go 1.18 require ( - entgo.io/ent v0.12.4 + entgo.io/ent v0.12.5 github.com/MicahParks/keyfunc v1.9.0 - github.com/coreos/go-oidc/v3 v3.6.0 - github.com/go-co-op/gocron v1.35.0 + github.com/coreos/go-oidc/v3 v3.9.0 + github.com/go-co-op/gocron v1.37.0 github.com/go-playground/validator/v10 v10.16.0 github.com/golang-jwt/jwt/v4 v4.5.0 - github.com/google/uuid v1.3.1 - github.com/labstack/echo/v4 v4.11.1 + github.com/google/uuid v1.5.0 + github.com/kelseyhightower/envconfig v1.4.0 + github.com/labstack/echo/v4 v4.11.3 github.com/lib/pq v1.10.9 github.com/patrickmn/go-cache v2.1.0+incompatible - github.com/prometheus/client_golang v1.16.0 + github.com/prometheus/client_golang v1.17.0 github.com/rs/zerolog v1.31.0 - github.com/spf13/viper v1.16.0 + github.com/spf13/viper v1.18.2 github.com/swaggo/swag v1.16.2 - golang.org/x/crypto v0.14.0 - golang.org/x/oauth2 v0.12.0 + go.temporal.io/api v1.26.0 + go.temporal.io/sdk v1.25.1 + golang.org/x/crypto v0.17.0 + golang.org/x/oauth2 v0.15.0 gopkg.in/square/go-jose.v2 v2.6.0 ) require ( github.com/KyleBanks/depth v1.2.1 // indirect - github.com/gabriel-vasile/mimetype v1.4.2 // indirect + github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect + github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/ghodss/yaml v1.0.0 // indirect - github.com/go-openapi/jsonpointer v0.19.6 // indirect - github.com/go-openapi/jsonreference v0.20.2 // indirect - github.com/go-openapi/spec v0.20.8 // indirect - github.com/go-openapi/swag v0.22.3 // indirect + github.com/go-openapi/jsonpointer v0.20.1 // indirect + github.com/go-openapi/jsonreference v0.20.3 // indirect + github.com/go-openapi/spec v0.20.12 // indirect + github.com/go-openapi/swag v0.22.5 // indirect + github.com/gogo/googleapis v1.4.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/gogo/status v1.1.1 // indirect + github.com/golang/mock v1.6.0 // indirect + github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect + github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect + github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect + github.com/pborman/uuid v1.2.1 // indirect + github.com/robfig/cron v1.2.0 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/stretchr/objx v0.5.1 // indirect github.com/swaggo/files/v2 v2.0.0 // indirect - go.uber.org/atomic v1.9.0 // indirect - golang.org/x/tools v0.8.1-0.20230428195545-5283a0178901 // indirect + go.uber.org/atomic v1.11.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/exp v0.0.0-20231214170342-aacd6d4b4611 // indirect + golang.org/x/tools v0.16.1 // indirect + google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0 // indirect + google.golang.org/grpc v1.60.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) require ( - ariga.io/atlas v0.14.1-0.20230918065911-83ad451a4935 // indirect + ariga.io/atlas v0.15.0 // indirect github.com/agext/levenshtein v1.2.3 // indirect - github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-jose/go-jose/v3 v3.0.1 // indirect github.com/go-openapi/inflect v0.19.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/golang/protobuf v1.5.3 // indirect - github.com/google/go-cmp v0.5.9 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/hashicorp/hcl/v2 v2.16.1 // indirect - github.com/labstack/gommon v0.4.0 // indirect + github.com/hashicorp/hcl/v2 v2.19.1 // indirect + github.com/labstack/gommon v0.4.1 // indirect github.com/leodido/go-urn v1.2.4 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-sqlite3 v1.14.17 - github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/pelletier/go-toml/v2 v2.0.8 // indirect + github.com/pelletier/go-toml/v2 v2.1.1 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_model v0.3.0 // indirect - github.com/prometheus/common v0.42.0 // indirect - github.com/prometheus/procfs v0.10.1 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_model v0.5.0 // indirect + github.com/prometheus/common v0.45.0 // indirect + github.com/prometheus/procfs v0.12.0 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect - github.com/spf13/afero v1.9.5 // indirect - github.com/spf13/cast v1.5.1 // indirect - github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.6.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/testify v1.8.4 - github.com/subosito/gotenv v1.4.2 // indirect + github.com/subosito/gotenv v1.6.0 // indirect github.com/swaggo/echo-swagger v1.4.1 github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect - github.com/zclconf/go-cty v1.12.1 // indirect - golang.org/x/mod v0.10.0 // indirect - golang.org/x/net v0.17.0 // indirect - golang.org/x/sys v0.13.0 // indirect - golang.org/x/text v0.13.0 // indirect - golang.org/x/time v0.3.0 // indirect - google.golang.org/appengine v1.6.7 // indirect + github.com/zclconf/go-cty v1.14.1 // indirect + golang.org/x/mod v0.14.0 // indirect + golang.org/x/net v0.19.0 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.5.0 // indirect + google.golang.org/appengine v1.6.8 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index f84ba898..a1236c20 100644 --- a/go.sum +++ b/go.sum @@ -1,47 +1,10 @@ -ariga.io/atlas v0.14.1-0.20230918065911-83ad451a4935 h1:JnYs/y8RJ3+MiIUp+3RgyyeO48VHLAZimqiaZYnMKk8= -ariga.io/atlas v0.14.1-0.20230918065911-83ad451a4935/go.mod h1:isZrlzJ5cpoCoKFoY9knZug7Lq4pP1cm8g3XciLZ0Pw= +ariga.io/atlas v0.15.0 h1:9lwSVcO/D3WgaCzstSGqR1hEDtsGibu6JqUofEI/0sY= +ariga.io/atlas v0.15.0/go.mod h1:isZrlzJ5cpoCoKFoY9knZug7Lq4pP1cm8g3XciLZ0Pw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -entgo.io/ent v0.12.4 h1:LddPnAyxls/O7DTXZvUGDj0NZIdGSu317+aoNLJWbD8= -entgo.io/ent v0.12.4/go.mod h1:Y3JVAjtlIk8xVZYSn3t3mf8xlZIn5SAOXZQxD6kKI+Q= +entgo.io/ent v0.12.5 h1:KREM5E4CSoej4zeGa88Ou/gfturAnpUv0mzAjch1sj4= +entgo.io/ent v0.12.5/go.mod h1:Y3JVAjtlIk8xVZYSn3t3mf8xlZIn5SAOXZQxD6kKI+Q= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= @@ -49,62 +12,54 @@ github.com/MicahParks/keyfunc v1.9.0 h1:lhKd5xrFHLNOWrDc4Tyb/Q1AJ4LCzQ48GVJyVIID github.com/MicahParks/keyfunc v1.9.0/go.mod h1:IdnCilugA0O/99dW+/MkvlyrsX8+L8+x95xuVNtM5jw= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= -github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= -github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= +github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/coreos/go-oidc/v3 v3.6.0 h1:AKVxfYw1Gmkn/w96z0DbT/B/xFnzTd3MkZvWLjF4n/o= -github.com/coreos/go-oidc/v3 v3.6.0/go.mod h1:ZpHUsHBucTUj6WOkrP4E20UPynbLZzhTQ1XKCXkxyPc= +github.com/coreos/go-oidc/v3 v3.9.0 h1:0J/ogVOd4y8P0f0xUh8l9t07xRP/d8tccvjHl2dcsSo= +github.com/coreos/go-oidc/v3 v3.9.0/go.mod h1:rTKz2PYwftcrtoCzV5g5kvfJoWcm0Mk8AF8y1iAQro4= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= -github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= +github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a h1:yDWHCSQ40h88yih2JAcL6Ls/kVkSE8GFACTGVnMPruw= +github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a/go.mod h1:7Ga40egUymuWXxAe151lTNnCv97MddSOVsjpPPkityA= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-co-op/gocron v1.35.0 h1:niC91OHiSEimXgPPay02AI1gLGL4JGBgDzmWtgZ8n5A= -github.com/go-co-op/gocron v1.35.0/go.mod h1:NLi+bkm4rRSy1F8U7iacZOz0xPseMoIOnvabGoSe/no= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-co-op/gocron v1.37.0 h1:ZYDJGtQ4OMhTLKOKMIch+/CY70Brbb1dGdooLEhh7b0= +github.com/go-co-op/gocron v1.37.0/go.mod h1:3L/n6BkO7ABj+TrfSVXLRzsP26zmikL4ISkLQ0O8iNY= github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkchhA= github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNPXu/4= github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4= -github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= -github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= -github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= -github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= -github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= -github.com/go-openapi/spec v0.20.8 h1:ubHmXNY3FCIOinT8RNrrPfGc9t7I1qhPtdOGoG2AxRU= -github.com/go-openapi/spec v0.20.8/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= -github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= -github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/jsonpointer v0.20.1 h1:MkK4VEIEZMj4wT9PmjaUmGflVBr9nvud4Q4UVFbDoBE= +github.com/go-openapi/jsonpointer v0.20.1/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs= +github.com/go-openapi/jsonreference v0.20.3 h1:EjGcjTW8pD1mRis6+w/gmoBdqv5+RbE9B85D1NgDOVQ= +github.com/go-openapi/jsonreference v0.20.3/go.mod h1:FviDZ46i9ivh810gqzFLl5NttD5q3tSlMLqLr6okedM= +github.com/go-openapi/spec v0.20.12 h1:cgSLbrsmziAP2iais+Vz7kSazwZ8rsUZd6TUzdDgkVI= +github.com/go-openapi/spec v0.20.12/go.mod h1:iSCgnBcwbMW9SfzJb8iYynXvcY6C/QFrI7otzF7xGM4= +github.com/go-openapi/swag v0.22.5 h1:fVS63IE3M0lsuWRzuom3RLwUMVI2peDH01s6M70ugys= +github.com/go-openapi/swag v0.22.5/go.mod h1:Gl91UqO+btAM0plGGxHqJcQZ1ZTy6jbmridBTsDy8A0= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= @@ -112,89 +67,58 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE= github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/googleapis v1.4.1 h1:1Yx4Myt7BxzvUr5ldGSbwYiZG6t9wGBZ+8/fX3Wvtq0= +github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/gogo/status v1.1.1 h1:DuHXlSFHNKqTQ+/ACf5Vs6r4X/dH2EgIzR9Vr+H65kg= +github.com/gogo/status v1.1.1/go.mod h1:jpG3dM5QPcqu19Hg8lkUhBFBa3TcLs1DG7+2Jqci7oU= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= -github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= +github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= +github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/hcl/v2 v2.16.1 h1:BwuxEMD/tsYgbhIW7UuI3crjovf3MzuFWiVgiv57iHg= -github.com/hashicorp/hcl/v2 v2.16.1/go.mod h1:JRmR89jycNkrrqnMmvPDMd56n1rQJ2Q6KocSLCMCXng= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/hashicorp/hcl/v2 v2.19.1 h1://i05Jqznmb2EXqa39Nsvyan2o5XyMowW5fnCKW5RPI= +github.com/hashicorp/hcl/v2 v2.19.1/go.mod h1:ThLC89FV4p9MPW804KVbe/cEXoQ8NZEh+JtMeeGErHE= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= +github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= @@ -204,94 +128,100 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4= -github.com/labstack/echo/v4 v4.11.1 h1:dEpLU2FLg4UVmvCGPuk/APjlH6GDpbEPti61srUUUs4= -github.com/labstack/echo/v4 v4.11.1/go.mod h1:YuYRTSM3CHs2ybfrL8Px48bO6BAnYIN4l8wSTMP6BDQ= -github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= -github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= +github.com/labstack/echo/v4 v4.11.3 h1:Upyu3olaqSHkCjs1EJJwQ3WId8b8b1hxbogyommKktM= +github.com/labstack/echo/v4 v4.11.3/go.mod h1:UcGuQ8V6ZNRmSweBIJkPvGfwCMIlFmiqrPqiEBfPYws= +github.com/labstack/gommon v0.4.1 h1:gqEff0p/hTENGMABzezPoPSRtIh1Cvw0ueMOe0/dfOk= +github.com/labstack/gommon v0.4.1/go.mod h1:TyTrpPqxR5KMk8LKVtLmfMjeQ5FEkBYdxLYPw/WfrOM= github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= -github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= -github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= -github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= -github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= +github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw= +github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= +github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= -github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= +github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= -github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= -github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= -github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= -github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= -github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= +github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= +github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= +github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= -github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= -github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= -github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= -github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= -github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= -github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= +github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= +github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.1 h1:4VhoImhV/Bm0ToFkXFi8hXNXwpDRZ/ynw3amt82mzq0= +github.com/stretchr/objx v0.5.1/go.mod h1:/iHQpkQwBD6DLUmQ4pE+s1TXdob1mORJ4/UFdrifcy0= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= -github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/swaggo/echo-swagger v1.4.1 h1:Yf0uPaJWp1uRtDloZALyLnvdBeoEL5Kc7DtnjzO/TUk= github.com/swaggo/echo-swagger v1.4.1/go.mod h1:C8bSi+9yH2FLZsnhqMZLIZddpUxZdBYuNHbtaS1Hljc= github.com/swaggo/files/v2 v2.0.0 h1:hmAt8Dkynw7Ssz46F6pn8ok6YmGZqHSVLZ+HQM7i0kw= @@ -300,331 +230,149 @@ github.com/swaggo/swag v1.16.2 h1:28Pp+8DkQoV+HLzLx8RGJZXNGKbFqnuvSbAAtoxiY04= github.com/swaggo/swag v1.16.2/go.mod h1:6YzXnDcpr0767iOejs318CwYkCQqyGer6BizOg03f+E= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/zclconf/go-cty v1.12.1 h1:PcupnljUm9EIvbgSHQnHhUr3fO6oFmkOrvs2BAFNXXY= -github.com/zclconf/go-cty v1.12.1/go.mod h1:s9IfD1LK5ccNMSWCVFCE2rJfHiZgi7JijgeWIMfhLvA= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zclconf/go-cty v1.14.1 h1:t9fyA35fwjjUMcmL5hLER+e/rEPqrbCK1/OSE4SI9KA= +github.com/zclconf/go-cty v1.14.1/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= +go.temporal.io/api v1.26.0 h1:N4V0Daqa0qqK5+9LELSZV7clBYrwB4l33iaFfKgycPk= +go.temporal.io/api v1.26.0/go.mod h1:uVAcpQJ6bM4mxZ3m7vSHU65fHjrwy9ktGQMtsNfMZQQ= +go.temporal.io/sdk v1.25.1 h1:jC9l9vHHz5OJ7PR6OjrpYSN4+uEG0bLe5rdF9nlMSGk= +go.temporal.io/sdk v1.25.1/go.mod h1:X7iFKZpsj90BfszfpFCzLX8lwEJXbnRrl351/HyEgmU= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/exp v0.0.0-20231214170342-aacd6d4b4611 h1:qCEDpW1G+vcj3Y7Fy52pEM1AWm3abj8WimGYejI3SC4= +golang.org/x/exp v0.0.0-20231214170342-aacd6d4b4611/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= -golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4= -golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4= +golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ= +golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.8.1-0.20230428195545-5283a0178901 h1:0wxTF6pSjIIhNt7mo9GvjDfzyCOiWhmICgtO/Ah948s= -golang.org/x/tools v0.8.1-0.20230428195545-5283a0178901/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= +golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= +google.golang.org/genproto v0.0.0-20180518175338-11a468237815/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 h1:YJ5pD9rF8o9Qtta0Cmy9rdBwkSjrTCT6XTiUQVOtIos= +google.golang.org/genproto v0.0.0-20231212172506-995d672761c0/go.mod h1:l/k7rMz0vFTBPy+tFSGvXEd3z+BcoG1k7EHbqm+YBsY= +google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0 h1:s1w3X6gQxwrLEpxnLd/qXTVLgQE2yXwaOaoa6IlY/+o= +google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0/go.mod h1:CAny0tYF+0/9rmDB9fahA9YLzX3+AEVl1qXbv5hhj6c= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0 h1:/jFB8jK5R3Sq3i/lmeZO0cATSzFfZaJq1J2Euan3XKU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0/go.mod h1:FUoWkonphQm3RhTS+kOEhF8h0iDpm4tdXolVCeZ9KKA= +google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU= +google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= @@ -633,20 +381,13 @@ gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/internal/activities/general.go b/internal/activities/general.go new file mode 100644 index 00000000..0a5e429d --- /dev/null +++ b/internal/activities/general.go @@ -0,0 +1,35 @@ +package activities + +import ( + "context" + "fmt" + + "github.com/zibbp/ganymede/internal/database" + "github.com/zibbp/ganymede/internal/dto" + "github.com/zibbp/ganymede/internal/utils" +) + +func CreateDirectory(ctx context.Context, input dto.ArchiveVideoInput) error { + + _, err := database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskVodCreateFolder(utils.Running).Save(ctx) + if err != nil { + return err + } + + err = utils.CreateFolder(fmt.Sprintf("%s/%s", input.Channel.Name, input.Vod.FolderName)) + if err != nil { + + _, err := database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskVodCreateFolder(utils.Failed).Save(ctx) + if err != nil { + return err + } + return err + } + + _, err = database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskVodCreateFolder(utils.Success).Save(ctx) + if err != nil { + return err + } + + return nil +} diff --git a/internal/activities/video.go b/internal/activities/video.go new file mode 100644 index 00000000..64e16bc1 --- /dev/null +++ b/internal/activities/video.go @@ -0,0 +1,967 @@ +package activities + +import ( + "context" + "fmt" + "strconv" + "strings" + "time" + + osExec "os/exec" + + "github.com/rs/zerolog/log" + "github.com/spf13/viper" + "github.com/zibbp/ganymede/internal/chapter" + "github.com/zibbp/ganymede/internal/database" + "github.com/zibbp/ganymede/internal/dto" + "github.com/zibbp/ganymede/internal/exec" + "github.com/zibbp/ganymede/internal/twitch" + "github.com/zibbp/ganymede/internal/utils" + "go.temporal.io/sdk/activity" + "go.temporal.io/sdk/temporal" +) + +func convertTwitchChaptersToChapters(chapters []twitch.Node, duration int) ([]chapter.Chapter, error) { + if len(chapters) == 0 { + return nil, fmt.Errorf("no chapters found") + } + + convertedChapters := make([]chapter.Chapter, len(chapters)) + for i := 0; i < len(chapters); i++ { + convertedChapters[i].ID = chapters[i].ID + convertedChapters[i].Title = chapters[i].Description + convertedChapters[i].Type = string(chapters[i].Type) + convertedChapters[i].Start = int(chapters[i].PositionMilliseconds / 1000) + + if i+1 < len(chapters) { + convertedChapters[i].End = int(chapters[i+1].PositionMilliseconds / 1000) + } else { + convertedChapters[i].End = duration + } + } + + return convertedChapters, nil +} + +func ArchiveVideoActivity(ctx context.Context, input dto.ArchiveVideoInput) error { + return nil +} + +func SaveTwitchVideoInfo(ctx context.Context, input dto.ArchiveVideoInput) error { + + _, err := database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskVodSaveInfo(utils.Running).Save(ctx) + if err != nil { + return err + } + + twitchService := twitch.NewService() + twitchVideo, err := twitchService.GetVodByID(input.VideoID) + if err != nil { + _, dbErr := database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskVodSaveInfo(utils.Failed).Save(ctx) + if dbErr != nil { + return dbErr + } + return temporal.NewApplicationError(err.Error(), "", nil) + } + + // get chapters + twitchChapters, err := twitch.GQLGetChapters(input.VideoID) + if err != nil { + _, dbEr := database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskVodSaveInfo(utils.Failed).Save(ctx) + if dbEr != nil { + return dbEr + } + return temporal.NewApplicationError(err.Error(), "", nil) + } + + // convert twitch chapters to chapters + // get nodes from gql response + var nodes []twitch.Node + for _, v := range twitchChapters.Data.Video.Moments.Edges { + nodes = append(nodes, v.Node) + } + if len(nodes) > 0 { + chapters, err := convertTwitchChaptersToChapters(nodes, input.Vod.Duration) + if err != nil { + _, dbErr := database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskVodSaveInfo(utils.Failed).Save(ctx) + if dbErr != nil { + return dbErr + } + return temporal.NewApplicationError(err.Error(), "", nil) + } + // add chapters to database + chapterService := chapter.NewService() + for _, c := range chapters { + _, err := chapterService.CreateChapter(c, input.Vod.ID) + if err != nil { + _, dbErr := database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskVodSaveInfo(utils.Failed).Save(ctx) + if dbErr != nil { + return dbErr + } + return temporal.NewApplicationError(err.Error(), "", nil) + } + } + + twitchVideo.Chapters = chapters + } + + err = utils.WriteJson(twitchVideo, fmt.Sprintf("%s/%s", input.Channel.Name, input.Vod.FolderName), fmt.Sprintf("%s-info.json", input.Vod.FileName)) + if err != nil { + _, dbErr := database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskVodSaveInfo(utils.Failed).Save(ctx) + if dbErr != nil { + return dbErr + } + return temporal.NewApplicationError(err.Error(), "", nil) + } + + _, err = database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskVodSaveInfo(utils.Success).Save(ctx) + if err != nil { + return err + } + + return nil +} + +func SaveTwitchLiveVideoInfo(ctx context.Context, input dto.ArchiveVideoInput) error { + + _, dbErr := database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskVodSaveInfo(utils.Running).Save(ctx) + if dbErr != nil { + return dbErr + } + + twitchService := twitch.NewService() + stream, err := twitchService.GetStreams(fmt.Sprintf("?user_login=%s", input.Channel.Name)) + if err != nil { + _, dbErr := database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskVodSaveInfo(utils.Failed).Save(ctx) + if dbErr != nil { + return dbErr + } + return temporal.NewApplicationError(err.Error(), "", nil) + } + + if len(stream.Data) == 0 { + _, dbErr := database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskVodSaveInfo(utils.Failed).Save(ctx) + if dbErr != nil { + return dbErr + } + return fmt.Errorf("no stream found for channel %s", input.Channel.Name) + } + + twitchVideo := stream.Data[0] + + err = utils.WriteJson(twitchVideo, fmt.Sprintf("%s/%s", input.Channel.Name, input.Vod.FolderName), fmt.Sprintf("%s-info.json", input.Vod.FileName)) + if err != nil { + _, dbErr := database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskVodSaveInfo(utils.Failed).Save(ctx) + if dbErr != nil { + return dbErr + } + return temporal.NewApplicationError(err.Error(), "", nil) + } + + _, err = database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskVodSaveInfo(utils.Success).Save(ctx) + if err != nil { + return err + } + + return nil +} + +func DownloadTwitchThumbnails(ctx context.Context, input dto.ArchiveVideoInput) error { + + _, dbErr := database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskVodDownloadThumbnail(utils.Running).Save(ctx) + if dbErr != nil { + return dbErr + } + + twitchService := twitch.NewService() + twitchVideo, err := twitchService.GetVodByID(input.VideoID) + if err != nil { + _, dbErr := database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskVodDownloadThumbnail(utils.Failed).Save(ctx) + if dbErr != nil { + return dbErr + } + return temporal.NewApplicationError(err.Error(), "", nil) + } + + fullResThumbnailUrl := replacePlaceholders(twitchVideo.ThumbnailURL, "1920", "1080") + webResThumbnailUrl := replacePlaceholders(twitchVideo.ThumbnailURL, "640", "360") + + err = utils.DownloadFile(fullResThumbnailUrl, fmt.Sprintf("%s/%s", input.Channel.Name, input.Vod.FolderName), fmt.Sprintf("%s-thumbnail.jpg", input.Vod.FileName)) + if err != nil { + _, dbErr := database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskVodDownloadThumbnail(utils.Failed).Save(ctx) + if dbErr != nil { + return dbErr + } + return temporal.NewApplicationError(err.Error(), "", nil) + } + + err = utils.DownloadFile(webResThumbnailUrl, fmt.Sprintf("%s/%s", input.Channel.Name, input.Vod.FolderName), fmt.Sprintf("%s-web_thumbnail.jpg", input.Vod.FileName)) + if err != nil { + _, dbErr := database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskVodDownloadThumbnail(utils.Failed).Save(ctx) + if dbErr != nil { + return dbErr + } + return temporal.NewApplicationError(err.Error(), "", nil) + } + + _, err = database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskVodDownloadThumbnail(utils.Success).Save(ctx) + if err != nil { + return err + } + + return nil +} + +func DownloadTwitchLiveThumbnails(ctx context.Context, input dto.ArchiveVideoInput) error { + + _, dbErr := database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskVodDownloadThumbnail(utils.Running).Save(ctx) + if dbErr != nil { + return dbErr + } + + twitchService := twitch.NewService() + stream, err := twitchService.GetStreams(fmt.Sprintf("?user_login=%s", input.Channel.Name)) + if err != nil { + _, dbErr := database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskVodDownloadThumbnail(utils.Failed).Save(ctx) + if dbErr != nil { + return dbErr + } + return temporal.NewApplicationError(err.Error(), "", nil) + } + + if len(stream.Data) == 0 { + _, dbErr := database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskVodDownloadThumbnail(utils.Failed).Save(ctx) + if dbErr != nil { + return dbErr + } + return fmt.Errorf("no stream found for channel %s", input.Channel.Name) + } + + twitchVideo := stream.Data[0] + + fullResThumbnailUrl := replaceLivePlaceholders(twitchVideo.ThumbnailURL, "1920", "1080") + webResThumbnailUrl := replaceLivePlaceholders(twitchVideo.ThumbnailURL, "640", "360") + + err = utils.DownloadFile(fullResThumbnailUrl, fmt.Sprintf("%s/%s", input.Channel.Name, input.Vod.FolderName), fmt.Sprintf("%s-thumbnail.jpg", input.Vod.FileName)) + if err != nil { + _, dbErr := database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskVodDownloadThumbnail(utils.Failed).Save(ctx) + if dbErr != nil { + return dbErr + } + return temporal.NewApplicationError(err.Error(), "", nil) + } + + err = utils.DownloadFile(webResThumbnailUrl, fmt.Sprintf("%s/%s", input.Channel.Name, input.Vod.FolderName), fmt.Sprintf("%s-web_thumbnail.jpg", input.Vod.FileName)) + if err != nil { + _, dbErr := database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskVodDownloadThumbnail(utils.Failed).Save(ctx) + if dbErr != nil { + return dbErr + } + return temporal.NewApplicationError(err.Error(), "", nil) + } + + _, dbErr = database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskVodDownloadThumbnail(utils.Success).Save(ctx) + if dbErr != nil { + return dbErr + } + + return nil +} + +func replacePlaceholders(url, width, height string) string { + url = strings.ReplaceAll(url, "%{width}", width) + url = strings.ReplaceAll(url, "%{height}", height) + return url +} +func replaceLivePlaceholders(url, width, height string) string { + url = strings.ReplaceAll(url, "{width}", width) + url = strings.ReplaceAll(url, "{height}", height) + return url +} + +func DownloadTwitchVideo(ctx context.Context, input dto.ArchiveVideoInput) error { + + _, dbErr := database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskVideoDownload(utils.Running).Save(ctx) + if dbErr != nil { + return dbErr + } + + // Create a new context with cancel + ctx, cancel := context.WithCancel(ctx) + defer cancel() // Make sure to cancel when download is complete + + // Start a goroutine that sends a heartbeat every 30 seconds + go func() { + for { + select { + case <-ctx.Done(): + // If the context is done, stop the goroutine + return + default: + // Otherwise, record a heartbeat and sleep for 30 seconds + activity.RecordHeartbeat(ctx, "my-heartbeat") + time.Sleep(30 * time.Second) + } + } + }() + + // Start the download + err := exec.DownloadTwitchVodVideo(input.Vod) + if err != nil { + _, dbErr := database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskVideoDownload(utils.Failed).Save(ctx) + if dbErr != nil { + return dbErr + } + + return temporal.NewApplicationError(err.Error(), "", nil) + } + + _, dbErr = database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskVideoDownload(utils.Success).Save(ctx) + if dbErr != nil { + return dbErr + } + + return nil +} + +func DownloadTwitchLiveVideo(ctx context.Context, input dto.ArchiveVideoInput, ch chan bool) error { + + _, dbErr := database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskVideoDownload(utils.Running).Save(ctx) + if dbErr != nil { + return dbErr + } + + // Create a new context with cancel + ctx, cancel := context.WithCancel(ctx) + defer cancel() // Make sure to cancel when download is complete + + // Start a goroutine that sends a heartbeat every 30 seconds + go func() { + for { + select { + case <-ctx.Done(): + // If the context is done, stop the goroutine + return + default: + // Otherwise, record a heartbeat and sleep for 30 seconds + activity.RecordHeartbeat(ctx, "my-heartbeat") + time.Sleep(30 * time.Second) + } + } + }() + + // Start the download + err := exec.DownloadTwitchLiveVideo(ctx, input.Vod, input.Channel, input.LiveChatWorkflowId) + if err != nil { + _, dbErr := database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskVideoDownload(utils.Failed).Save(ctx) + if dbErr != nil { + return dbErr + } + return temporal.NewApplicationError(err.Error(), "", nil) + } + + _, dbErr = database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskVideoDownload(utils.Success).Save(ctx) + if dbErr != nil { + return dbErr + } + + // Update video duration with duration from downloaded video + duration, err := exec.GetVideoDuration(fmt.Sprintf("/tmp/%s_%s-video.mp4", input.Vod.ExtID, input.Vod.ID)) + if err != nil { + return temporal.NewApplicationError(err.Error(), "", nil) + } + _, dbErr = database.DB().Client.Vod.UpdateOneID(input.Vod.ID).SetDuration(duration).Save(ctx) + if dbErr != nil { + return dbErr + } + + return nil +} + +func PostprocessVideo(ctx context.Context, input dto.ArchiveVideoInput) error { + + _, dbErr := database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskVideoConvert(utils.Running).Save(ctx) + if dbErr != nil { + return dbErr + } + + // Create a new context with cancel + ctx, cancel := context.WithCancel(ctx) + defer cancel() // Make sure to cancel when download is complete + + // Start a goroutine that sends a heartbeat every 30 seconds + go func() { + for { + select { + case <-ctx.Done(): + // If the context is done, stop the goroutine + return + default: + // Otherwise, record a heartbeat and sleep for 30 seconds + activity.RecordHeartbeat(ctx, "my-heartbeat") + time.Sleep(30 * time.Second) + } + } + }() + + // Start post process + err := exec.ConvertTwitchVodVideo(input.Vod) + if err != nil { + _, dbErr := database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskVideoConvert(utils.Failed).Save(ctx) + if dbErr != nil { + return dbErr + } + return temporal.NewApplicationError(err.Error(), "", nil) + } + + // Convert to HLS if needed + if viper.GetBool("archive.save_as_hls") { + err = exec.ConvertToHLS(input.Vod) + if err != nil { + _, dbErr := database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskVideoConvert(utils.Failed).Save(ctx) + if dbErr != nil { + return dbErr + } + return temporal.NewApplicationError(err.Error(), "", nil) + } + } + + _, dbErr = database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskVideoConvert(utils.Success).Save(ctx) + if dbErr != nil { + return dbErr + } + + return nil +} + +func MoveVideo(ctx context.Context, input dto.ArchiveVideoInput) error { + + _, dbErr := database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskVideoMove(utils.Running).Save(ctx) + if dbErr != nil { + return dbErr + } + + // Create a new context with cancel + ctx, cancel := context.WithCancel(ctx) + defer cancel() // Make sure to cancel when download is complete + + // Start a goroutine that sends a heartbeat every 30 seconds + go func() { + for { + select { + case <-ctx.Done(): + // If the context is done, stop the goroutine + return + default: + // Otherwise, record a heartbeat and sleep for 30 seconds + activity.RecordHeartbeat(ctx, "my-heartbeat") + time.Sleep(30 * time.Second) + } + } + }() + + if viper.GetBool("archive.save_as_hls") { + sourcePath := fmt.Sprintf("/tmp/%s_%s-video_hls0", input.Vod.ExtID, input.Vod.ID) + destPath := fmt.Sprintf("/vods/%s/%s/%s-video_hls", input.Channel.Name, input.Vod.FolderName, input.Vod.FileName) + err := utils.MoveFolder(sourcePath, destPath) + if err != nil { + _, dbErr := database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskVideoMove(utils.Failed).Save(ctx) + if dbErr != nil { + return dbErr + } + return temporal.NewApplicationError(err.Error(), "", nil) + } + // Update video path to hls path + _, dbErr = database.DB().Client.Vod.UpdateOneID(input.Vod.ID).SetVideoPath(fmt.Sprintf("/vods/%s/%s/%s-video_hls/%s-video.m3u8", input.Channel.Name, input.Vod.FolderName, input.Vod.FileName, input.Vod.ExtID)).Save(ctx) + if dbErr != nil { + return dbErr + } + } else { + sourcePath := fmt.Sprintf("/tmp/%s_%s-video-convert.mp4", input.Vod.ExtID, input.Vod.ID) + destPath := fmt.Sprintf("/vods/%s/%s/%s-video.mp4", input.Channel.Name, input.Vod.FolderName, input.Vod.FileName) + + err := utils.MoveFile(sourcePath, destPath) + if err != nil { + _, dbErr := database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskVideoMove(utils.Failed).Save(ctx) + if dbErr != nil { + return dbErr + } + return temporal.NewApplicationError(err.Error(), "", nil) + } + } + + // Clean up files + // Delete source file + err := utils.DeleteFile(fmt.Sprintf("/tmp/%s_%s-video.mp4", input.Vod.ExtID, input.Vod.ID)) + if err != nil { + log.Info().Err(err).Msgf("error deleting source file for vod %s", input.Vod.ID) + } + + _, dbErr = database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskVideoMove(utils.Success).Save(ctx) + if dbErr != nil { + return dbErr + } + + return nil +} + +func DownloadTwitchChat(ctx context.Context, input dto.ArchiveVideoInput) error { + + _, dbErr := database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskChatDownload(utils.Running).Save(ctx) + if dbErr != nil { + return dbErr + } + + // Create a new context with cancel + ctx, cancel := context.WithCancel(ctx) + defer cancel() // Make sure to cancel when download is complete + + // Start a goroutine that sends a heartbeat every 30 seconds + go func() { + for { + select { + case <-ctx.Done(): + // If the context is done, stop the goroutine + return + default: + // Otherwise, record a heartbeat and sleep for 30 seconds + activity.RecordHeartbeat(ctx, "my-heartbeat") + time.Sleep(30 * time.Second) + } + } + }() + + // Start the download + err := exec.DownloadTwitchVodChat(input.Vod) + if err != nil { + _, dbErr := database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskChatDownload(utils.Failed).Save(ctx) + if dbErr != nil { + return dbErr + } + return temporal.NewApplicationError(err.Error(), "", nil) + } + + // copy json to vod folder + sourcePath := fmt.Sprintf("/tmp/%s_%s-chat.json", input.Vod.ExtID, input.Vod.ID) + destPath := fmt.Sprintf("/vods/%s/%s/%s-chat.json", input.Channel.Name, input.Vod.FolderName, input.Vod.FileName) + + err = utils.CopyFile(sourcePath, destPath) + if err != nil { + _, dbErr := database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskChatDownload(utils.Failed).Save(ctx) + if dbErr != nil { + return dbErr + } + return temporal.NewApplicationError(err.Error(), "", nil) + } + + _, dbErr = database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskChatDownload(utils.Success).Save(ctx) + if dbErr != nil { + return dbErr + } + + return nil +} + +func DownloadTwitchLiveChat(ctx context.Context, input dto.ArchiveVideoInput) error { + + _, dbErr := database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskChatDownload(utils.Running).Save(ctx) + if dbErr != nil { + return dbErr + } + + // Create a new context with cancel + ctx, cancel := context.WithCancel(ctx) + defer cancel() // Make sure to cancel when download is complete + + // Start a goroutine that sends a heartbeat every 30 seconds + go func() { + for { + select { + case <-ctx.Done(): + // If the context is done, stop the goroutine + return + default: + // Otherwise, record a heartbeat and sleep for 30 seconds + activity.RecordHeartbeat(ctx, "my-heartbeat") + time.Sleep(30 * time.Second) + } + } + }() + + // Start the download + err := exec.DownloadTwitchLiveChat(ctx, input.Vod, input.Channel, input.Queue) + if err != nil { + _, dbErr := database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskChatDownload(utils.Failed).Save(ctx) + if dbErr != nil { + return dbErr + } + return temporal.NewApplicationError(err.Error(), "", nil) + } + + // copy json to vod folder + sourcePath := fmt.Sprintf("/tmp/%s_%s-live-chat.json", input.Vod.ExtID, input.Vod.ID) + destPath := fmt.Sprintf("/vods/%s/%s/%s-live-chat.json", input.Channel.Name, input.Vod.FolderName, input.Vod.FileName) + + err = utils.CopyFile(sourcePath, destPath) + if err != nil { + _, dbErr := database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskChatDownload(utils.Failed).Save(ctx) + if dbErr != nil { + return dbErr + } + return temporal.NewApplicationError(err.Error(), "", nil) + } + + _, dbErr = database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskChatDownload(utils.Success).Save(ctx) + if dbErr != nil { + return dbErr + } + + return nil +} + +func RenderTwitchChat(ctx context.Context, input dto.ArchiveVideoInput) error { + + _, dbErr := database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskChatRender(utils.Running).Save(ctx) + if dbErr != nil { + return dbErr + } + + // Create a new context with cancel + ctx, cancel := context.WithCancel(ctx) + defer cancel() // Make sure to cancel when download is complete + + // Start a goroutine that sends a heartbeat every 30 seconds + go func() { + for { + select { + case <-ctx.Done(): + // If the context is done, stop the goroutine + return + default: + // Otherwise, record a heartbeat and sleep for 30 seconds + activity.RecordHeartbeat(ctx, "my-heartbeat") + time.Sleep(30 * time.Second) + } + } + }() + + // Start the download + err, _ := exec.RenderTwitchVodChat(input.Vod) + if err != nil { + _, dbErr := database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskChatRender(utils.Failed).Save(ctx) + if dbErr != nil { + return dbErr + } + return temporal.NewApplicationError(err.Error(), "", nil) + } + + _, dbErr = database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskChatRender(utils.Success).Save(ctx) + if dbErr != nil { + return dbErr + } + + return nil +} + +func MoveChat(ctx context.Context, input dto.ArchiveVideoInput) error { + + _, dbErr := database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskChatMove(utils.Running).Save(ctx) + if dbErr != nil { + return dbErr + } + + // Create a new context with cancel + ctx, cancel := context.WithCancel(ctx) + defer cancel() // Make sure to cancel when download is complete + + // Start a goroutine that sends a heartbeat every 30 seconds + go func() { + for { + select { + case <-ctx.Done(): + // If the context is done, stop the goroutine + return + default: + // Otherwise, record a heartbeat and sleep for 30 seconds + activity.RecordHeartbeat(ctx, "my-heartbeat") + time.Sleep(30 * time.Second) + } + } + }() + + sourcePath := fmt.Sprintf("/tmp/%s_%s-chat.json", input.Vod.ExtID, input.Vod.ID) + destPath := fmt.Sprintf("/vods/%s/%s/%s-chat.json", input.Channel.Name, input.Vod.FolderName, input.Vod.FileName) + + err := utils.MoveFile(sourcePath, destPath) + if err != nil { + _, dbErr := database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskChatMove(utils.Failed).Save(ctx) + if dbErr != nil { + return dbErr + } + return temporal.NewApplicationError(err.Error(), "", nil) + } + + if input.Queue.RenderChat { + sourcePath = fmt.Sprintf("/tmp/%s_%s-chat.mp4", input.Vod.ExtID, input.Vod.ID) + destPath = fmt.Sprintf("/vods/%s/%s/%s-chat.mp4", input.Channel.Name, input.Vod.FolderName, input.Vod.FileName) + + err = utils.MoveFile(sourcePath, destPath) + if err != nil { + _, dbErr := database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskChatMove(utils.Failed).Save(ctx) + if dbErr != nil { + return dbErr + } + return temporal.NewApplicationError(err.Error(), "", nil) + } + } + + _, dbErr = database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskChatMove(utils.Success).Save(ctx) + if dbErr != nil { + return dbErr + } + + return nil +} + +func KillTwitchLiveChatDownload(ctx context.Context, input dto.ArchiveVideoInput) error { + + log.Info().Msgf("killing chat downloader for channel %s", input.Channel.Name) + + // find pid of chat_downloader to kill + cmd := osExec.Command("pgrep", "-f", fmt.Sprintf("chat_downloader https://twitch.tv/%s", input.Channel.Name)) + out, err := cmd.Output() + if err != nil { + return temporal.NewApplicationError(err.Error(), "", nil) + } + pid := strings.ReplaceAll(string(out), "\n", "") + log.Debug().Msgf("found pid %s for chat_downloader", string(out)) + + // kill pid + cmd = osExec.Command("kill", "-2", pid) + _, err = cmd.Output() + if err != nil { + return temporal.NewApplicationError(err.Error(), "", nil) + } + + log.Info().Msgf("killed chat downloader for channel %s", input.Channel.Name) + + _, dbErr := database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskChatDownload(utils.Success).Save(ctx) + if dbErr != nil { + return dbErr + } + + return nil +} + +func ConvertTwitchLiveChat(ctx context.Context, input dto.ArchiveVideoInput) error { + + _, dbErr := database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskChatConvert(utils.Running).Save(ctx) + if dbErr != nil { + return dbErr + } + + // Create a new context with cancel + ctx, cancel := context.WithCancel(ctx) + defer cancel() // Make sure to cancel when download is complete + + // Start a goroutine that sends a heartbeat every 30 seconds + go func() { + for { + select { + case <-ctx.Done(): + // If the context is done, stop the goroutine + return + default: + // Otherwise, record a heartbeat and sleep for 30 seconds + activity.RecordHeartbeat(ctx, "my-heartbeat") + time.Sleep(30 * time.Second) + } + } + }() + + // Check if chat file exists + chatPath := fmt.Sprintf("/tmp/%s_%s-live-chat.json", input.Vod.ExtID, input.Vod.ID) + if !utils.FileExists(chatPath) { + log.Debug().Msgf("chat file does not exist %s - this means there were no chat messages - setting chat to complete", chatPath) + // Set queue chat task to complete + _, dbErr := database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskChatConvert(utils.Success).SetTaskChatRender(utils.Success).SetTaskChatMove((utils.Success)).Save(ctx) + if dbErr != nil { + return dbErr + } + // Set VOD chat to empty + _, dbErr = database.DB().Client.Vod.UpdateOneID(input.Vod.ID).SetChatVideoPath("").SetChatPath("").Save(ctx) + if dbErr != nil { + return dbErr + } + // Check if all task are done + return nil + } + + // Fetch streamer from Twitch API for their user ID + streamer, err := twitch.API.GetUserByLogin(input.Channel.Name) + if err != nil { + log.Error().Err(err).Msg("error getting streamer from Twitch API") + _, dbErr := database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskChatConvert(utils.Failed).Save(ctx) + if dbErr != nil { + return dbErr + } + return temporal.NewApplicationError(err.Error(), "", nil) + } + cID, err := strconv.Atoi(streamer.ID) + if err != nil { + log.Error().Err(err).Msg("error converting streamer ID to int") + _, dbErr := database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskChatConvert(utils.Failed).Save(ctx) + if dbErr != nil { + return dbErr + } + return temporal.NewApplicationError(err.Error(), "", nil) + } + + // update queue item + updatedQueue, dbErr := database.DB().Client.Queue.Get(ctx, input.Queue.ID) + if dbErr != nil { + return dbErr + } + input.Queue = updatedQueue + log.Info().Msgf("streamer ID: %s", streamer.ID) + // TwitchDownloader requires the ID of the video, or at least a previous video ID + videos, err := twitch.GetVideosByUser(streamer.ID, "archive") + if err != nil { + return temporal.NewApplicationError(err.Error(), "", nil) + } + + // loop through videos and find the one that matches vExtID + var previousVideoID string + for _, video := range videos { + if video.StreamID == input.Vod.ExtID { + previousVideoID = video.ID + } + } + // If no previous video ID was found, use the current video ID + if previousVideoID == "" { + log.Warn().Msgf("Unable to convert chat due to no previous video IDs") + // TODO: exit gracefully + } + + err = utils.ConvertTwitchLiveChatToVodChat(fmt.Sprintf("/tmp/%s_%s-live-chat.json", input.Vod.ExtID, input.Vod.ID), input.Channel.Name, input.Vod.ID.String(), input.Vod.ExtID, cID, input.Queue.ChatStart, string(previousVideoID)) + if err != nil { + log.Error().Err(err).Msg("error converting chat") + _, dbErr := database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskChatConvert(utils.Failed).Save(ctx) + if dbErr != nil { + return dbErr + } + return temporal.NewApplicationError(err.Error(), "", nil) + } + + // TwitchDownloader "chatupdate" + // Embeds emotes and badges into the chat file + err = exec.TwitchChatUpdate(input.Vod) + if err != nil { + log.Error().Err(err).Msg("error updating chat") + _, dbErr := database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskChatConvert(utils.Failed).Save(ctx) + if dbErr != nil { + return dbErr + } + return temporal.NewApplicationError(err.Error(), "", nil) + } + + // copy converted chat + sourcePath := fmt.Sprintf("/tmp/%s_%s-chat-convert.json", input.Vod.ExtID, input.Vod.ID) + destPath := fmt.Sprintf("/vods/%s/%s/%s-chat-convert.json", input.Channel.Name, input.Vod.FolderName, input.Vod.FileName) + + err = utils.CopyFile(sourcePath, destPath) + if err != nil { + log.Error().Err(err).Msg("error copying chat convert") + _, dbErr := database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskChatConvert(utils.Failed).Save(ctx) + if dbErr != nil { + return dbErr + } + return temporal.NewApplicationError(err.Error(), "", nil) + } + + _, dbErr = database.DB().Client.Queue.UpdateOneID(input.Queue.ID).SetTaskChatConvert(utils.Success).Save(ctx) + if dbErr != nil { + return dbErr + } + + return nil +} + +func TwitchSaveVideoChapters(ctx context.Context) error { + // Create a new context with cancel + ctx, cancel := context.WithCancel(ctx) + defer cancel() // Make sure to cancel when download is complete + + // Start a goroutine that sends a heartbeat every 30 seconds + go func() { + for { + select { + case <-ctx.Done(): + // If the context is done, stop the goroutine + return + default: + // Otherwise, record a heartbeat and sleep for 30 seconds + activity.RecordHeartbeat(ctx, "my-heartbeat") + time.Sleep(30 * time.Second) + } + } + }() + + // get all videos + videos, err := database.DB().Client.Vod.Query().All(ctx) + if err != nil { + return temporal.NewApplicationError(err.Error(), "", nil) + } + + for _, video := range videos { + if video.Type == "live" { + continue + } + if video.ExtID == "" { + continue + } + log.Debug().Msgf("getting chapters for video %s", video.ID) + // get chapters + twitchChapters, err := twitch.GQLGetChapters(video.ExtID) + if err != nil { + log.Error().Err(err).Msgf("error getting chapters for video %s", video.ID) + continue + } + + // convert twitch chapters to chapters + // get nodes from gql response + var nodes []twitch.Node + for _, v := range twitchChapters.Data.Video.Moments.Edges { + nodes = append(nodes, v.Node) + } + if len(nodes) > 0 { + chapters, err := convertTwitchChaptersToChapters(nodes, video.Duration) + if err != nil { + return temporal.NewApplicationError(err.Error(), "", nil) + } + // add chapters to database + chapterService := chapter.NewService() + // check if chapters already exist + existingChapters, err := chapterService.GetVideoChapters(video.ID) + if err != nil { + log.Error().Err(err).Msgf("error getting chapters for video %s", video.ID) + } + if len(existingChapters) > 0 { + log.Debug().Msgf("chapters already exist for video %s", video.ID) + continue + } + + for _, c := range chapters { + _, err := chapterService.CreateChapter(c, video.ID) + if err != nil { + return temporal.NewApplicationError(err.Error(), "", nil) + } + } + log.Info().Msgf("added %d chapters to video %s", len(chapters), video.ID) + } + // sleep for 0.25 seconds to not hit rate limit + time.Sleep(250 * time.Millisecond) + } + + return nil +} diff --git a/internal/archive/archive.go b/internal/archive/archive.go index a3212079..8f46f9a0 100644 --- a/internal/archive/archive.go +++ b/internal/archive/archive.go @@ -3,7 +3,6 @@ package archive import ( "context" "fmt" - "strconv" "strings" "time" @@ -15,12 +14,16 @@ import ( queue2 "github.com/zibbp/ganymede/ent/queue" "github.com/zibbp/ganymede/internal/channel" "github.com/zibbp/ganymede/internal/database" + "github.com/zibbp/ganymede/internal/dto" "github.com/zibbp/ganymede/internal/exec" "github.com/zibbp/ganymede/internal/notification" "github.com/zibbp/ganymede/internal/queue" + "github.com/zibbp/ganymede/internal/temporal" "github.com/zibbp/ganymede/internal/twitch" "github.com/zibbp/ganymede/internal/utils" "github.com/zibbp/ganymede/internal/vod" + "github.com/zibbp/ganymede/internal/workflows" + "go.temporal.io/sdk/client" ) type Service struct { @@ -194,14 +197,26 @@ func (s *Service) ArchiveTwitchVod(vID string, quality string, chat bool, render // If chat is disabled update queue if !chat { - q.Update().SetChatProcessing(false).SetTaskChatDownload(utils.Success).SetTaskChatRender(utils.Success).SetTaskChatMove(utils.Success).SaveX(context.Background()) - v.Update().SetChatPath("").SetChatVideoPath("").SaveX(context.Background()) + _, err := q.Update().SetChatProcessing(false).SetTaskChatDownload(utils.Success).SetTaskChatRender(utils.Success).SetTaskChatMove(utils.Success).Save(context.Background()) + if err != nil { + return nil, fmt.Errorf("error updating queue item: %v", err) + } + _, err = v.Update().SetChatPath("").SetChatVideoPath("").Save(context.Background()) + if err != nil { + return nil, fmt.Errorf("error updating vod: %v", err) + } } // If render chat is disabled update queue if !renderChat { - q.Update().SetTaskChatRender(utils.Success).SetRenderChat(false).SaveX(context.Background()) - v.Update().SetChatVideoPath("").SaveX(context.Background()) + _, err := q.Update().SetTaskChatRender(utils.Success).SetRenderChat(false).Save(context.Background()) + if err != nil { + return nil, fmt.Errorf("error updating queue item: %v", err) + } + _, err = v.Update().SetChatVideoPath("").Save(context.Background()) + if err != nil { + return nil, fmt.Errorf("error updating vod: %v", err) + } } // Re-query queue from DB for updated values @@ -210,26 +225,29 @@ func (s *Service) ArchiveTwitchVod(vID string, quality string, chat bool, render return nil, fmt.Errorf("error fetching queue item: %v", err) } - // Get max active queue items from config - maxActiveQueueItems := viper.GetInt("active_queue_items") - - // Get all queue items that are not on hold - qItems, err := s.Store.Client.Queue.Query().Where(queue2.Processing(true)).Where(queue2.OnHold(false)).All(context.Background()) - if err != nil { - return nil, fmt.Errorf("error fetching queue items: %v", err) + wfOptions := client.StartWorkflowOptions{ + ID: vUUID.String(), + TaskQueue: "archive", } - if len(qItems)-1 >= maxActiveQueueItems { - // If there are more than X active items in queue set new queue item to on hold - log.Debug().Msgf("more than %d active items in queue. setting new queue item %s to on hold", maxActiveQueueItems, q.ID) - q.Update().SetOnHold(true).SaveX(context.Background()) - return &TwitchVodResponse{ - VOD: v, - Queue: q, - }, nil + input := dto.ArchiveVideoInput{ + VideoID: vID, + Type: "vod", + Platform: "twitch", + Resolution: "source", + DownloadChat: true, + RenderChat: true, + Vod: v, + Channel: dbC, + Queue: q, + } + we, err := temporal.GetTemporalClient().Client.ExecuteWorkflow(context.Background(), wfOptions, workflows.ArchiveVideoWorkflow, input) + if err != nil { + log.Error().Err(err).Msg("error starting workflow") + return nil, fmt.Errorf("error starting workflow: %v", err) } - go s.TaskVodCreateFolder(dbC, v, q, true) + log.Debug().Msgf("workflow id %s started for vod %s", we.GetID(), vID) return &TwitchVodResponse{ VOD: v, @@ -379,13 +397,27 @@ func (s *Service) ArchiveTwitchLive(lwc *ent.Live, ts twitch.Live) (*TwitchVodRe // If chat is disabled update queue if !lwc.ArchiveChat { - q.Update().SetChatProcessing(false).SetTaskChatDownload(utils.Success).SetTaskChatConvert(utils.Success).SetTaskChatRender(utils.Success).SetTaskChatMove(utils.Success).SaveX(context.Background()) - v.Update().SetChatPath("").SetChatVideoPath("").SaveX(context.Background()) + _, err := q.Update().SetChatProcessing(false).SetTaskChatDownload(utils.Success).SetTaskChatConvert(utils.Success).SetTaskChatRender(utils.Success).SetTaskChatMove(utils.Success).Save(context.Background()) + if err != nil { + return nil, fmt.Errorf("error updating queue item: %v", err) + } + + _, err = v.Update().SetChatPath("").SetChatVideoPath("").Save(context.Background()) + if err != nil { + return nil, fmt.Errorf("error updating vod: %v", err) + } + } if !lwc.RenderChat { - q.Update().SetTaskChatRender(utils.Success).SetRenderChat(false).SaveX(context.Background()) - v.Update().SetChatVideoPath("").SaveX(context.Background()) + _, err := q.Update().SetTaskChatRender(utils.Success).SetRenderChat(false).Save(context.Background()) + if err != nil { + return nil, fmt.Errorf("error updating queue item: %v", err) + } + _, err = v.Update().SetChatVideoPath("").Save(context.Background()) + if err != nil { + return nil, fmt.Errorf("error updating vod: %v", err) + } } // Re-query queue from DB for updated values @@ -394,7 +426,33 @@ func (s *Service) ArchiveTwitchLive(lwc *ent.Live, ts twitch.Live) (*TwitchVodRe return nil, fmt.Errorf("error fetching queue item: %v", err) } - go s.TaskVodCreateFolder(dbC, v, q, true) + wfOptions := client.StartWorkflowOptions{ + ID: vUUID.String(), + TaskQueue: "archive", + } + + input := dto.ArchiveVideoInput{ + VideoID: ts.ID, + Type: "live", + Platform: "twitch", + Resolution: lwc.Resolution, + DownloadChat: lwc.ArchiveChat, + RenderChat: lwc.RenderChat, + Vod: v, + Channel: dbC, + Queue: q, + LiveWatchChannel: lwc, + } + + we, err := temporal.GetTemporalClient().Client.ExecuteWorkflow(context.Background(), wfOptions, workflows.ArchiveLiveVideoWorkflow, input) + if err != nil { + log.Error().Err(err).Msg("error starting workflow") + return nil, fmt.Errorf("error starting workflow: %v", err) + } + + log.Debug().Msgf("workflow id %s started for live stream %s", we.GetID(), ts.ID) + + // go s.TaskVodCreateFolder(dbC, v, q, true) return &TwitchVodResponse{ VOD: v, @@ -650,9 +708,7 @@ func (s *Service) TaskVodSaveLiveInfo(ch *ent.Channel, v *ent.Vod, q *ent.Queue, go s.TaskLiveVideoDownload(ch, v, q, true, busC, startChatDownloadChannel) // Check if chat download task is set to success - if q.TaskChatDownload == utils.Pending { - go s.TaskLiveChatDownload(ch, v, q, true, busC, startChatDownloadChannel, true) - } + } return nil } @@ -691,13 +747,13 @@ func (s *Service) TaskLiveVideoDownload(ch *ent.Channel, v *ent.Vod, q *ent.Queu log.Debug().Msgf("starting task video download for live stream %s", v.ID) q.Update().SetTaskVideoDownload(utils.Running).SaveX(context.Background()) - err := exec.DownloadTwitchLiveVideo(v, ch, startChatDownloadChannel) - if err != nil { - log.Error().Err(err).Msg("error downloading live video") - q.Update().SetTaskVideoDownload(utils.Failed).SaveX(context.Background()) - s.TaskError(ch, v, q, "video_download") - return - } + // err := exec.DownloadTwitchLiveVideo(v, ch, startChatDownloadChannel) + // if err != nil { + // log.Error().Err(err).Msg("error downloading live video") + // q.Update().SetTaskVideoDownload(utils.Failed).SaveX(context.Background()) + // s.TaskError(ch, v, q, "video_download") + // return + // } // Send kill command to chat download if q.TaskChatDownload != utils.Success { @@ -862,33 +918,33 @@ func (s *Service) TaskChatDownload(ch *ent.Channel, v *ent.Vod, q *ent.Queue, co } } -func (s *Service) TaskLiveChatDownload(ch *ent.Channel, v *ent.Vod, q *ent.Queue, cont bool, busC chan bool, startChatDownloadChannel chan bool, waitForVideo bool) { - log.Debug().Msgf("starting task chat download for live stream %s", v.ID) - q.Update().SetTaskChatDownload(utils.Running).SaveX(context.Background()) +// func (s *Service) TaskLiveChatDownload(ch *ent.Channel, v *ent.Vod, q *ent.Queue, cont bool, busC chan bool, startChatDownloadChannel chan bool, waitForVideo bool) { +// log.Debug().Msgf("starting task chat download for live stream %s", v.ID) +// q.Update().SetTaskChatDownload(utils.Running).SaveX(context.Background()) - err := exec.DownloadTwitchLiveChat(v, ch, q, busC, startChatDownloadChannel, waitForVideo) - if err != nil { - log.Error().Err(err).Msg("error downloading live chat") - q.Update().SetTaskChatDownload(utils.Failed).SaveX(context.Background()) - s.TaskError(ch, v, q, "chat_download") - return - } +// err := exec.DownloadTwitchLiveChat(v, ch, q, busC, startChatDownloadChannel, waitForVideo) +// if err != nil { +// log.Error().Err(err).Msg("error downloading live chat") +// q.Update().SetTaskChatDownload(utils.Failed).SaveX(context.Background()) +// s.TaskError(ch, v, q, "chat_download") +// return +// } - q.Update().SetTaskChatDownload(utils.Success).SaveX(context.Background()) +// q.Update().SetTaskChatDownload(utils.Success).SaveX(context.Background()) - // copy live chat - sourcePath := fmt.Sprintf("/tmp/%s_%s-live-chat.json", v.ExtID, v.ID) - destPath := fmt.Sprintf("/vods/%s/%s/%s-live-chat.json", ch.Name, v.FolderName, v.FileName) +// // copy live chat +// sourcePath := fmt.Sprintf("/tmp/%s_%s-live-chat.json", v.ExtID, v.ID) +// destPath := fmt.Sprintf("/vods/%s/%s/%s-live-chat.json", ch.Name, v.FolderName, v.FileName) - err = utils.CopyFile(sourcePath, destPath) - if err != nil { - log.Error().Err(err).Msg("error moving live chat") - } +// err = utils.CopyFile(sourcePath, destPath) +// if err != nil { +// log.Error().Err(err).Msg("error moving live chat") +// } - // Always convert live chat to vod chat - go s.TaskLiveChatConvert(ch, v, q, true) +// // Always convert live chat to vod chat +// go s.TaskLiveChatConvert(ch, v, q, true) -} +// } func (s *Service) TaskChatConvertRestart(ch *ent.Channel, v *ent.Vod, q *ent.Queue, cont bool) { // Check if chat file exists @@ -908,87 +964,87 @@ func (s *Service) TaskChatConvertRestart(ch *ent.Channel, v *ent.Vod, q *ent.Que } } - go s.TaskLiveChatConvert(ch, v, q, cont) + // go s.TaskLiveChatConvert(ch, v, q, cont) } -func (s *Service) TaskLiveChatConvert(ch *ent.Channel, v *ent.Vod, q *ent.Queue, cont bool) { - log.Debug().Msgf("starting task chat convert for vod %s", v.ID) - q.Update().SetTaskChatConvert(utils.Running).SaveX(context.Background()) - - // Check if chat file exists - chatPath := fmt.Sprintf("/tmp/%s_%s-live-chat.json", v.ExtID, v.ID) - if !utils.FileExists(chatPath) { - log.Debug().Msgf("chat file does not exist %s - this means there were no chat messages - setting chat to complete", chatPath) - // Set queue chat task to complete - q.Update().SetChatProcessing(false).SetTaskChatConvert(utils.Success).SetTaskChatRender(utils.Success).SetTaskChatMove(utils.Success).SaveX(context.Background()) - // Set VOD chat to empty - v.Update().SetChatPath("").SetChatVideoPath("").SaveX(context.Background()) - // Check if all task are done - go s.CheckIfLiveTasksAreDone(ch, v, q) - return - } - - // Fetch streamer from Twitch API for their user ID - streamer, err := twitch.API.GetUserByLogin(ch.Name) - if err != nil { - log.Error().Err(err).Msg("error getting streamer from Twitch API") - q.Update().SetTaskChatConvert(utils.Failed).SaveX(context.Background()) - s.TaskError(ch, v, q, "chat_convert") - return - } - cID, err := strconv.Atoi(streamer.ID) - if err != nil { - log.Error().Err(err).Msg("error converting streamer ID to int") - q.Update().SetTaskChatConvert(utils.Failed).SaveX(context.Background()) - s.TaskError(ch, v, q, "chat_convert") - return - } - - // Get queue item (refresh) - q, err = s.QueueService.GetQueueItem(q.ID) - if err != nil { - log.Error().Err(err).Msg("error getting queue item") - q.Update().SetTaskChatConvert(utils.Failed).SaveX(context.Background()) - s.TaskError(ch, v, q, "chat_convert") - return - } - - err = utils.ConvertTwitchLiveChatToVodChat(fmt.Sprintf("/tmp/%s_%s-live-chat.json", v.ExtID, v.ID), ch.Name, v.ID.String(), v.ExtID, cID, q.ChatStart) - if err != nil { - log.Error().Err(err).Msg("error converting chat") - q.Update().SetTaskChatConvert(utils.Failed).SaveX(context.Background()) - s.TaskError(ch, v, q, "chat_convert") - log.Info().Msgf("livestream chat task failed - setting vod to processed so it can be viewed") - v.Update().SetProcessing(false).SaveX(context.Background()) - return - } - - // TwitchDownloader "chatupdate" - // Embeds emotes and badges into the chat file - err = exec.TwitchChatUpdate(v) - if err != nil { - log.Error().Err(err).Msg("error updating chat") - q.Update().SetTaskChatConvert(utils.Failed).SaveX(context.Background()) - s.TaskError(ch, v, q, "chat_convert") - log.Info().Msgf("livestream chat task failed - setting vod to processed so it can be viewed") - v.Update().SetProcessing(false).SaveX(context.Background()) - return - } - - q.Update().SetTaskChatConvert(utils.Success).SaveX(context.Background()) - - // copy converted chat - sourcePath := fmt.Sprintf("/tmp/%s_%s-chat-convert.json", v.ExtID, v.ID) - destPath := fmt.Sprintf("/vods/%s/%s/%s-chat-convert.json", ch.Name, v.FolderName, v.FileName) - - err = utils.CopyFile(sourcePath, destPath) - if err != nil { - log.Error().Err(err).Msg("error copying chat convert") - } - - // Always render chat - go s.TaskChatRender(ch, v, q, true) -} +// func (s *Service) TaskLiveChatConvert(ch *ent.Channel, v *ent.Vod, q *ent.Queue, cont bool) { +// log.Debug().Msgf("starting task chat convert for vod %s", v.ID) +// q.Update().SetTaskChatConvert(utils.Running).SaveX(context.Background()) + +// // Check if chat file exists +// chatPath := fmt.Sprintf("/tmp/%s_%s-live-chat.json", v.ExtID, v.ID) +// if !utils.FileExists(chatPath) { +// log.Debug().Msgf("chat file does not exist %s - this means there were no chat messages - setting chat to complete", chatPath) +// // Set queue chat task to complete +// q.Update().SetChatProcessing(false).SetTaskChatConvert(utils.Success).SetTaskChatRender(utils.Success).SetTaskChatMove(utils.Success).SaveX(context.Background()) +// // Set VOD chat to empty +// v.Update().SetChatPath("").SetChatVideoPath("").SaveX(context.Background()) +// // Check if all task are done +// go s.CheckIfLiveTasksAreDone(ch, v, q) +// return +// } + +// // // Fetch streamer from Twitch API for their user ID +// // streamer, err := twitch.API.GetUserByLogin(ch.Name) +// // if err != nil { +// // log.Error().Err(err).Msg("error getting streamer from Twitch API") +// // q.Update().SetTaskChatConvert(utils.Failed).SaveX(context.Background()) +// // s.TaskError(ch, v, q, "chat_convert") +// // return +// // } +// // cID, err := strconv.Atoi(streamer.ID) +// // if err != nil { +// // log.Error().Err(err).Msg("error converting streamer ID to int") +// // q.Update().SetTaskChatConvert(utils.Failed).SaveX(context.Background()) +// // s.TaskError(ch, v, q, "chat_convert") +// // return +// // } + +// // Get queue item (refresh) +// q, err = s.QueueService.GetQueueItem(q.ID) +// if err != nil { +// log.Error().Err(err).Msg("error getting queue item") +// q.Update().SetTaskChatConvert(utils.Failed).SaveX(context.Background()) +// s.TaskError(ch, v, q, "chat_convert") +// return +// } + +// // err = utils.ConvertTwitchLiveChatToVodChat(fmt.Sprintf("/tmp/%s_%s-live-chat.json", v.ExtID, v.ID), ch.Name, v.ID.String(), v.ExtID, cID, q.ChatStart) +// // if err != nil { +// // log.Error().Err(err).Msg("error converting chat") +// // q.Update().SetTaskChatConvert(utils.Failed).SaveX(context.Background()) +// // s.TaskError(ch, v, q, "chat_convert") +// // log.Info().Msgf("livestream chat task failed - setting vod to processed so it can be viewed") +// // v.Update().SetProcessing(false).SaveX(context.Background()) +// // return +// // } + +// // TwitchDownloader "chatupdate" +// // Embeds emotes and badges into the chat file +// err = exec.TwitchChatUpdate(v) +// if err != nil { +// log.Error().Err(err).Msg("error updating chat") +// q.Update().SetTaskChatConvert(utils.Failed).SaveX(context.Background()) +// s.TaskError(ch, v, q, "chat_convert") +// log.Info().Msgf("livestream chat task failed - setting vod to processed so it can be viewed") +// v.Update().SetProcessing(false).SaveX(context.Background()) +// return +// } + +// q.Update().SetTaskChatConvert(utils.Success).SaveX(context.Background()) + +// // copy converted chat +// sourcePath := fmt.Sprintf("/tmp/%s_%s-chat-convert.json", v.ExtID, v.ID) +// destPath := fmt.Sprintf("/vods/%s/%s/%s-chat-convert.json", ch.Name, v.FolderName, v.FileName) + +// err = utils.CopyFile(sourcePath, destPath) +// if err != nil { +// log.Error().Err(err).Msg("error copying chat convert") +// } + +// // Always render chat +// go s.TaskChatRender(ch, v, q, true) +// } func (s *Service) TaskChatRender(ch *ent.Channel, v *ent.Vod, q *ent.Queue, cont bool) { var renderContinue bool @@ -999,18 +1055,18 @@ func (s *Service) TaskChatRender(ch *ent.Channel, v *ent.Vod, q *ent.Queue, cont log.Debug().Msgf("starting task chat render for vod %s", v.ID) q.Update().SetTaskChatRender(utils.Running).SaveX(context.Background()) - err, rCont := exec.RenderTwitchVodChat(v, q) - if err != nil { - log.Error().Err(err).Msg("error rendering chat") - q.Update().SetTaskChatRender(utils.Failed).SaveX(context.Background()) - s.TaskError(ch, v, q, "chat_render") - if q.LiveArchive { - log.Info().Msgf("livestream chat task failed - setting vod to processed so it can be viewed") - v.Update().SetProcessing(false).SaveX(context.Background()) - } - return - } - renderContinue = rCont + // err, rCont := exec.RenderTwitchVodChat(v, q) + // if err != nil { + // log.Error().Err(err).Msg("error rendering chat") + // q.Update().SetTaskChatRender(utils.Failed).SaveX(context.Background()) + // s.TaskError(ch, v, q, "chat_render") + // if q.LiveArchive { + // log.Info().Msgf("livestream chat task failed - setting vod to processed so it can be viewed") + // v.Update().SetProcessing(false).SaveX(context.Background()) + // } + // return + // } + // renderContinue = rCont q.Update().SetTaskChatRender(utils.Success).SaveX(context.Background()) } diff --git a/internal/chapter/chapter.go b/internal/chapter/chapter.go new file mode 100644 index 00000000..48c74500 --- /dev/null +++ b/internal/chapter/chapter.go @@ -0,0 +1,64 @@ +package chapter + +import ( + "context" + "fmt" + + "github.com/google/uuid" + "github.com/zibbp/ganymede/ent" + entChapter "github.com/zibbp/ganymede/ent/chapter" + "github.com/zibbp/ganymede/ent/vod" + "github.com/zibbp/ganymede/internal/database" + "github.com/zibbp/ganymede/internal/utils" +) + +type Service struct { +} + +func NewService() *Service { + return &Service{} +} + +type Chapter struct { + ID string `json:"id"` + Type string `json:"type"` + Title string `json:"title"` + Start int `json:"start"` + End int `json:"end"` +} + +func (s *Service) CreateChapter(c Chapter, videoId uuid.UUID) (*ent.Chapter, error) { + dbVideo, err := database.DB().Client.Vod.Query().Where(vod.ID(videoId)).First(context.Background()) + if err != nil { + if _, ok := err.(*ent.NotFoundError); ok { + return nil, fmt.Errorf("video not found") + } + return nil, fmt.Errorf("error getting video: %v", err) + } + + dbChapter, err := database.DB().Client.Chapter.Create().SetType(c.Type).SetTitle(c.Title).SetStart(c.Start).SetEnd(c.End).SetVod(dbVideo).Save(context.Background()) + if err != nil { + return nil, fmt.Errorf("error creating chapter: %v", err) + } + + return dbChapter, nil +} + +func (s *Service) GetVideoChapters(videoId uuid.UUID) ([]*ent.Chapter, error) { + chapters, err := database.DB().Client.Chapter.Query().Where(entChapter.HasVodWith(vod.ID(videoId))).All(context.Background()) + if err != nil { + return nil, fmt.Errorf("error getting chapters: %v", err) + } + + return chapters, nil +} + +func (s *Service) CreateWebVtt(chapters []*ent.Chapter) (string, error) { + webVtt := "WEBVTT\n\n" + + for _, chapter := range chapters { + webVtt += fmt.Sprintf("%s --> %s\n%s\n\n", utils.SecondsToHHMMSS(chapter.Start), utils.SecondsToHHMMSS(chapter.End), chapter.Title) + } + + return webVtt, nil +} diff --git a/internal/database/database.go b/internal/database/database.go index dbd0dc73..92acf8f2 100644 --- a/internal/database/database.go +++ b/internal/database/database.go @@ -3,13 +3,14 @@ package database import ( "context" "fmt" + "os" + _ "github.com/lib/pq" "github.com/rs/zerolog/log" "github.com/spf13/viper" "github.com/zibbp/ganymede/ent" "github.com/zibbp/ganymede/internal/utils" "golang.org/x/crypto/bcrypt" - "os" ) var db *Database @@ -18,7 +19,7 @@ type Database struct { Client *ent.Client } -func InitializeDatabase() { +func InitializeDatabase(worker bool) { log.Debug().Msg("setting up database connection") dbHost := os.Getenv("DB_HOST") @@ -38,21 +39,22 @@ func InitializeDatabase() { log.Fatal().Err(err).Msg("error connecting to database") } - // Run auto migration - if err := client.Schema.Create(context.Background()); err != nil { - log.Fatal().Err(err).Msg("error running auto migration") - } - - isSeeded := viper.Get("db_seeded").(bool) - if !isSeeded { - log.Debug().Msg("seeding database") - if err := seedDatabase(client); err != nil { - log.Fatal().Err(err).Msg("error seeding database") + if !worker { + // Run auto migration + if err := client.Schema.Create(context.Background()); err != nil { + log.Fatal().Err(err).Msg("error running auto migration") } - viper.Set("db_seeded", true) - err := viper.WriteConfig() - if err != nil { - log.Fatal().Err(err).Msg("error writing config") + isSeeded := viper.Get("db_seeded").(bool) + if !isSeeded { + log.Debug().Msg("seeding database") + if err := seedDatabase(client); err != nil { + log.Fatal().Err(err).Msg("error seeding database") + } + viper.Set("db_seeded", true) + err := viper.WriteConfig() + if err != nil { + log.Fatal().Err(err).Msg("error writing config") + } } } db = &Database{Client: client} diff --git a/internal/dto/video.go b/internal/dto/video.go new file mode 100644 index 00000000..7073fa66 --- /dev/null +++ b/internal/dto/video.go @@ -0,0 +1,20 @@ +package dto + +import ( + "github.com/zibbp/ganymede/ent" +) + +type ArchiveVideoInput struct { + VideoID string + Type string + Platform string + Resolution string + DownloadChat bool + RenderChat bool + Vod *ent.Vod + Channel *ent.Channel + Queue *ent.Queue + LiveWatchChannel *ent.Live + LiveChatWorkflowId string + LiveChatArchiveWorkflowId string +} diff --git a/internal/exec/exec.go b/internal/exec/exec.go index 2d3fe514..503099a4 100644 --- a/internal/exec/exec.go +++ b/internal/exec/exec.go @@ -17,6 +17,8 @@ import ( "github.com/spf13/viper" "github.com/zibbp/ganymede/ent" "github.com/zibbp/ganymede/internal/config" + "github.com/zibbp/ganymede/internal/database" + "github.com/zibbp/ganymede/internal/temporal" "github.com/zibbp/ganymede/internal/twitch" "github.com/zibbp/ganymede/internal/utils" ) @@ -41,16 +43,19 @@ func DownloadTwitchVodVideo(v *ent.Vod) error { videoLogfile, err := os.Create(fmt.Sprintf("/logs/%s_%s-video.log", v.ExtID, v.ID)) if err != nil { - log.Error().Err(err).Msg("error creating video logfile") - return err + return fmt.Errorf("error creating video logfile: %w", err) } + defer videoLogfile.Close() cmd.Stdout = videoLogfile cmd.Stderr = videoLogfile if err := cmd.Run(); err != nil { - log.Error().Err(err).Msg("error running streamlink for vod video download") - return err + if exitError, ok := err.(*osExec.ExitError); ok { + log.Error().Err(err).Msg("error running streamlink for vod download") + return fmt.Errorf("error running streamlink for vod download with exit code %d: %w", exitError.ExitCode(), exitError) + } + return fmt.Errorf("error running streamlink for vod video download: %w", err) } log.Debug().Msgf("finished downloading vod video for %s", v.ExtID) @@ -62,23 +67,26 @@ func DownloadTwitchVodChat(v *ent.Vod) error { chatLogfile, err := os.Create(fmt.Sprintf("/logs/%s_%s-chat.log", v.ExtID, v.ID)) if err != nil { - log.Error().Err(err).Msg("error creating chat logfile") - return err + return fmt.Errorf("error creating chat logfile: %w", err) } defer chatLogfile.Close() cmd.Stdout = chatLogfile cmd.Stderr = chatLogfile if err := cmd.Run(); err != nil { + if exitError, ok := err.(*osExec.ExitError); ok { + log.Error().Err(err).Msg("error running TwitchDownloaderCLI for vod chat download") + return fmt.Errorf("error running TwitchDownloaderCLI for vod chat download with exit code %d: %w", exitError.ExitCode(), exitError) + } log.Error().Err(err).Msg("error running TwitchDownloaderCLI for vod chat download") - return err + return fmt.Errorf("error running TwitchDownloaderCLI for vod chat download: %w", err) } log.Debug().Msgf("finished downloading vod chat for %s", v.ExtID) return nil } -func RenderTwitchVodChat(v *ent.Vod, q *ent.Queue) (error, bool) { +func RenderTwitchVodChat(v *ent.Vod) (error, bool) { // Fetch config params chatRenderParams := viper.GetString("parameters.chat_render") // Split supplied params into array @@ -95,14 +103,17 @@ func RenderTwitchVodChat(v *ent.Vod, q *ent.Queue) (error, bool) { chatRenderLogfile, err := os.Create(fmt.Sprintf("/logs/%s_%s-chat-render.log", v.ExtID, v.ID)) if err != nil { - log.Error().Err(err).Msg("error creating chat render logfile") - return err, true + return fmt.Errorf("error creating chat render logfile: %w", err), true } defer chatRenderLogfile.Close() cmd.Stdout = chatRenderLogfile cmd.Stderr = chatRenderLogfile if err := cmd.Run(); err != nil { + if exitError, ok := err.(*osExec.ExitError); ok { + log.Error().Err(err).Msg("error running TwitchDownloaderCLI for vod chat render") + return fmt.Errorf("error running TwitchDownloaderCLI for vod chat render with exit code %d: %w", exitError.ExitCode(), exitError), true + } log.Error().Err(err).Msg("error running TwitchDownloaderCLI for vod chat render") // Check if error is because of no messages @@ -110,12 +121,13 @@ func RenderTwitchVodChat(v *ent.Vod, q *ent.Queue) (error, bool) { _, err := osExec.Command("bash", "-c", checkCmd).Output() if err != nil { log.Error().Err(err).Msg("error checking chat render logfile for no messages") - return err, true + return fmt.Errorf("erreor checking chat render logfile for no messages %w", err), true } - log.Debug().Msg("no messages found in chat render logfile. setting vod and queue to reflect no chat.") - v.Update().SetChatPath("").SetChatVideoPath("").SaveX(context.Background()) - q.Update().SetChatProcessing(false).SetTaskChatMove(utils.Success).SaveX(context.Background()) + // TODO: re-implment this + // log.Debug().Msg("no messages found in chat render logfile. setting vod and queue to reflect no chat.") + // v.Update().SetChatPath("").SetChatVideoPath("").SaveX(context.Background()) + // q.Update().SetChatProcessing(false).SetTaskChatMove(utils.Success).SaveX(context.Background()) return nil, false } @@ -185,7 +197,7 @@ func ConvertToHLS(v *ent.Vod) error { } -func DownloadTwitchLiveVideo(v *ent.Vod, ch *ent.Channel, startChatDownloadChannel chan bool) error { +func DownloadTwitchLiveVideo(ctx context.Context, v *ent.Vod, ch *ent.Channel, liveChatWorkflowId string) error { // Fetch config params liveStreamlinkParams := viper.GetString("parameters.streamlink_live") // Split supplied params into array @@ -262,32 +274,48 @@ func DownloadTwitchLiveVideo(v *ent.Vod, ch *ent.Channel, startChatDownloadChann v.Resolution = strings.Replace(v.Resolution, "30", "", 1) // Generate args for exec - newArgs := []string{"--force-progress", "--force", streamURL, fmt.Sprintf("%s,best", v.Resolution)} + args := []string{"--progress=force", "--force", streamURL, fmt.Sprintf("%s,best", v.Resolution)} // if proxy requires headers, pass them if proxyHeader != "" { - newArgs = append(newArgs, "--add-headers", proxyHeader) + args = append(args, "--add-headers", proxyHeader) } // pass twitch token as header if available // only pass if not using proxy for security reasons if twitchToken != "" && !proxyFound { - newArgs = append(newArgs, "--http-header", fmt.Sprintf("Authorization=OAuth %s", twitchToken)) + args = append(args, "--http-header", fmt.Sprintf("Authorization=OAuth %s", twitchToken)) } // pass config params - newArgs = append(newArgs, splitStreamlinkParams...) + args = append(args, splitStreamlinkParams...) - newArgs = append(newArgs, "-o", fmt.Sprintf("/tmp/%s_%s-video.mp4", v.ExtID, v.ID)) + filteredArgs := make([]string, 0, len(args)) + for _, arg := range args { + if arg != "" { + filteredArgs = append(filteredArgs, arg) + } + } + + cmdArgs := append(filteredArgs, "-o", fmt.Sprintf("/tmp/%s_%s-video.mp4", v.ExtID, v.ID)) - log.Debug().Msgf("streamlink live args: %v", newArgs) - log.Debug().Msgf("running: streamlink %s", strings.Join(newArgs, " ")) + log.Debug().Msgf("streamlink live args: %v", cmdArgs) + log.Debug().Msgf("running: streamlink %s", strings.Join(cmdArgs, " ")) // Notify chat download that video download is about to start log.Debug().Msg("notifying chat download that video download is about to start") - startChatDownloadChannel <- true + + // !send signal to workflow to start chat download + temporal.InitializeTemporalClient() + signal := utils.ArchiveTwitchLiveChatStartSignal{ + Start: true, + } + err := temporal.GetTemporalClient().Client.SignalWorkflow(ctx, liveChatWorkflowId, "", "start-chat-download", signal) + if err != nil { + return fmt.Errorf("error sending signal to workflow to start chat download: %w", err) + } // Execute streamlink - cmd := osExec.Command("streamlink", newArgs...) + cmd := osExec.Command("streamlink", cmdArgs...) videoLogfile, err := os.Create(fmt.Sprintf("/logs/%s_%s-video.log", v.ExtID, v.ID)) if err != nil { @@ -303,6 +331,7 @@ func DownloadTwitchLiveVideo(v *ent.Vod, ch *ent.Channel, startChatDownloadChann cmd.Stdout = multiWriterStdout if err := cmd.Run(); err != nil { + fmt.Printf("streamlink error: %v", err) // Streamlink will error when the stream is offline - do not log this as an error log.Debug().Msgf("finished downloading live video for %s - %s", v.ExtID, err.Error()) log.Debug().Msgf("streamlink live stdout: %s", stdout.String()) @@ -313,23 +342,15 @@ func DownloadTwitchLiveVideo(v *ent.Vod, ch *ent.Channel, startChatDownloadChann return nil } -func DownloadTwitchLiveChat(v *ent.Vod, ch *ent.Channel, q *ent.Queue, busC chan bool, startChatDownloadChannel chan bool, waitForVideo bool) error { - - if waitForVideo { - log.Debug().Msg("waiting for video to start before downloading chat") - // Wait for video to start - <-startChatDownloadChannel - log.Debug().Msg("video started - starting chat download") - } else { - log.Debug().Msg("sleeping 3 seconds for video download to start.") - time.Sleep(3 * time.Second) - } +func DownloadTwitchLiveChat(ctx context.Context, v *ent.Vod, ch *ent.Channel, q *ent.Queue) error { log.Debug().Msg("setting chat start time") chatStartTime := time.Now() - q.Update().SetChatStart(chatStartTime).SaveX(context.Background()) - - log.Debug().Msgf("spawning chat_downloader for live stream %s", v.ID) + _, err := database.DB().Client.Queue.UpdateOneID(q.ID).SetChatStart(chatStartTime).Save(ctx) + if err != nil { + log.Error().Err(err).Msg("error setting chat start time") + return err + } cmd := osExec.Command("chat_downloader", fmt.Sprintf("https://twitch.tv/%s", ch.Name), "--output", fmt.Sprintf("/tmp/%s_%s-live-chat.json", v.ExtID, v.ID), "-q") @@ -352,17 +373,6 @@ func DownloadTwitchLiveChat(v *ent.Vod, ch *ent.Channel, q *ent.Queue, busC chan return err } - // When video download is complete kill chat download - k := <-busC - if k { - log.Debug().Msg("streamlink detected the stream was down - killing chat_downloader") - err := cmd.Process.Signal(os.Interrupt) - if err != nil { - log.Error().Err(err).Msg("error killing chat_downloader") - return err - } - } - if err := cmd.Wait(); err != nil { log.Error().Err(err).Msg("error waiting for chat_downloader for live chat download") return err diff --git a/internal/live/live.go b/internal/live/live.go index 6499c3b1..ac861ede 100644 --- a/internal/live/live.go +++ b/internal/live/live.go @@ -3,7 +3,6 @@ package live import ( "context" "fmt" - "strconv" "time" "github.com/google/uuid" @@ -247,25 +246,25 @@ func (s *Service) Check() error { return nil } -func (s *Service) ConvertChat(c echo.Context, convertChatDto ConvertChat) error { - i, err := strconv.ParseInt(convertChatDto.ChatStart, 10, 64) - if err != nil { - return fmt.Errorf("error parsing chat start: %v", err) - } - tm := time.Unix(i, 0) - err = utils.ConvertTwitchLiveChatToVodChat( - fmt.Sprintf("/tmp/%s", convertChatDto.FileName), - convertChatDto.ChannelName, - convertChatDto.VodID, - convertChatDto.VodExternalID, - convertChatDto.ChannelID, - tm, - ) - if err != nil { - return fmt.Errorf("error converting chat: %v", err) - } - return nil -} +// func (s *Service) ConvertChat(c echo.Context, convertChatDto ConvertChat) error { +// i, err := strconv.ParseInt(convertChatDto.ChatStart, 10, 64) +// if err != nil { +// return fmt.Errorf("error parsing chat start: %v", err) +// } +// tm := time.Unix(i, 0) +// err = utils.ConvertTwitchLiveChatToVodChat( +// fmt.Sprintf("/tmp/%s", convertChatDto.FileName), +// convertChatDto.ChannelName, +// convertChatDto.VodID, +// convertChatDto.VodExternalID, +// convertChatDto.ChannelID, +// tm, +// ) +// if err != nil { +// return fmt.Errorf("error converting chat: %v", err) +// } +// return nil +// } func (s *Service) ArchiveLiveChannel(c echo.Context, archiveLiveChannelDto ArchiveLive) error { // fetch channel diff --git a/internal/temporal/client.go b/internal/temporal/client.go new file mode 100644 index 00000000..3b512a82 --- /dev/null +++ b/internal/temporal/client.go @@ -0,0 +1,36 @@ +package temporal + +import ( + "os" + + "github.com/rs/zerolog/log" + + "go.temporal.io/sdk/client" +) + +var temporalClient *Temporal + +type Temporal struct { + Client client.Client +} + +func InitializeTemporalClient() { + // TODO: config env parsed + temporalUrl := os.Getenv("TEMPORAL_URL") + clientOptions := client.Options{ + HostPort: temporalUrl, + } + + c, err := client.Dial(clientOptions) + if err != nil { + log.Panic().Msgf("Unable to create client: %v", err) + } + + log.Info().Msgf("Connected to temporal at %s", clientOptions.HostPort) + + temporalClient = &Temporal{Client: c} +} + +func GetTemporalClient() *Temporal { + return temporalClient +} diff --git a/internal/temporal/workflows.go b/internal/temporal/workflows.go new file mode 100644 index 00000000..fe2c4d7e --- /dev/null +++ b/internal/temporal/workflows.go @@ -0,0 +1,152 @@ +package temporal + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/google/uuid" + "github.com/rs/zerolog/log" + "github.com/zibbp/ganymede/ent" + entVod "github.com/zibbp/ganymede/ent/vod" + "github.com/zibbp/ganymede/internal/database" + "github.com/zibbp/ganymede/internal/dto" + "go.temporal.io/api/enums/v1" + "go.temporal.io/api/history/v1" + "go.temporal.io/api/workflow/v1" + "go.temporal.io/api/workflowservice/v1" + "go.temporal.io/sdk/client" +) + +type WorkflowHistory struct { + *history.HistoryEvent +} + +type WorkflowVideoIdResult struct { + VideoId string `json:"video_id"` + ExternalVideoId string `json:"external_video_id"` +} + +func GetActiveWorkflows(ctx context.Context) ([]*workflow.WorkflowExecutionInfo, error) { + w, err := temporalClient.Client.ListOpenWorkflow(ctx, &workflowservice.ListOpenWorkflowExecutionsRequest{}) + if err != nil { + log.Error().Err(err).Msg("failed to list open workflows") + return nil, nil + } + + return w.Executions, nil + +} + +func GetClosedWorkflows(ctx context.Context) ([]*workflow.WorkflowExecutionInfo, error) { + w, err := temporalClient.Client.ListClosedWorkflow(ctx, &workflowservice.ListClosedWorkflowExecutionsRequest{}) + if err != nil { + log.Error().Err(err).Msg("failed to list closed workflows") + return nil, nil + } + + return w.Executions, nil +} + +func GetWorkflowById(ctx context.Context, workflowId string, runId string) (*workflow.WorkflowExecutionInfo, error) { + w, err := temporalClient.Client.DescribeWorkflowExecution(ctx, workflowId, runId) + if err != nil { + log.Error().Err(err).Msg("failed to describe workflow") + return nil, nil + } + + return w.WorkflowExecutionInfo, nil +} + +func GetWorkflowHistory(ctx context.Context, workflowId string, runId string) ([]*history.HistoryEvent, error) { + iterator := temporalClient.Client.GetWorkflowHistory(ctx, workflowId, runId, false, 1) + + var history []*history.HistoryEvent + for iterator.HasNext() { + event, err := iterator.Next() + if err != nil { + log.Error().Err(err).Msg("failed to get workflow history") + return nil, nil + } + + history = append(history, event) + } + + return history, nil +} + +func RestartArchiveWorkflow(ctx context.Context, videoId uuid.UUID, workflowName string) (string, error) { + // fetch items to create a dto.ArchiveVideoInput + var input dto.ArchiveVideoInput + + vod, err := database.DB().Client.Vod.Query().Where(entVod.ID(videoId)).WithChannel().WithQueue().Only(context.Background()) + if err != nil { + log.Error().Err(err).Msg("failed to fetch vod") + return "", nil + } + + // check if a live watch exists + liveWatch, err := vod.Edges.Channel.QueryLive().Only(context.Background()) + if err != nil { + if _, ok := err.(*ent.NotFoundError); ok { + log.Debug().Msg("no live watch found") + } else { + log.Error().Err(err).Msg("failed to fetch live watch") + return "", nil + } + } + + input.Vod = vod + input.Channel = vod.Edges.Channel + input.Queue = vod.Edges.Queue + input.VideoID = vod.ExtID + input.Type = string(vod.Type) + input.Platform = string(vod.Platform) + input.Resolution = vod.Resolution + input.RenderChat = input.Queue.RenderChat + input.DownloadChat = true + input.LiveWatchChannel = liveWatch + + workflowOptions := client.StartWorkflowOptions{ + TaskQueue: "archive", + } + + workflowRun, err := temporalClient.Client.ExecuteWorkflow(ctx, workflowOptions, workflowName, input) + if err != nil { + log.Error().Err(err).Msg("failed to start workflow") + return "", nil + } + + log.Info().Msgf("Started workflow %s", workflowRun.GetID()) + + return workflowRun.GetID(), nil +} + +func GetVideoIdFromWorkflow(ctx context.Context, workflowId string, runId string) (WorkflowVideoIdResult, error) { + var result WorkflowVideoIdResult + history, err := GetWorkflowHistory(ctx, workflowId, runId) + if err != nil { + return WorkflowVideoIdResult{}, err + } + + for _, event := range history { + if event.GetEventType() == enums.EVENT_TYPE_WORKFLOW_EXECUTION_STARTED { + attributes := event.GetWorkflowExecutionStartedEventAttributes() + if attributes != nil { + input := attributes.Input + if input != nil { + data := input.Payloads[0].GetData() + var input dto.ArchiveVideoInput + err := json.Unmarshal(data, &input) + if err != nil { + return WorkflowVideoIdResult{}, fmt.Errorf("failed to unmarshal input: %w", err) + } + result.VideoId = input.Vod.ID.String() + result.ExternalVideoId = input.Vod.ExtID + } + } + } + } + + return result, nil +} diff --git a/internal/transport/http/chapter.go b/internal/transport/http/chapter.go new file mode 100644 index 00000000..45e70b75 --- /dev/null +++ b/internal/transport/http/chapter.go @@ -0,0 +1,53 @@ +package http + +import ( + "net/http" + + "github.com/google/uuid" + "github.com/labstack/echo/v4" + "github.com/zibbp/ganymede/ent" +) + +type ChapterService interface { + GetVideoChapters(videoId uuid.UUID) ([]*ent.Chapter, error) + CreateWebVtt(chapters []*ent.Chapter) (string, error) +} + +func (h *Handler) GetVideoChapters(c echo.Context) error { + videoId := c.Param("videoId") + + // parse uuid + vid, err := uuid.Parse(videoId) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, err.Error()) + } + + chapters, err := h.Service.ChapterService.GetVideoChapters(vid) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + + return c.JSON(http.StatusOK, chapters) +} + +func (h *Handler) GetWebVTTChapters(c echo.Context) error { + videoId := c.Param("videoId") + + // parse uuid + vid, err := uuid.Parse(videoId) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, err.Error()) + } + + chapters, err := h.Service.ChapterService.GetVideoChapters(vid) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + + webVtt, err := h.Service.ChapterService.CreateWebVtt(chapters) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + + return c.String(http.StatusOK, webVtt) +} diff --git a/internal/transport/http/handler.go b/internal/transport/http/handler.go index d3407555..23c2e02d 100644 --- a/internal/transport/http/handler.go +++ b/internal/transport/http/handler.go @@ -36,6 +36,7 @@ type Services struct { MetricsService MetricsService PlaylistService PlaylistService TaskService TaskService + ChapterService ChapterService } type Handler struct { @@ -43,7 +44,7 @@ type Handler struct { Service Services } -func NewHandler(authService AuthService, channelService ChannelService, vodService VodService, queueService QueueService, twitchService TwitchService, archiveService ArchiveService, adminService AdminService, userService UserService, configService ConfigService, liveService LiveService, schedulerService SchedulerService, playbackService PlaybackService, metricsService MetricsService, playlistService PlaylistService, taskService TaskService) *Handler { +func NewHandler(authService AuthService, channelService ChannelService, vodService VodService, queueService QueueService, twitchService TwitchService, archiveService ArchiveService, adminService AdminService, userService UserService, configService ConfigService, liveService LiveService, schedulerService SchedulerService, playbackService PlaybackService, metricsService MetricsService, playlistService PlaylistService, taskService TaskService, chapterService ChapterService) *Handler { log.Debug().Msg("creating new handler") h := &Handler{ @@ -64,6 +65,7 @@ func NewHandler(authService AuthService, channelService ChannelService, vodServi MetricsService: metricsService, PlaylistService: playlistService, TaskService: taskService, + ChapterService: chapterService, }, } @@ -235,7 +237,6 @@ func groupV1Routes(e *echo.Group, h *Handler) { liveGroup.PUT("/:id", h.UpdateLiveWatchedChannel, auth.GuardMiddleware, auth.GetUserMiddleware, auth.UserRoleMiddleware(utils.EditorRole)) liveGroup.DELETE("/:id", h.DeleteLiveWatchedChannel, auth.GuardMiddleware, auth.GetUserMiddleware, auth.UserRoleMiddleware(utils.EditorRole)) liveGroup.GET("/check", h.Check, auth.GuardMiddleware, auth.GetUserMiddleware, auth.UserRoleMiddleware(utils.EditorRole)) - liveGroup.POST("/chat-convert", h.ConvertChat, auth.GuardMiddleware, auth.GetUserMiddleware, auth.UserRoleMiddleware(utils.EditorRole)) liveGroup.GET("/vod", h.CheckVodWatchedChannels, auth.GuardMiddleware, auth.GetUserMiddleware, auth.UserRoleMiddleware(utils.EditorRole)) liveGroup.POST("/archive", h.ArchiveLiveChannel, auth.GuardMiddleware, auth.GetUserMiddleware, auth.UserRoleMiddleware(utils.ArchiverRole)) @@ -270,6 +271,21 @@ func groupV1Routes(e *echo.Group, h *Handler) { // Notification notificationGroup := e.Group("/notification") notificationGroup.POST("/test", h.TestNotification, auth.GuardMiddleware, auth.GetUserMiddleware, auth.UserRoleMiddleware(utils.AdminRole)) + + // Workflows + workflowGroup := e.Group("/workflows") + workflowGroup.GET("/active", h.GetActiveWorkflows, auth.GuardMiddleware, auth.GetUserMiddleware, auth.UserRoleMiddleware(utils.ArchiverRole)) + workflowGroup.GET("/closed", h.GetClosedWorkflows, auth.GuardMiddleware, auth.GetUserMiddleware, auth.UserRoleMiddleware(utils.ArchiverRole)) + workflowGroup.GET("/:workflowId/:runId", h.GetWorkflowById, auth.GuardMiddleware, auth.GetUserMiddleware, auth.UserRoleMiddleware(utils.ArchiverRole)) + workflowGroup.GET("/:workflowId/:runId/history", h.GetWorkflowHistory, auth.GuardMiddleware, auth.GetUserMiddleware, auth.UserRoleMiddleware(utils.ArchiverRole)) + workflowGroup.GET("/:workflowId/:runId/video_id", h.GetVideoIdFromWorkflow, auth.GuardMiddleware, auth.GetUserMiddleware, auth.UserRoleMiddleware(utils.ArchiverRole)) + workflowGroup.POST("/start", h.StartWorkflow, auth.GuardMiddleware, auth.GetUserMiddleware, auth.UserRoleMiddleware(utils.ArchiverRole)) + workflowGroup.POST("/restart", h.RestartArchiveWorkflow, auth.GuardMiddleware, auth.GetUserMiddleware, auth.UserRoleMiddleware(utils.ArchiverRole)) + + // Chapter + chapterGroup := e.Group("/chapter") + chapterGroup.GET("/video/:videoId", h.GetVideoChapters) + chapterGroup.GET("/video/:videoId/webvtt", h.GetWebVTTChapters) } func (h *Handler) Serve() error { diff --git a/internal/transport/http/live.go b/internal/transport/http/live.go index 87e79605..df159949 100644 --- a/internal/transport/http/live.go +++ b/internal/transport/http/live.go @@ -7,7 +7,6 @@ import ( "github.com/labstack/echo/v4" "github.com/zibbp/ganymede/ent" "github.com/zibbp/ganymede/internal/live" - "github.com/zibbp/ganymede/internal/utils" ) type LiveService interface { @@ -16,7 +15,6 @@ type LiveService interface { DeleteLiveWatchedChannel(c echo.Context, lID uuid.UUID) error UpdateLiveWatchedChannel(c echo.Context, liveDto live.Live) (*ent.Live, error) Check() error - ConvertChat(c echo.Context, convertDto live.ConvertChat) error CheckVodWatchedChannels() ArchiveLiveChannel(c echo.Context, archiveDto live.ArchiveLive) error } @@ -281,57 +279,6 @@ func (h *Handler) Check(c echo.Context) error { return c.JSON(http.StatusOK, "ok") } -// ConvertChat godoc -// -// @Summary Convert chat -// @Description Adhoc convert chat endpoint. This is what happens when a live stream chat is converted to a "vod" chat. -// @Tags Live -// @Accept json -// @Produce json -// @Param body body ConvertChatRequest true "Convert chat" -// @Success 200 {object} string -// @Failure 400 {object} utils.ErrorResponse -// @Failure 500 {object} utils.ErrorResponse -// @Router /live/chat-convert [post] -// @Security ApiKeyCookieAuth -func (h *Handler) ConvertChat(c echo.Context) error { - ccr := new(ConvertChatRequest) - if err := c.Bind(ccr); err != nil { - return echo.NewHTTPError(http.StatusBadRequest, err.Error()) - } - if err := c.Validate(ccr); err != nil { - return echo.NewHTTPError(http.StatusBadRequest, err.Error()) - } - // Validate user input that is used for file name - validVodID, err := utils.ValidateFileNameInput(ccr.VodID) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, err.Error()) - } - validVodExternalID, err := utils.ValidateFileNameInput(ccr.VodExternalID) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, err.Error()) - } - validFileName, err := utils.ValidateFileName(ccr.FileName) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, err.Error()) - } - - convertDto := live.ConvertChat{ - FileName: validFileName, - ChannelName: ccr.ChannelName, - VodID: validVodID, - ChannelID: ccr.ChannelID, - VodExternalID: validVodExternalID, - ChatStart: ccr.ChatStart, - } - err = h.Service.LiveService.ConvertChat(c, convertDto) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) - } - - return c.JSON(http.StatusOK, "ok - converted chat found in /tmp/") -} - // CheckVodWatchedChannels godoc // // @Summary Check watched channels diff --git a/internal/transport/http/workflow.go b/internal/transport/http/workflow.go new file mode 100644 index 00000000..1fa0fb67 --- /dev/null +++ b/internal/transport/http/workflow.go @@ -0,0 +1,126 @@ +package http + +import ( + "net/http" + + "github.com/google/uuid" + "github.com/labstack/echo/v4" + "github.com/zibbp/ganymede/internal/temporal" + "github.com/zibbp/ganymede/internal/workflows" +) + +type StartWorkflowRequest struct { + WorkflowName string `json:"workflow_name" validate:"required"` +} +type RestartArchiveWorkflowRequest struct { + WorkflowName string `json:"workflow_name" validate:"required"` + VideoID string `json:"video_id" validate:"required"` +} + +func (h *Handler) GetActiveWorkflows(c echo.Context) error { + executions, err := temporal.GetActiveWorkflows(c.Request().Context()) + if err != nil { + return err + } + + return c.JSON(200, executions) + +} + +func (h *Handler) GetClosedWorkflows(c echo.Context) error { + executions, err := temporal.GetClosedWorkflows(c.Request().Context()) + if err != nil { + return err + } + + return c.JSON(200, executions) +} + +func (h *Handler) GetWorkflowById(c echo.Context) error { + workflowId := c.Param("workflowId") + runId := c.Param("runId") + + execution, err := temporal.GetWorkflowById(c.Request().Context(), workflowId, runId) + if err != nil { + return err + } + + return c.JSON(200, execution) +} + +func (h *Handler) GetWorkflowHistory(c echo.Context) error { + workflowId := c.Param("workflowId") + runId := c.Param("runId") + + history, err := temporal.GetWorkflowHistory(c.Request().Context(), workflowId, runId) + if err != nil { + return err + } + + return c.JSON(200, history) +} + +func (h *Handler) StartWorkflow(c echo.Context) error { + var request StartWorkflowRequest + err := c.Bind(&request) + if err != nil { + return err + } + + // validate request + if err := c.Validate(request); err != nil { + return echo.NewHTTPError(http.StatusBadRequest, err.Error()) + } + + startWorkflowResponse, err := workflows.StartWorkflow(c.Request().Context(), request.WorkflowName) + if err != nil { + return err + } + + return c.JSON(200, startWorkflowResponse) +} + +func (h *Handler) RestartArchiveWorkflow(c echo.Context) error { + var request RestartArchiveWorkflowRequest + err := c.Bind(&request) + if err != nil { + return err + } + + // validate request + if err := c.Validate(request); err != nil { + return echo.NewHTTPError(http.StatusBadRequest, err.Error()) + } + + // create uuid + videoId, err := uuid.Parse(request.VideoID) + if err != nil { + return err + } + + // some workflows should not be restarted such as live video and chat downloads + if request.WorkflowName == "ArchiveTwitchLiveVideoWorkflow" || request.WorkflowName == "ArchiveTwitchLiveChatWorkflow" || request.WorkflowName == " DownloadTwitchLiveChatWorkflow" || request.WorkflowName == "DownloadTwitchLiveVideoWorkflow" { + return echo.NewHTTPError(http.StatusBadRequest, "cannot restart live video or chat workflows") + } + + workflowId, err := temporal.RestartArchiveWorkflow(c.Request().Context(), videoId, request.WorkflowName) + if err != nil { + return err + } + + return c.JSON(200, map[string]string{ + "workflow_id": workflowId, + }) +} + +func (h *Handler) GetVideoIdFromWorkflow(c echo.Context) error { + workflowId := c.Param("workflowId") + runId := c.Param("runId") + + id, err := temporal.GetVideoIdFromWorkflow(c.Request().Context(), workflowId, runId) + if err != nil { + return err + } + + return c.JSON(200, id) +} diff --git a/internal/twitch/twitch.go b/internal/twitch/twitch.go index ba078dac..beb5702f 100644 --- a/internal/twitch/twitch.go +++ b/internal/twitch/twitch.go @@ -11,6 +11,7 @@ import ( "github.com/rs/zerolog/log" "github.com/zibbp/ganymede/ent" + "github.com/zibbp/ganymede/internal/chapter" "github.com/zibbp/ganymede/internal/database" ) @@ -85,23 +86,24 @@ type VodResponse struct { } type Vod struct { - ID string `json:"id"` - StreamID string `json:"stream_id"` - UserID string `json:"user_id"` - UserLogin string `json:"user_login"` - UserName string `json:"user_name"` - Title string `json:"title"` - Description string `json:"description"` - CreatedAt string `json:"created_at"` - PublishedAt string `json:"published_at"` - URL string `json:"url"` - ThumbnailURL string `json:"thumbnail_url"` - Viewable string `json:"viewable"` - ViewCount int64 `json:"view_count"` - Language string `json:"language"` - Type string `json:"type"` - Duration string `json:"duration"` - MutedSegments interface{} `json:"muted_segments"` + ID string `json:"id"` + StreamID string `json:"stream_id"` + UserID string `json:"user_id"` + UserLogin string `json:"user_login"` + UserName string `json:"user_name"` + Title string `json:"title"` + Description string `json:"description"` + CreatedAt string `json:"created_at"` + PublishedAt string `json:"published_at"` + URL string `json:"url"` + ThumbnailURL string `json:"thumbnail_url"` + Viewable string `json:"viewable"` + ViewCount int64 `json:"view_count"` + Language string `json:"language"` + Type string `json:"type"` + Duration string `json:"duration"` + MutedSegments interface{} `json:"muted_segments"` + Chapters []chapter.Chapter `json:"chapters"` } type Stream struct { @@ -258,15 +260,15 @@ func (s *Service) GetVodByID(vID string) (Vod, error) { defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - return Vod{}, fmt.Errorf("vod not found") - } - body, err := io.ReadAll(resp.Body) if err != nil { return Vod{}, fmt.Errorf("failed to read response body: %v", err) } + if resp.StatusCode != http.StatusOK { + return Vod{}, fmt.Errorf("%s", body) + } + var vodResponse VodResponse err = json.Unmarshal(body, &vodResponse) if err != nil { diff --git a/internal/utils/activity.go b/internal/utils/activity.go new file mode 100644 index 00000000..7282e5c8 --- /dev/null +++ b/internal/utils/activity.go @@ -0,0 +1,8 @@ +package utils + +type ArchiveTwitchLiveChatStartSignal struct { + Start bool +} +type ArchiveTwitchLiveChatContinueSignal struct { + Continue bool +} diff --git a/internal/utils/chat.go b/internal/utils/chat.go index d41d9843..18f45e93 100644 --- a/internal/utils/chat.go +++ b/internal/utils/chat.go @@ -15,6 +15,7 @@ import ( type ParsedChat struct { Streamer Streamer `json:"streamer"` + Video Video `json:"video"` Comments []Comment `json:"comments"` } @@ -23,6 +24,12 @@ type Streamer struct { ID int `json:"id"` } +type Video struct { + ID string `json:"id"` + Start int64 `json:"start"` + End int64 `json:"end"` +} + type Comment struct { ID string `json:"_id"` Source string `json:"source"` @@ -140,7 +147,7 @@ func OpenChatFile(path string) ([]LiveComment, error) { return liveComments, nil } -func ConvertTwitchLiveChatToVodChat(path string, channelName string, vID string, vExtID string, cID int, chatStart time.Time) error { +func ConvertTwitchLiveChatToVodChat(path string, channelName string, vID string, vExtID string, cID int, chatStart time.Time, previousVideoID string) error { log.Debug().Msg("Converting Twitch Live Chat to Vod Chat") @@ -153,6 +160,8 @@ func ConvertTwitchLiveChatToVodChat(path string, channelName string, vID string, var parsedChat ParsedChat parsedChat.Streamer.Name = channelName parsedChat.Streamer.ID = cID + parsedChat.Video.ID = previousVideoID + parsedChat.Video.Start = 0 var parsedComments []Comment @@ -338,6 +347,10 @@ func ConvertTwitchLiveChatToVodChat(path string, channelName string, vID string, parsedChat.Comments = parsedComments + // get last comment offset and set as video end + lastComment := parsedChat.Comments[len(parsedChat.Comments)-1] + parsedChat.Video.End = int64(lastComment.ContentOffsetSeconds) + err = writeParsedChat(parsedChat, vID, vExtID) if err != nil { return err diff --git a/internal/utils/utils.go b/internal/utils/utils.go index af3ad06b..fc956955 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -4,6 +4,7 @@ import ( "fmt" "runtime" "strings" + "time" ) func PrintMemUsage() { @@ -46,3 +47,13 @@ func Contains(s []string, e string) bool { } return false } + +func SecondsToHHMMSS(seconds int) string { + duration := time.Duration(seconds) * time.Second + + hours := int(duration.Hours()) + minutes := int(duration.Minutes()) % 60 + seconds = int(duration.Seconds()) % 60 + + return fmt.Sprintf("%02d:%02d:%02d", hours, minutes, seconds) +} diff --git a/internal/vod/vod.go b/internal/vod/vod.go index e2a0bfab..43678d14 100644 --- a/internal/vod/vod.go +++ b/internal/vod/vod.go @@ -16,6 +16,7 @@ import ( "github.com/rs/zerolog/log" "github.com/zibbp/ganymede/ent" "github.com/zibbp/ganymede/ent/channel" + entChapter "github.com/zibbp/ganymede/ent/chapter" "github.com/zibbp/ganymede/ent/vod" "github.com/zibbp/ganymede/internal/cache" "github.com/zibbp/ganymede/internal/chat" @@ -128,7 +129,7 @@ func (s *Service) GetVodWithChannel(vodID uuid.UUID) (*ent.Vod, error) { func (s *Service) DeleteVod(c echo.Context, vodID uuid.UUID, deleteFiles bool) error { log.Debug().Msgf("deleting vod %s", vodID) // delete vod and queue item - v, err := s.Store.Client.Vod.Query().Where(vod.ID(vodID)).WithQueue().WithChannel().Only(c.Request().Context()) + v, err := s.Store.Client.Vod.Query().Where(vod.ID(vodID)).WithQueue().WithChannel().WithChapters().Only(c.Request().Context()) if err != nil { if _, ok := err.(*ent.NotFoundError); ok { return fmt.Errorf("vod not found") @@ -141,6 +142,12 @@ func (s *Service) DeleteVod(c echo.Context, vodID uuid.UUID, deleteFiles bool) e return fmt.Errorf("error deleting queue item: %v", err) } } + if v.Edges.Chapters != nil { + _, err = s.Store.Client.Chapter.Delete().Where(entChapter.HasVodWith(vod.ID(vodID))).Exec(c.Request().Context()) + if err != nil { + return fmt.Errorf("error deleting chapters: %v", err) + } + } // delete files if deleteFiles { diff --git a/internal/workflows/video.go b/internal/workflows/video.go new file mode 100644 index 00000000..2f7c7098 --- /dev/null +++ b/internal/workflows/video.go @@ -0,0 +1,669 @@ +package workflows + +import ( + "context" + "errors" + "time" + + "github.com/rs/zerolog/log" + "github.com/zibbp/ganymede/ent" + "github.com/zibbp/ganymede/ent/live" + "github.com/zibbp/ganymede/ent/queue" + "github.com/zibbp/ganymede/internal/activities" + "github.com/zibbp/ganymede/internal/database" + "github.com/zibbp/ganymede/internal/dto" + "github.com/zibbp/ganymede/internal/notification" + "github.com/zibbp/ganymede/internal/utils" + "go.temporal.io/sdk/temporal" + "go.temporal.io/sdk/workflow" +) + +func checkIfTasksAreDone(input dto.ArchiveVideoInput) error { + log.Debug().Msgf("checking if tasks are done for video %s", input.VideoID) + q, err := database.DB().Client.Queue.Query().Where(queue.ID(input.Queue.ID)).Only(context.Background()) + if err != nil { + log.Error().Err(err).Msg("error getting queue item") + return err + } + + if input.Queue.LiveArchive { + if q.TaskVideoDownload == utils.Success && q.TaskVideoConvert == utils.Success && q.TaskVideoMove == utils.Success && q.TaskChatDownload == utils.Success && q.TaskChatConvert == utils.Success && q.TaskChatRender == utils.Success && q.TaskChatMove == utils.Success { + log.Debug().Msgf("all tasks for video %s are done", input.VideoID) + + _, err := q.Update().SetVideoProcessing(false).SetChatProcessing(false).SetProcessing(false).Save(context.Background()) + if err != nil { + log.Error().Err(err).Msg("error updating queue item") + return err + } + + _, err = database.DB().Client.Vod.UpdateOneID(input.Vod.ID).SetProcessing(false).Save(context.Background()) + if err != nil { + log.Error().Err(err).Msg("error updating vod") + return err + } + + notification.SendLiveArchiveSuccessNotification(input.Channel, input.Vod, input.Queue) + } + } else { + if q.TaskVideoDownload == utils.Success && q.TaskVideoConvert == utils.Success && q.TaskVideoMove == utils.Success && q.TaskChatDownload == utils.Success && q.TaskChatRender == utils.Success && q.TaskChatMove == utils.Success { + log.Debug().Msgf("all tasks for video %s are done", input.VideoID) + + _, err := q.Update().SetVideoProcessing(false).SetChatProcessing(false).SetProcessing(false).Save(context.Background()) + if err != nil { + log.Error().Err(err).Msg("error updating queue item") + return err + } + + _, err = database.DB().Client.Vod.UpdateOneID(input.Vod.ID).SetProcessing(false).Save(context.Background()) + if err != nil { + log.Error().Err(err).Msg("error updating vod") + return err + } + + notification.SendVideoArchiveSuccessNotification(input.Channel, input.Vod, input.Queue) + } + } + + return nil +} + +func workflowErrorHandler(err error, input dto.ArchiveVideoInput, task string) error { + notification.SendErrorNotification(input.Channel, input.Vod, input.Queue, task) + + return err +} + +// *Top Level Workflow* +func ArchiveVideoWorkflow(ctx workflow.Context, input dto.ArchiveVideoInput) error { + ctx = workflow.WithActivityOptions(ctx, workflow.ActivityOptions{}) + + // create directory + err := workflow.ExecuteChildWorkflow(ctx, CreateDirectoryWorkflow, input).Get(ctx, nil) + if err != nil { + return err + } + + // download thumbnails + err = workflow.ExecuteChildWorkflow(ctx, DownloadTwitchThumbnailsWorkflow, input).Get(ctx, nil) + if err != nil { + return err + } + + // save video info + err = workflow.ExecuteChildWorkflow(ctx, SaveTwitchVideoInfoWorkflow, input).Get(ctx, nil) + if err != nil { + return err + } + + // archive video + videoFuture := workflow.ExecuteChildWorkflow(ctx, ArchiveTwitchVideoWorkflow, input) + + if input.Queue.ChatProcessing { + chatFuture := workflow.ExecuteChildWorkflow(ctx, ArchiveTwitchChatWorkflow, input) + if err := chatFuture.Get(ctx, nil); err != nil { + return err + } + } + + if err := videoFuture.Get(ctx, nil); err != nil { + return err + } + + return nil +} + +// *Top Level Workflow* +func ArchiveLiveVideoWorkflow(ctx workflow.Context, input dto.ArchiveVideoInput) error { + ctx = workflow.WithActivityOptions(ctx, workflow.ActivityOptions{}) + + // create directory + err := workflow.ExecuteChildWorkflow(ctx, CreateDirectoryWorkflow, input).Get(ctx, nil) + if err != nil { + return err + } + + // download thumbnails + err = workflow.ExecuteChildWorkflow(ctx, DownloadTwitchLiveThumbnailsWorkflow, input).Get(ctx, nil) + if err != nil { + return err + } + + // save video info + err = workflow.ExecuteChildWorkflow(ctx, SaveTwitchLiveVideoInfoWorkflow, input).Get(ctx, nil) + if err != nil { + return err + } + + var chatFuture workflow.ChildWorkflowFuture + if input.Queue.ChatProcessing { + chatFuture = workflow.ExecuteChildWorkflow(ctx, ArchiveTwitchLiveChatWorkflow, input) + var chatWorkflowExecution workflow.Execution + _ = chatFuture.GetChildWorkflowExecution().Get(ctx, &chatWorkflowExecution) + + log.Debug().Msgf("Live chat archive workflow ID: %s", chatWorkflowExecution.ID) + input.LiveChatArchiveWorkflowId = chatWorkflowExecution.ID + + // execute chat download first to get a workflow ID for signals + // the actual download of chat is held until the video is about to start + liveChatFuture := workflow.ExecuteChildWorkflow(ctx, DownloadTwitchLiveChatWorkflow, input) + var liveChatWorkflowExecution workflow.Execution + _ = liveChatFuture.GetChildWorkflowExecution().Get(ctx, &liveChatWorkflowExecution) + + log.Debug().Msgf("Live chat workflow ID: %s", liveChatWorkflowExecution.ID) + input.LiveChatWorkflowId = liveChatWorkflowExecution.ID + } + + // archive video + videoFuture := workflow.ExecuteChildWorkflow(ctx, ArchiveTwitchLiveVideoWorkflow, input) + + if err := videoFuture.Get(ctx, nil); err != nil { + return err + } + + if input.Queue.ChatProcessing { + if err := chatFuture.Get(ctx, nil); err != nil { + return err + } + } + + return nil +} + +// *Low Level Workflow* +func CreateDirectoryWorkflow(ctx workflow.Context, input dto.ArchiveVideoInput) error { + ctx = workflow.WithActivityOptions(ctx, workflow.ActivityOptions{ + StartToCloseTimeout: 10 * time.Second, + }) + + err := workflow.ExecuteActivity(ctx, activities.CreateDirectory, input).Get(ctx, nil) + if err != nil { + return workflowErrorHandler(err, input, "create-directory") + } + + return nil +} + +// *Low Level Workflow* +func DownloadTwitchThumbnailsWorkflow(ctx workflow.Context, input dto.ArchiveVideoInput) error { + ctx = workflow.WithActivityOptions(ctx, workflow.ActivityOptions{ + StartToCloseTimeout: 10 * time.Second, + }) + + err := workflow.ExecuteActivity(ctx, activities.DownloadTwitchThumbnails, input).Get(ctx, nil) + if err != nil { + return workflowErrorHandler(err, input, "download-thumbnails") + } + + err = checkIfTasksAreDone(input) + if err != nil { + return err + } + + return nil +} + +// *Low Level Workflow* +func DownloadTwitchLiveThumbnailsWorkflow(ctx workflow.Context, input dto.ArchiveVideoInput) error { + ctx = workflow.WithActivityOptions(ctx, workflow.ActivityOptions{ + StartToCloseTimeout: 10 * time.Second, + }) + + err := workflow.ExecuteActivity(ctx, activities.DownloadTwitchLiveThumbnails, input).Get(ctx, nil) + if err != nil { + return workflowErrorHandler(err, input, "download-thumbnails") + } + + err = checkIfTasksAreDone(input) + if err != nil { + return err + } + + return nil +} + +// *Low Level Workflow* +func SaveTwitchVideoInfoWorkflow(ctx workflow.Context, input dto.ArchiveVideoInput) error { + ctx = workflow.WithActivityOptions(ctx, workflow.ActivityOptions{ + StartToCloseTimeout: 10 * time.Second, + }) + + err := workflow.ExecuteActivity(ctx, activities.SaveTwitchVideoInfo, input).Get(ctx, nil) + if err != nil { + return workflowErrorHandler(err, input, "save-video-info") + } + + err = checkIfTasksAreDone(input) + if err != nil { + return err + } + + return nil +} + +// *Low Level Workflow* +func SaveTwitchLiveVideoInfoWorkflow(ctx workflow.Context, input dto.ArchiveVideoInput) error { + ctx = workflow.WithActivityOptions(ctx, workflow.ActivityOptions{ + StartToCloseTimeout: 10 * time.Second, + }) + + err := workflow.ExecuteActivity(ctx, activities.SaveTwitchLiveVideoInfo, input).Get(ctx, nil) + if err != nil { + return workflowErrorHandler(err, input, "save-video-info") + } + + err = checkIfTasksAreDone(input) + if err != nil { + return err + } + + return nil +} + +// *Mid Level Workflow* +func ArchiveTwitchVideoWorkflow(ctx workflow.Context, input dto.ArchiveVideoInput) error { + + err := workflow.ExecuteChildWorkflow(ctx, DownloadTwitchVideoWorkflow, input).Get(ctx, nil) + if err != nil { + return err + } + + err = workflow.ExecuteChildWorkflow(ctx, PostprocessVideoWorkflow, input).Get(ctx, nil) + if err != nil { + return err + } + + err = workflow.ExecuteChildWorkflow(ctx, MoveVideoWorkflow, input).Get(ctx, nil) + if err != nil { + return err + } + + return nil +} + +// *Mid Level Workflow* +func ArchiveTwitchLiveVideoWorkflow(ctx workflow.Context, input dto.ArchiveVideoInput) error { + + err := workflow.ExecuteChildWorkflow(ctx, DownloadTwitchLiveVideoWorkflow, input).Get(ctx, nil) + if err != nil { + return err + } + + err = workflow.ExecuteChildWorkflow(ctx, PostprocessVideoWorkflow, input).Get(ctx, nil) + if err != nil { + return err + } + + err = workflow.ExecuteChildWorkflow(ctx, MoveVideoWorkflow, input).Get(ctx, nil) + if err != nil { + return err + } + + return nil + +} + +// *Mid Level Workflow* +func ArchiveTwitchLiveChatWorkflow(ctx workflow.Context, input dto.ArchiveVideoInput) error { + // download happened earlier, this is post-download tasks + + var signal utils.ArchiveTwitchLiveChatStartSignal + signalChan := workflow.GetSignalChannel(ctx, "continue-chat-arhive") + signalChan.Receive(ctx, &signal) + + log.Info().Msgf("Received signal: %v", signal) + + err := workflow.ExecuteChildWorkflow(ctx, ConvertTwitchLiveChatWorkflow, input).Get(ctx, nil) + if err != nil { + return err + } + + if input.Queue.RenderChat { + err = workflow.ExecuteChildWorkflow(ctx, RenderTwitchChatWorkflow, input).Get(ctx, nil) + if err != nil { + return err + } + } + + err = workflow.ExecuteChildWorkflow(ctx, MoveTwitchChatWorkflow, input).Get(ctx, nil) + if err != nil { + return err + } + + return nil +} + +// *Low Level Workflow* +func ConvertTwitchLiveChatWorkflow(ctx workflow.Context, input dto.ArchiveVideoInput) error { + ctx = workflow.WithActivityOptions(ctx, workflow.ActivityOptions{ + HeartbeatTimeout: 90 * time.Second, + StartToCloseTimeout: 168 * time.Hour, + RetryPolicy: &temporal.RetryPolicy{ + InitialInterval: 1 * time.Minute, + BackoffCoefficient: 2, + MaximumAttempts: 3, + MaximumInterval: 15 * time.Minute, + }, + }) + + err := workflow.ExecuteActivity(ctx, activities.ConvertTwitchLiveChat, input).Get(ctx, nil) + if err != nil { + return workflowErrorHandler(err, input, "convert-chat") + } + + err = checkIfTasksAreDone(input) + if err != nil { + return err + } + + return nil + +} + +// *Mid Level Workflow* +func ArchiveTwitchChatWorkflow(ctx workflow.Context, input dto.ArchiveVideoInput) error { + + err := workflow.ExecuteChildWorkflow(ctx, DownloadTwitchChatWorkflow, input).Get(ctx, nil) + if err != nil { + return err + } + + if input.Queue.RenderChat { + err = workflow.ExecuteChildWorkflow(ctx, RenderTwitchChatWorkflow, input).Get(ctx, nil) + if err != nil { + return err + } + } + + err = workflow.ExecuteChildWorkflow(ctx, MoveTwitchChatWorkflow, input).Get(ctx, nil) + if err != nil { + return err + } + + return nil +} + +// *Low Level Workflow* +func DownloadTwitchVideoWorkflow(ctx workflow.Context, input dto.ArchiveVideoInput) error { + cctx := workflow.WithActivityOptions(ctx, workflow.ActivityOptions{ + TaskQueue: "video-download", + HeartbeatTimeout: 90 * time.Second, + StartToCloseTimeout: 168 * time.Hour, + RetryPolicy: &temporal.RetryPolicy{ + InitialInterval: 1 * time.Minute, + BackoffCoefficient: 2, + MaximumAttempts: 3, + MaximumInterval: 15 * time.Minute, + }, + }) + + err := workflow.ExecuteActivity(cctx, activities.DownloadTwitchVideo, input).Get(ctx, nil) + if err != nil { + return workflowErrorHandler(err, input, "download-video") + } + + err = checkIfTasksAreDone(input) + if err != nil { + return err + } + + return nil +} + +// *Low Level Workflow* +func DownloadTwitchLiveVideoWorkflow(ctx workflow.Context, input dto.ArchiveVideoInput) error { + ctx = workflow.WithActivityOptions(ctx, workflow.ActivityOptions{ + HeartbeatTimeout: 90 * time.Second, + StartToCloseTimeout: 168 * time.Hour, + RetryPolicy: &temporal.RetryPolicy{ + InitialInterval: 1 * time.Minute, + BackoffCoefficient: 2, + MaximumAttempts: 1, + MaximumInterval: 15 * time.Minute, + }, + }) + + err := workflow.ExecuteActivity(ctx, activities.DownloadTwitchLiveVideo, input).Get(ctx, nil) + if err != nil { + return workflowErrorHandler(err, input, "download-video") + } + + future := workflow.RequestCancelExternalWorkflow(ctx, input.LiveChatWorkflowId, "") + if err := future.Get(ctx, nil); err != nil { + return err + } + + // mark live channel as not live + live, err := database.DB().Client.Live.Query().Where(live.ID(input.LiveWatchChannel.ID)).Only(context.Background()) + if err != nil { + // allow not found error to pass + if _, ok := err.(*ent.NotFoundError); !ok { + log.Error().Err(err).Msg("error getting live channel") + return err + } + } + if live != nil { + _, err = live.Update().SetIsLive(false).Save(context.Background()) + if err != nil { + log.Error().Err(err).Msg("error updating live channel") + return err + } + } + + err = checkIfTasksAreDone(input) + if err != nil { + return err + } + + return nil +} + +// *Low Level Workflow* +func PostprocessVideoWorkflow(ctx workflow.Context, input dto.ArchiveVideoInput) error { + ctx = workflow.WithActivityOptions(ctx, workflow.ActivityOptions{ + TaskQueue: "video-convert", + HeartbeatTimeout: 90 * time.Second, + StartToCloseTimeout: 168 * time.Hour, + RetryPolicy: &temporal.RetryPolicy{ + InitialInterval: 1 * time.Minute, + BackoffCoefficient: 2, + MaximumAttempts: 3, + MaximumInterval: 15 * time.Minute, + }, + }) + + err := workflow.ExecuteActivity(ctx, activities.PostprocessVideo, input).Get(ctx, nil) + if err != nil { + return workflowErrorHandler(err, input, "postprocess-video") + } + + err = checkIfTasksAreDone(input) + if err != nil { + return err + } + + return nil +} + +// *Low Level Workflow* +func MoveVideoWorkflow(ctx workflow.Context, input dto.ArchiveVideoInput) error { + ctx = workflow.WithActivityOptions(ctx, workflow.ActivityOptions{ + HeartbeatTimeout: 90 * time.Second, + StartToCloseTimeout: 168 * time.Hour, + RetryPolicy: &temporal.RetryPolicy{ + InitialInterval: 1 * time.Minute, + BackoffCoefficient: 2, + MaximumAttempts: 3, + MaximumInterval: 15 * time.Minute, + }, + }) + + err := workflow.ExecuteActivity(ctx, activities.MoveVideo, input).Get(ctx, nil) + if err != nil { + return workflowErrorHandler(err, input, "move-video") + } + + err = checkIfTasksAreDone(input) + if err != nil { + return err + } + + return nil +} + +// *Low Level Workflow* +func DownloadTwitchChatWorkflow(ctx workflow.Context, input dto.ArchiveVideoInput) error { + ctx = workflow.WithActivityOptions(ctx, workflow.ActivityOptions{ + TaskQueue: "chat-download", + HeartbeatTimeout: 90 * time.Second, + StartToCloseTimeout: 168 * time.Hour, + RetryPolicy: &temporal.RetryPolicy{ + InitialInterval: 1 * time.Minute, + BackoffCoefficient: 2, + MaximumAttempts: 3, + MaximumInterval: 15 * time.Minute, + }, + }) + + err := workflow.ExecuteActivity(ctx, activities.DownloadTwitchChat, input).Get(ctx, nil) + if err != nil { + return workflowErrorHandler(err, input, "download-chat") + } + + err = checkIfTasksAreDone(input) + if err != nil { + return err + } + + return nil +} + +// *Low Level Workflow* +func DownloadTwitchLiveChatWorkflow(ctx workflow.Context, input dto.ArchiveVideoInput) error { + ctx = workflow.WithActivityOptions(ctx, workflow.ActivityOptions{ + HeartbeatTimeout: 90 * time.Second, + StartToCloseTimeout: 168 * time.Hour, + RetryPolicy: &temporal.RetryPolicy{ + InitialInterval: 1 * time.Minute, + BackoffCoefficient: 2, + MaximumAttempts: 1, + MaximumInterval: 15 * time.Minute, + }, + WaitForCancellation: false, + }) + + defer func() { + + if !errors.Is(ctx.Err(), workflow.ErrCanceled) { + return + } + + // When the Workflow is canceled, it has to get a new disconnected context to execute any Activities + log.Debug().Msgf("Killing chat download: %s", input.LiveChatWorkflowId) + newCtx, _ := workflow.NewDisconnectedContext(ctx) + err := workflow.ExecuteActivity(newCtx, activities.KillTwitchLiveChatDownload, input).Get(ctx, nil) + if err != nil { + log.Error().Err(err).Msgf("error killing chat download: %v", err) + } + + log.Debug().Msgf("Sending signal to continue chat archive: %s", input.LiveChatArchiveWorkflowId) + signal := utils.ArchiveTwitchLiveChatStartSignal{ + Start: true, + } + err = workflow.SignalExternalWorkflow(ctx, input.LiveChatArchiveWorkflowId, "", "continue-chat-arhive", signal).Get(ctx, nil) + if err != nil { + log.Error().Err(err).Msgf("error sending signal to continue chat archive: %v", err) + } + }() + + var signal utils.ArchiveTwitchLiveChatStartSignal + signalChan := workflow.GetSignalChannel(ctx, "start-chat-download") + signalChan.Receive(ctx, &signal) + + log.Info().Msgf("Received signal: %v", signal) + + err := workflow.ExecuteActivity(ctx, activities.DownloadTwitchLiveChat, input).Get(ctx, nil) + if err != nil { + return err + } + + err = checkIfTasksAreDone(input) + if err != nil { + return err + } + + return nil +} + +// *Low Level Workflow* +func RenderTwitchChatWorkflow(ctx workflow.Context, input dto.ArchiveVideoInput) error { + ctx = workflow.WithActivityOptions(ctx, workflow.ActivityOptions{ + TaskQueue: "chat-render", + HeartbeatTimeout: 90 * time.Second, + StartToCloseTimeout: 168 * time.Hour, + RetryPolicy: &temporal.RetryPolicy{ + InitialInterval: 1 * time.Minute, + BackoffCoefficient: 2, + MaximumAttempts: 3, + MaximumInterval: 15 * time.Minute, + }, + }) + + err := workflow.ExecuteActivity(ctx, activities.RenderTwitchChat, input).Get(ctx, nil) + if err != nil { + return workflowErrorHandler(err, input, "render-chat") + } + + err = checkIfTasksAreDone(input) + if err != nil { + return err + } + + return nil +} + +// *Low Level Workflow* +func MoveTwitchChatWorkflow(ctx workflow.Context, input dto.ArchiveVideoInput) error { + ctx = workflow.WithActivityOptions(ctx, workflow.ActivityOptions{ + HeartbeatTimeout: 90 * time.Second, + StartToCloseTimeout: 168 * time.Hour, + RetryPolicy: &temporal.RetryPolicy{ + InitialInterval: 1 * time.Minute, + BackoffCoefficient: 2, + MaximumAttempts: 3, + MaximumInterval: 15 * time.Minute, + }, + }) + + err := workflow.ExecuteActivity(ctx, activities.MoveChat, input).Get(ctx, nil) + if err != nil { + return workflowErrorHandler(err, input, "move-chat") + } + + err = checkIfTasksAreDone(input) + if err != nil { + return err + } + + return nil +} + +// *Low Level Workflow* +func SaveTwitchVideoChapters(ctx workflow.Context) error { + ctx = workflow.WithActivityOptions(ctx, workflow.ActivityOptions{ + HeartbeatTimeout: 90 * time.Second, + StartToCloseTimeout: 168 * time.Hour, + RetryPolicy: &temporal.RetryPolicy{ + InitialInterval: 1 * time.Minute, + BackoffCoefficient: 2, + MaximumAttempts: 3, + MaximumInterval: 15 * time.Minute, + }, + }) + + err := workflow.ExecuteActivity(ctx, activities.TwitchSaveVideoChapters).Get(ctx, nil) + if err != nil { + return err + } + + return nil +} diff --git a/internal/workflows/workflows.go b/internal/workflows/workflows.go new file mode 100644 index 00000000..618cc116 --- /dev/null +++ b/internal/workflows/workflows.go @@ -0,0 +1,35 @@ +package workflows + +import ( + "context" + + "github.com/rs/zerolog/log" + "github.com/zibbp/ganymede/internal/temporal" + "go.temporal.io/sdk/client" +) + +type StartWorkflowResponse struct { + WorkflowId string `json:"workflow_id"` + RunId string `json:"run_id"` +} + +func StartWorkflow(ctx context.Context, workflowName string) (StartWorkflowResponse, error) { + // TODO: develop a better way to do this + + var startWorkflowResponse StartWorkflowResponse + + workflowOptions := client.StartWorkflowOptions{ + TaskQueue: "archive", + } + + we, err := temporal.GetTemporalClient().Client.ExecuteWorkflow(ctx, workflowOptions, workflowName) + if err != nil { + log.Error().Err(err).Msg("failed to start workflow") + return startWorkflowResponse, err + } + + startWorkflowResponse.WorkflowId = we.GetID() + startWorkflowResponse.RunId = we.GetRunID() + + return startWorkflowResponse, nil +}