diff --git a/api/server.go b/api/server.go index 15bab7f9..e94115d6 100644 --- a/api/server.go +++ b/api/server.go @@ -545,6 +545,7 @@ func NewApiServer(config config.Config) *ApiServer { g.Get("/events/:eventId/follow-state", app.v1EventFollowState) g.Post("/events/:eventId/follow", app.requireAuthMiddleware, app.requireWriteScope, app.postV1EventFollow) g.Delete("/events/:eventId/follow", app.requireAuthMiddleware, app.requireWriteScope, app.deleteV1EventFollow) + g.Post("/events/:eventId/submit", app.requireAuthMiddleware, app.requireWriteScope, app.postV1EventSubmitToContest) g.Get("/tracks/:trackId/comments", app.v1TrackComments) g.Get("/tracks/:trackId/comment_count", app.v1TrackCommentCount) diff --git a/api/v1_events_submit.go b/api/v1_events_submit.go new file mode 100644 index 00000000..6d8e91d8 --- /dev/null +++ b/api/v1_events_submit.go @@ -0,0 +1,79 @@ +package api + +import ( + "encoding/json" + "strconv" + "time" + + "api.audius.co/indexer" + "api.audius.co/trashid" + corev1 "github.com/OpenAudio/go-openaudio/pkg/api/core/v1" + "github.com/ethereum/go-ethereum/common" + "github.com/gofiber/fiber/v2" + "go.uber.org/zap" +) + +type SubmitToContestBody struct { + TrackID string `json:"track_id"` +} + +// postV1EventSubmitToContest enters a track into an open-contest event. +// Open contests don't have a parent track (unlike remix_contest), so we +// can't infer the submission from the remixes table — discovery indexes +// the SubmitToContest action into contest_submissions instead. +func (app *ApiServer) postV1EventSubmitToContest(c *fiber.Ctx) error { + userID := app.getMyId(c) + eventID, err := trashid.DecodeHashId(c.Params("eventId")) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, "invalid event id") + } + + body := SubmitToContestBody{} + if err := c.BodyParser(&body); err != nil { + return fiber.NewError(fiber.StatusBadRequest, "invalid body") + } + if body.TrackID == "" { + return fiber.NewError(fiber.StatusBadRequest, "track_id is required") + } + trackID, err := trashid.DecodeHashId(body.TrackID) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, "invalid track id") + } + + signer, err := app.getApiSigner(c) + if err != nil { + return err + } + + metadata, err := json.Marshal(map[string]any{ + "track_id": trackID, + }) + if err != nil { + return err + } + + nonce := time.Now().UnixNano() + manageEntityTx := &corev1.ManageEntityLegacy{ + Signer: common.HexToAddress(signer.Address).String(), + UserId: int64(userID), + EntityId: int64(eventID), + Action: indexer.Action_SubmitToContest, + EntityType: indexer.Entity_Event, + Nonce: strconv.FormatInt(nonce, 10), + Metadata: string(metadata), + } + + response, err := app.sendTransactionWithSigner(manageEntityTx, signer.PrivateKey) + if err != nil { + app.logger.Error("Failed to send SubmitToContest transaction", zap.Error(err)) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": "Failed to submit to contest", + }) + } + + return c.JSON(fiber.Map{ + "transaction_hash": response.Msg.GetTransaction().GetHash(), + "block_hash": response.Msg.GetTransaction().GetBlockHash(), + "block_number": response.Msg.GetTransaction().GetHeight(), + }) +} diff --git a/ddl/migrations/0203_contest_submissions.sql b/ddl/migrations/0203_contest_submissions.sql new file mode 100644 index 00000000..a69b9adb --- /dev/null +++ b/ddl/migrations/0203_contest_submissions.sql @@ -0,0 +1,24 @@ +begin; + +-- Backs the new "open contest" event type, where entries are explicit +-- submissions (no parent-track remix relationship). Discovery's entity +-- manager writes a row here when it processes a SubmitToContest action. +-- The existing remix_contest path keeps using the remixes-table join, +-- so this table only carries open_contest entries. +create table if not exists contest_submissions ( + contest_id integer not null, + track_id integer not null, + user_id integer not null, + created_at timestamp without time zone not null default current_timestamp, + primary key (contest_id, track_id) +); + +create index if not exists contest_submissions_track_id_idx + on contest_submissions using btree (track_id); + +create index if not exists contest_submissions_user_id_idx + on contest_submissions using btree (user_id); + +comment on table contest_submissions is 'Tracks submitted to an open_contest event. Keyed by (contest_id, track_id); contest_id references events.event_id.'; + +commit; diff --git a/indexer/constants.go b/indexer/constants.go index 0fe8dd4f..bfe39d57 100644 --- a/indexer/constants.go +++ b/indexer/constants.go @@ -26,6 +26,10 @@ const ( Action_React = "React" Action_Unreact = "Unreact" Action_Report = "Report" + // Action_SubmitToContest entries a track in an open-contest event. + // EntityType is Event, EntityId is the contest event_id, and the + // submitted track_id rides on the metadata JSON ({"track_id": }). + Action_SubmitToContest = "SubmitToContest" ) const (