async def edit_user(req): db = req.app["db"] data = req["data"] ref_id = req.match_info["ref_id"] user_id = req.match_info["user_id"] document = await db.references.find_one( { "_id": ref_id, "users.id": user_id }, ["groups", "users"]) if document is None: return not_found() if not await virtool.references.db.check_right(req, ref_id, "modify"): return insufficient_rights() subdocument = await virtool.references.db.edit_group_or_user( db, ref_id, user_id, "users", data) if subdocument is None: return not_found() subdocument = await virtool.users.db.attach_identicons(db, subdocument) return json_response(subdocument)
async def download_sequence(req): """ Download a FASTA file containing a single Virtool sequence. """ db = req.app["db"] sequence_id = req.match_info["sequence_id"] try: filename, fasta = await virtool.downloads.db.generate_sequence_fasta( db, sequence_id) except virtool.errors.DatabaseError as err: if "Sequence does not exist" in str(err): return not_found("Sequence not found") if "Isolate does not exist" in str(err): return not_found("Isolate not found") if "OTU does not exist" in str(err): return not_found("OTU not found") raise if fasta is None: return web.Response(status=404) return web.Response( text=fasta, headers={"Content-Disposition": f"attachment; filename={filename}"})
async def download_sample_reads(req): db = req.app["db"] sample_id = req.match_info["sample_id"] extension = req.match_info["extension"] files = await virtool.db.utils.get_one_field(db.samples, "files", sample_id) if not files: return not_found() suffix = req.match_info["suffix"] sample_path = virtool.samples.utils.join_sample_path( req.app["settings"], sample_id) if extension == "fastq" or extension == "fq": path = virtool.samples.utils.join_legacy_read_path(sample_path, suffix) else: path = virtool.samples.utils.join_read_path(sample_path, suffix) if not os.path.isfile(path): return not_found() file_stats = virtool.utils.file_stats(path) headers = { "Content-Length": file_stats["size"], "Content-Type": "application/gzip" } return web.FileResponse(path, chunk_size=1024 * 1024, headers=headers)
async def replace_sample_file(req): error_resp = naive_validator(req) if error_resp: return error_resp db = req.app["db"] filename = req.query["name"] sample_id = req.match_info["sample_id"] suffix = req.match_info["suffix"] index = int(suffix) - 1 minimal = await db.samples.find_one(sample_id, ["paired"]) if minimal is None: return not_found("Sample not found") if suffix != "1" and suffix != "2": return not_found("Invalid file suffix. Must be 1 or 2.") if suffix == "2" and not minimal.get("paired"): return not_found("Sample is not paired") document = await virtool.files.db.create(db, filename, "sample_replacement", user_id=req["client"].user_id, reserved=True) await naive_writer(req, document["id"]) replacement = { "id": document["id"], "name": document["name"], "uploaded_at": document["uploaded_at"] } files = await virtool.db.utils.get_one_field(db.samples, "files", sample_id) files[index].update({"replacement": replacement}) await db.samples.find_one_and_update({"_id": sample_id}, {"$set": { "files": files }}) await virtool.samples.db.attempt_file_replacement(req.app, sample_id, req["client"].user_id) return json_response(document, status=201)
async def edit_sequence(req): db, data = req.app["db"], req["data"] otu_id, isolate_id, sequence_id = ( req.match_info[key] for key in ["otu_id", "isolate_id", "sequence_id"]) document = await db.otus.find_one({ "_id": otu_id, "isolates.id": isolate_id }) if not document or not await db.sequences.count({"_id": sequence_id}): return not_found() if not await virtool.references.db.check_right( req, document["reference"]["id"], "modify_otu"): return insufficient_rights() old = await virtool.otus.db.join(db, otu_id, document) segment = data.get("segment", None) if segment and segment not in { s["name"] for s in document.get("schema", {}) }: return not_found("Segment does not exist") data["sequence"] = data["sequence"].replace(" ", "").replace("\n", "") updated_sequence = await db.sequences.find_one_and_update( {"_id": sequence_id}, {"$set": data}) document = await db.otus.find_one_and_update({"_id": otu_id}, { "$set": { "verified": False }, "$inc": { "version": 1 } }) new = await virtool.otus.db.join(db, otu_id, document) await virtool.otus.db.update_verification(db, new) isolate = virtool.otus.utils.find_isolate(old["isolates"], isolate_id) await virtool.history.db.add( db, "edit_sequence", old, new, f"Edited sequence {sequence_id} in {virtool.otus.utils.format_isolate_name(isolate)}", req["client"].user_id) return json_response(virtool.utils.base_processor(updated_sequence))
async def remove_sequence(req): """ Remove a sequence from an isolate. """ db = req.app["db"] otu_id = req.match_info["otu_id"] isolate_id = req.match_info["isolate_id"] sequence_id = req.match_info["sequence_id"] if not await db.sequences.count({"_id": sequence_id}): return not_found() old = await virtool.otus.db.join(db, { "_id": otu_id, "isolates.id": isolate_id }) if old is None: return not_found() if not await virtool.references.db.check_right(req, old["reference"]["id"], "modify_otu"): return insufficient_rights() isolate = virtool.otus.utils.find_isolate(old["isolates"], isolate_id) await db.sequences.delete_one({"_id": sequence_id}) await db.otus.update_one({"_id": otu_id}, { "$set": { "verified": False }, "$inc": { "version": 1 } }) new = await virtool.otus.db.join(db, otu_id) await virtool.otus.db.update_verification(db, new) isolate_name = virtool.otus.utils.format_isolate_name(isolate) await virtool.history.db.add( db, "remove_sequence", old, new, f"Removed sequence {sequence_id} from {isolate_name}", req["client"].user_id) return no_content()
async def update_permissions(req): """ Updates the permissions of a given group. """ db = req.app["db"] data = req["data"] group_id = req.match_info["group_id"] old_document = await db.groups.find_one({"_id": group_id}, ["permissions"]) if not old_document: return not_found() old_document["permissions"].update(data["permissions"]) # Get the current permissions dict for the passed group id. document = await db.groups.find_one_and_update( {"_id": group_id}, {"$set": { "permissions": old_document["permissions"] }}) await virtool.groups.db.update_member_users(db, group_id) return json_response(virtool.utils.base_processor(document))
async def add_user(req): db = req.app["db"] data = req["data"] ref_id = req.match_info["ref_id"] document = await db.references.find_one(ref_id, ["groups", "users"]) if document is None: return not_found() if not await virtool.references.db.check_right(req, ref_id, "modify"): return insufficient_rights() try: subdocument = await virtool.references.db.add_group_or_user( db, ref_id, "users", data) except virtool.errors.DatabaseError as err: if "already exists" in str(err): return bad_request("User already exists") if "does not exist" in str(err): return bad_request("User does not exist") raise headers = {"Location": f"/api/refs/{ref_id}/users/{subdocument['id']}"} subdocument = await virtool.users.db.attach_identicons(db, subdocument) return json_response(subdocument, headers=headers, status=201)
async def upload(req): db = req.app["db"] file_type = req.match_info["file_type"] if file_type not in FILE_TYPES: return not_found() errors = naive_validator(req) if errors: return invalid_query(errors) filename = req.query["name"] document = await virtool.files.db.create(db, filename, file_type, user_id=req["client"].user_id) file_id = document["id"] await naive_writer(req, file_id) headers = {"Location": f"/api/files/{file_id}"} return json_response(document, status=201, headers=headers)
async def set_rights(req): """ Change rights settings for the specified sample document. """ db = req.app["db"] data = req["data"] sample_id = req.match_info["sample_id"] if not await db.samples.count({"_id": sample_id}): return not_found() user_id = req["client"].user_id # Only update the document if the connected user owns the samples or is an administrator. if not req[ "client"].administrator and user_id != await virtool.samples.db.get_sample_owner( db, sample_id): return insufficient_rights("Must be administrator or sample owner") group = data.get("group", None) if group: existing_group_ids = await db.groups.distinct("_id") + ["none"] if group not in existing_group_ids: return bad_request("Group does not exist") # Update the sample document with the new rights. document = await db.samples.find_one_and_update( {"_id": sample_id}, {"$set": data}, projection=virtool.samples.db.RIGHTS_PROJECTION) return json_response(document)
async def get(req): """ Get the complete representation of a specific reference. """ db = req.app["db"] ref_id = req.match_info["ref_id"] document = await db.references.find_one(ref_id) if not document: return not_found() try: internal_control_id = document["internal_control"]["id"] except (KeyError, TypeError): internal_control_id = None computed = await asyncio.shield( virtool.references.db.get_computed(db, ref_id, internal_control_id)) users = await asyncio.shield( virtool.users.db.attach_identicons(db, document["users"])) document.update({**computed, "users": users}) return json_response(virtool.utils.base_processor(document))
async def remove(req): """ Remove a job. """ db = req.app["db"] job_id = req.match_info["job_id"] document = await db.jobs.find_one(job_id) if not document: return not_found() if virtool.jobs.is_running_or_waiting(document): return conflict("Job is running or waiting and cannot be removed") # Removed the documents associated with the job ids from the database. await db.jobs.delete_one({"_id": job_id}) try: # Calculate the log path and remove the log file. If it exists, return True. path = os.path.join(req.app["settings"]["data_path"], "logs", "jobs", job_id + ".log") await req.app["run_in_thread"](virtool.utils.rm, path) except OSError: pass return no_content()
async def find_history(req): """ Find history changes for a specific index. """ db = req.app["db"] index_id = req.match_info["index_id"] if not await db.indexes.count({"_id": index_id}): return not_found() term = req.query.get("term", None) db_query = {"index.id": index_id} if term: db_query.update(compose_regex_query(term, ["otu.name", "user.id"])) data = await paginate(db.history, db_query, req.query, sort=[("otu.name", 1), ("otu.version", -1)], projection=virtool.history.db.LIST_PROJECTION, reverse=True) return json_response(data)
async def get(req): """ Get a complete analysis document. """ db = req.app["db"] analysis_id = req.match_info["analysis_id"] document = await db.analyses.find_one(analysis_id) if document is None: return not_found() sample = await db.samples.find_one({"_id": document["sample"]["id"]}, {"quality": False}) if not sample: return bad_request("Parent sample does not exist") read, _ = virtool.samples.utils.get_sample_rights(sample, req["client"]) if not read: return insufficient_rights() if document["ready"]: document = await virtool.analyses.format.format_analysis(db, req.app["settings"], document) document["subtraction"] = { "id": sample["subtraction"]["id"] } return json_response(virtool.utils.base_processor(document))
async def install(req): db = req.app["db"] releases = await virtool.db.utils.get_one_field(db.status, "releases", "software") try: latest_release = releases[0] except IndexError: return not_found("Could not find latest uninstalled release") process = await virtool.processes.db.register( db, "update_software", context={"file_size": latest_release["size"]}) await db.status.update_one( {"_id": "software"}, {"$set": { "process": process, "updating": True }}) update = virtool.github.create_update_subdocument( latest_release, False, req["client"].user_id, virtool.utils.timestamp()) await aiojobs.aiohttp.spawn( req, virtool.software.db.install(req.app, latest_release, process["id"])) return json_response(update)
async def remove(req): """ Remove a reference and its otus, history, and indexes. """ db = req.app["db"] ref_id = req.match_info["ref_id"] if not await virtool.db.utils.id_exists(db.references, ref_id): return not_found() if not await virtool.references.db.check_right(req, ref_id, "remove"): return insufficient_rights() user_id = req["client"].user_id context = {"ref_id": ref_id, "user_id": user_id} process = await virtool.processes.db.register(db, "delete_reference", context=context) await db.references.delete_one({"_id": ref_id}) p = virtool.references.db.RemoveReferenceProcess(req.app, process["id"]) await aiojobs.aiohttp.spawn(req, p.run()) headers = {"Content-Location": f"/api/processes/{process['id']}"} return json_response(process, 202, headers)
async def get(req): """ Gets a complete group document. """ document = await req.app["db"].groups.find_one(req.match_info["group_id"]) if document: return json_response(virtool.utils.base_processor(document)) return not_found()
async def list_history(req): db = req.app["db"] otu_id = req.match_info["otu_id"] if not await db.otus.find({"_id": otu_id}).count(): return not_found() cursor = db.history.find({"otu.id": otu_id}) return json_response([d async for d in cursor])
async def get(req): db = req.app["db"] process_id = req.match_info["process_id"] document = await db.processes.find_one(process_id) if not document: return not_found() return json_response(virtool.utils.base_processor(document))
async def list_groups(req): db = req.app["db"] ref_id = req.match_info["ref_id"] if not await db.references.count({"_id": ref_id}): return not_found() groups = await virtool.db.utils.get_one_field(db.references, "groups", ref_id) return json_response(groups)
async def find_indexes(req): db = req.app["db"] ref_id = req.match_info["ref_id"] if not await virtool.db.utils.id_exists(db.references, ref_id): return not_found() data = await virtool.indexes.db.find(db, req.query, ref_id=ref_id) return json_response(data)
async def get_api_key(req): db = req.app["db"] user_id = req["client"].user_id key_id = req.match_info["key_id"] document = await db.keys.find_one({"id": key_id, "user.id": user_id}, API_KEY_PROJECTION) if document is None: return not_found() return json_response(document, status=200)
async def get(req): """ Get a near-complete user document. Password data are removed. """ document = await req.app["db"].users.find_one(req.match_info["user_id"], virtool.users.db.PROJECTION) if not document: return not_found() return json_response(virtool.utils.base_processor(document))
async def remove(req): file_id = req.match_info["file_id"] deleted_count = await virtool.files.db.remove(req.app["db"], req.app["settings"], req.app["run_in_thread"], file_id) if deleted_count == 0: return not_found() return json_response({"file_id": file_id, "removed": True})
async def get(req): """ Get a complete individual HMM annotation document. """ document = await req.app["db"].hmm.find_one( {"_id": req.match_info["hmm_id"]}) if document is None: return not_found() return json_response(virtool.utils.base_processor(document))
async def get(req): """ Return the complete document for a given job. """ job_id = req.match_info["job_id"] document = await req.app["db"].jobs.find_one(job_id) if not document: return not_found() return json_response(virtool.utils.base_processor(document))
async def get_sequence(req): """ Get a single sequence document by its ``accession`. """ db = req.app["db"] otu_id = req.match_info["otu_id"] isolate_id = req.match_info["isolate_id"] sequence_id = req.match_info["sequence_id"] if not await db.otus.count({"_id": otu_id, "isolates.id": isolate_id}): return not_found() query = {"_id": sequence_id, "otu_id": otu_id, "isolate_id": isolate_id} document = await db.sequences.find_one(query, virtool.otus.db.SEQUENCE_PROJECTION) if not document: return not_found() return json_response(virtool.utils.base_processor(document))
async def get(req): """ Return the complete representation for the cache with the given `cache_id`. """ db = req.app["db"] cache_id = req.match_info["cache_id"] cache = await virtool.caches.db.get(db, cache_id) if cache is None: return not_found() return json_response(cache)
async def remove_api_key(req): db = req.app["db"] user_id = req["client"].user_id key_id = req.match_info["key_id"] delete_result = await db.keys.delete_one({ "id": key_id, "user.id": user_id }) if delete_result.deleted_count == 0: return not_found() return no_content()
async def get(req): """ Get a complete otu document. Joins the otu document with its associated sequence documents. """ db = req.app["db"] otu_id = req.match_info["otu_id"] complete = await virtool.otus.db.join_and_format(db, otu_id) if not complete: return not_found() return json_response(complete)