779b0a67557941da1d0402d9d6f2952ff54fb479 hiram Mon Apr 27 16:48:42 2026 -0700 add API assemblyRequest endpoint properly manage requests from the assemblySearch page system refs #31811 diff --git src/hg/hubApi/findGenome.c src/hg/hubApi/findGenome.c index 42c93621c0f..63c63ba8ee8 100644 --- src/hg/hubApi/findGenome.c +++ src/hg/hubApi/findGenome.c @@ -534,15 +534,106 @@ if (itemCount) { jsonWriteNumber(jw, "itemCount", itemCount); jsonWriteNumber(jw, "totalMatchCount", totalMatchCount); jsonWriteNumber(jw, "availableAssemblies", universeCount); if (totalMatchCount > itemCount) jsonWriteBoolean(jw, "maxItemsLimit", TRUE); apiFinishOutput(0, NULL, jw); } else apiErrAbort(err400, err400Msg, "no genomes found matching search term %s='%s' for endpoint: /findGenome", argQ, inputSearchString); hDisconnectCentral(&conn); } + +void apiAssemblyRequest(char *words[MAX_PATH_INFO]) +/* interface to the assemblySearch.html request form. Replaces the + * primitive /cgi-bin/asr perl CGI script. Inserts a row into the + * ottoRequest table; the otto cron job watcher handles email. + */ +{ +char *extraArgs = verifyLegalArgs(argAssemblyRequest); +if (extraArgs) + apiErrAbort(err400, err400Msg, "extraneous arguments found for function /assemblyRequest '%s'", extraArgs); + +char *asmId = cgiOptionalString(argAsmId); +char *name = cgiOptionalString(argName); +char *email = cgiOptionalString(argEmail); +char *betterName = cgiOptionalString(argBetterName); +char *comment = cgiOptionalString(argComment); + +if (isEmpty(asmId) || isEmpty(name) || isEmpty(email)) + apiErrAbort(err400, err400Msg, "must have arguments: %s, %s, %s for endpoint '/assemblyRequest'", argAsmId, argName, argEmail); + +/* require referer to be our own assemblySearch.html, mirroring apiLiftRequest */ +char *cookieName = hUserCookie(); +char *userId = findCookieData(cookieName); +char *referer = getenv("HTTP_REFERER"); +char dir[PATH_LEN]; +char fname[FILENAME_LEN]; +char ext[FILEEXT_LEN]; +if (isNotEmpty(referer) && isNotEmpty(userId)) + { + /* assemblySearch.html uses history.pushState to add query strings to its + * URL, so the referer arrives as e.g. ".../assemblySearch.html?searchBox=foo". + * Strip query/fragment before splitPath so the .html extension matches. */ + char *cleanRef = cloneString(referer); + char *q = strchr(cleanRef, '?'); + if (q) *q = '\0'; + q = strchr(cleanRef, '#'); + if (q) *q = '\0'; + splitPath(cleanRef, dir, fname, ext); + if (! (endsWith(dir, ".ucsc.edu/") && sameWord(fname, "assemblySearch") && sameWord(ext, ".html"))) + apiErrAbort(err400, err400Msg, "can not find required inputs for endpoint '/assemblyRequest'"); + freeMem(cleanRef); + } +else + { + if (! debug) + apiErrAbort(err400, err400Msg, "can not find required inputs for endpoint '/assemblyRequest'"); + } + +/* the ottoRequest table has no name/betterName columns, fold them into comment */ +struct dyString *fullComment = dyStringNew(0); +dyStringPrintf(fullComment, "name: '%s'", name); +if (isNotEmpty(betterName)) + dyStringPrintf(fullComment, "; betterName: '%s'", betterName); +if (isNotEmpty(comment)) + dyStringPrintf(fullComment, "; comment: '%s'", comment); + +char nowTime[256]; +time_t seconds = clock1(); +struct tm *timeNow = localtime(&seconds); +strftime(nowTime, sizeof nowTime, "%Y-%m-%d %H:%M:%S", timeNow); + +struct dyString *msg = dyStringNew(0); +dyStringPrintf(msg, "%s\nAssembly request\nasmId: %s\nname: %s\nemail: %s\nbetterName: %s\ncomment: %s", + nowTime, asmId, name, email, + isNotEmpty(betterName) ? betterName : "", + isNotEmpty(comment) ? comment : ""); + +struct jsonWrite *jw = apiStartOutput(); +jsonWriteString(jw, "msg", dyStringContents(msg)); +apiFinishOutput(0, NULL, jw); + +char *ottoTable = cfgOption("ottoTable"); /* probably ottoRequest */ +if (isNotEmpty(ottoTable)) + { + struct sqlConnection *conn = hConnectCentral(); + if (sqlTableExists(conn, ottoTable)) + { + /* asmId placed in both fromDb and toDb in case toDb does not allow empty */ + struct dyString *update = dyStringNew(0); + sqlDyStringPrintf(update, + "INSERT INTO %s (requestType, fromDb, toDb, email, comment, requestTime, status, buildDir) " + "VALUES ('assembly', '%s', '%s', '%s', '%s', now(), 0, '')", + ottoTable, asmId, asmId, email, dyStringContents(fullComment)); + sqlUpdate(conn, dyStringCannibalize(&update)); + } + hDisconnectCentral(&conn); + } + +dyStringFree(&fullComment); +dyStringFree(&msg); +} /* void apiAssemblyRequest(char *words[MAX_PATH_INFO]) */