예제 #1
0
    def _bindings_sanity_check(self, the_ids, the_bindings, binding_cls):
        # sanity check: ensure all aliases are in the bindings
        binding_ids = {b['id'] for b in the_bindings}
        missing_ids = set(the_ids).difference(binding_ids)
        for missing in missing_ids:
            obj = binding_cls.from_id(missing, collection=self)
            the_bindings.append({"name": obj.name, "id": obj.id})

        # sanity check: ensure all names are valid
        for binding in the_bindings:
            if ' ' in binding['name']:
                raise NotAllowed("Spaces are not allowed in bindings.")
            # alias-only checks
            if binding_cls is WorkshopAlias:
                if binding['name'] in current_app.rdb.jget(
                        "default_commands", []):
                    raise NotAllowed(
                        f"{binding['name']} is already a built-in command.")
            # snippet-only checks
            if binding_cls is WorkshopSnippet:
                if len(binding['name']) < 2:
                    raise NotAllowed(
                        "Snippet names must be at least 2 characters.")

        # sanity check: ensure there is no binding to anything deleted
        return [b for b in the_bindings if b['id'] in the_ids]
예제 #2
0
    def remove_entitlement(self, sourced_entity):
        """
        Removes a required entitlement from this collectable.

        :type sourced_entity: gamedata.shared.Sourced
        """
        existing = next(
            (e for e in self.entitlements
             if (e.entity_type, e.entity_id) == (sourced_entity.entity_type,
                                                 sourced_entity.entity_id)),
            None)
        if existing is None:
            raise NotAllowed(
                "This collectable does not require this entitlement.")
        if existing.required:
            raise NotAllowed("This entitlement is required.")
        # add to database
        self.mdb_coll().update_one({"_id": self.id}, {
            "$pull": {
                "entitlements": {
                    "entity_type": existing.entity_type,
                    "entity_id": existing.entity_id
                }
            }
        })
        self.collection.update_edit_time()
        self.entitlements.remove(existing)
        return [e.to_dict() for e in self.entitlements]
예제 #3
0
    def delete(self):
        """Deletes the snippet from the collection."""

        # do not allow deletion of top-level aliases
        if self.collection.publish_state == PublicationState.PUBLISHED:
            raise NotAllowed(
                "You cannot delete a top-level snippet from a published collection."
            )

        # clear all bindings
        self.collection.sub_coll(current_app.mdb).update_many(
            {
                "type": {
                    "$in": ["subscribe", "server_active"]
                },
                "object_id": self.collection.id
            }, {"$pull": {
                "snippet_bindings": {
                    "id": self.id
                }
            }}
            # pull from the snippet_bindings array all docs with this id
        )

        # remove reference from collection
        current_app.mdb.workshop_collections.update_one(
            {"_id": self.collection.id}, {"$pull": {
                "snippet_ids": self.id
            }})
        self.collection.snippet_ids.remove(self.id)
        self.collection.update_edit_time()

        # delete from db
        self.mdb_coll().delete_one({"_id": self.id})
예제 #4
0
 def set_active_code_version(self, version: int):
     """Sets the code version with version=version active."""
     cv = next((cv for cv in self.versions if cv.version == version), None)
     if cv is None:
         raise NotAllowed("This code version does not exist")
     # set correct current version and update code
     self.mdb_coll().update_one({"_id": self.id}, {
         "$set": {
             "code": cv.content,
             "versions.$[current].is_current": True,
             "versions.$[notcurrent].is_current": False
         }
     },
                                array_filters=[{
                                    "current.version": version
                                }, {
                                    "notcurrent.version": {
                                        "$ne": version
                                    }
                                }])
     for old_cv in self.versions:
         old_cv.is_current = False
     cv.is_current = True
     self.code = cv.content
     self.collection.update_edit_time()
예제 #5
0
 def create_new(cls, user_id: int, name, description, image):
     """Inserts a new collection into the database and returns the new collection."""
     if not name:
         raise NotAllowed("Name is required.")
     if not description:
         raise NotAllowed("Description is required.")
     now = datetime.datetime.now()
     # noinspection PyTypeChecker
     # id is None until inserted
     inst = cls(None, name, description, image, user_id, [], [],
                PublicationState.PRIVATE, 0, 0, now, now, [])
     result = current_app.mdb.workshop_collections.insert_one(
         inst.to_dict())
     inst.id = result.inserted_id
     inst.update_elasticsearch()
     return inst
예제 #6
0
    def subscribe(self, user_id: int):
        """Adds the user as a subscriber."""
        if self.is_subscribed(user_id):
            raise NotAllowed("You are already subscribed to this.")

        self.sub_coll(current_app.mdb).insert_one(
            {"type": "subscribe", "subscriber_id": user_id, "object_id": self.id}
        )
예제 #7
0
    def set_state(self, new_state, run_checks=True):
        """
        Updates the collection's publication state, running checks as necessary.

        :type new_state: str or PublicationState
        :param bool run_checks: Whether or not to run the publication state checks (bypass for moderation).
        """
        if isinstance(new_state, str):
            new_state = PublicationState(new_state.upper())

        if new_state == self.publish_state:  # we don't need to do anything
            return

        if run_checks:
            # cannot unpublish
            if self.publish_state == PublicationState.PUBLISHED:
                raise NotAllowed(
                    "You cannot unpublish a collection after it has been published"
                )

            # prepublish check: name and description are present, at least one alias/snippet
            if new_state == PublicationState.PUBLISHED:
                if not self.name:
                    raise NotAllowed(
                        "A name must be present to publish this collection")
                if not self.description:
                    raise NotAllowed(
                        "A description must be present to publish this collection"
                    )
                if len(self.alias_ids) == 0 and len(self.snippet_ids) == 0:
                    raise NotAllowed(
                        "At least one alias or snippet must be present to publish this collection"
                    )

        current_app.mdb.workshop_collections.update_one({"_id": self.id}, {
            "$set": {
                "publish_state": new_state.value
            },
            "$currentDate": {
                "last_edited": True
            }
        })
        self.publish_state = new_state
        self.last_edited = datetime.datetime.now()
        self.update_elasticsearch()
예제 #8
0
    def unsubscribe(self, user_id: int):
        """Removes the user from subscribers."""
        if not self.is_subscribed(user_id):
            raise NotAllowed("You are not subscribed to this.")

        self.sub_coll(current_app.mdb).delete_many(
            {"type": {"$in": ["subscribe", "active"]}, "subscriber_id": user_id, "object_id": self.id}
            # unsubscribe, unactive
        )
예제 #9
0
    def set_server_active(self,
                          guild_id: int,
                          alias_bindings=None,
                          snippet_bindings=None,
                          invoker_id: int = None):
        """Sets the object as active for the contextual guild, with given name bindings."""
        if self.publish_state == PublicationState.PRIVATE \
                and not (self.is_owner(invoker_id) or self.is_editor(invoker_id)):
            raise NotAllowed("This collection is private.")

        # generate default bindings
        if alias_bindings is None:
            alias_bindings = self._generate_default_alias_bindings()
        else:
            alias_bindings = self._bindings_sanity_check(
                self.alias_ids, alias_bindings, WorkshopAlias)

        if snippet_bindings is None:
            snippet_bindings = self._generate_default_snippet_bindings()
        else:
            snippet_bindings = self._bindings_sanity_check(
                self.snippet_ids, snippet_bindings, WorkshopSnippet)

        # insert sub doc
        result = self.sub_coll(current_app.mdb).update_one(
            {
                "type": "server_active",
                "subscriber_id": guild_id,
                "object_id": self.id
            }, {
                "$set": {
                    "alias_bindings": alias_bindings,
                    "snippet_bindings": snippet_bindings
                }
            },
            upsert=True)

        if result.upserted_id is not None:
            # incr sub count
            current_app.mdb.workshop_collections.update_one(
                {"_id": self.id}, {"$inc": {
                    "num_guild_subscribers": 1
                }})
            # log sub event
            self.log_event({
                "type": "server_subscribe",
                "object_id": self.id,
                "timestamp": datetime.datetime.utcnow(),
                "user_id": invoker_id
            })

        return {
            "alias_bindings": alias_bindings,
            "snippet_bindings": snippet_bindings,
            "new_subscription": result.upserted_id is not None
        }
예제 #10
0
 def update_info(self, name: str, description: str, image):
     """Updates the collection's user information."""
     if not name:
         raise NotAllowed("Name is required.")
     if not description:
         raise NotAllowed("Description is required.")
     current_app.mdb.workshop_collections.update_one({"_id": self.id}, {
         "$set": {
             "name": name,
             "description": description,
             "image": image
         },
         "$currentDate": {
             "last_edited": True
         }
     })
     self.name = name
     self.description = description
     self.image = image
     self.last_edited = datetime.datetime.now()
     self.update_elasticsearch()
예제 #11
0
def _bindings_check(coll, bindings):
    if bindings is None:
        return

    for binding in bindings:
        if not isinstance(binding, dict):
            raise NotAllowed("bindings must be list of {name, id}")

        if set(binding) != {"name", "id"}:
            raise NotAllowed("bindings must be list of {name, id}")

        if not isinstance(binding['name'], str):
            raise NotAllowed("binding name must be str")

        if isinstance(binding['id'], dict):
            if '$oid' not in binding['id']:
                raise NotAllowed("binding id must be ObjectId")
            oid = ObjectId(binding['id']['$oid'])
        elif isinstance(binding['id'], str):
            oid = ObjectId(binding['id'])
        else:
            raise NotAllowed("binding id must be ObjectId")

        if not (oid in coll.alias_ids or oid in coll.snippet_ids):
            raise NotAllowed("binding must be to object in collection")

        binding['id'] = oid
예제 #12
0
    def add_entitlement(self, sourced_entity, required=False):
        """
        Adds a required entitlement to this collectable.

        :type sourced_entity: gamedata.shared.Sourced
        :param bool required: Whether or not this entitlement is required by a moderator (cannot be removed).
        """
        if sourced_entity.is_free:
            raise NotAllowed("This entitlement is for a free object.")
        re = RequiredEntitlement(sourced_entity.entity_type,
                                 sourced_entity.entity_id, required)
        if (re.entity_type,
                re.entity_id) in ((existing.entity_type, existing.entity_id)
                                  for existing in self.entitlements):
            raise NotAllowed(
                "This collectable already has this entitlement required.")
        # add to database
        self.mdb_coll().update_one({"_id": self.id},
                                   {"$push": {
                                       "entitlements": re.to_dict()
                                   }})
        self.collection.update_edit_time()
        self.entitlements.append(re)
        return [e.to_dict() for e in self.entitlements]
예제 #13
0
    def delete(self, run_checks=True):
        """Deletes the alias from the collection."""

        if run_checks:
            # do not allow deletion of top-level aliases
            if self.collection.publish_state == PublicationState.PUBLISHED and self._parent_id is None:
                raise NotAllowed(
                    "You cannot delete a top-level alias from a published collection."
                )

        if self._parent_id is None:
            # clear all bindings
            self.collection.sub_coll(current_app.mdb).update_many(
                {
                    "type": {
                        "$in": ["subscribe", "server_active"]
                    },
                    "object_id": self.collection.id
                }, {"$pull": {
                    "alias_bindings": {
                        "id": self.id
                    }
                }}
                # pull from the alias_bindings array all docs with this id
            )

            # remove reference from collection
            current_app.mdb.workshop_collections.update_one(
                {"_id": self.collection.id}, {"$pull": {
                    "alias_ids": self.id
                }})
            self.collection.alias_ids.remove(self.id)
        else:
            # remove reference from parent
            self.mdb_coll().update_one({"_id": self.parent.id},
                                       {"$pull": {
                                           "subcommand_ids": self.id
                                       }})
            self.parent._subcommand_ids.remove(self.id)

        # delete all children
        for child in self.subcommands:
            child.delete(run_checks)

        self.collection.update_edit_time()

        # delete from db
        self.mdb_coll().delete_one({"_id": self.id})
예제 #14
0
    def add_tag(self, tag: str):
        """Adds a tag to this collection. Validates the tag. Does nothing if the tag already exists."""
        if current_app.mdb.workshop_tags.find_one({"slug": tag}) is None:
            raise NotAllowed(f"{tag} is not a valid tag")
        if tag in self.tags:
            return  # we already have the tag, do a no-op

        current_app.mdb.workshop_collections.update_one({"_id": self.id}, {
            "$push": {
                "tags": tag
            },
            "$currentDate": {
                "last_edited": True
            }
        })
        self.tags.append(tag)
        self.last_edited = datetime.datetime.now()
        self.update_elasticsearch()
예제 #15
0
    def delete(self):
        # do not allow deletion of published collections
        if self.publish_state == PublicationState.PUBLISHED:
            raise NotAllowed("You cannot delete a published collection.")

        # delete all children
        for alias in self.aliases:
            alias.delete()
        for snippet in self.snippets:
            snippet.delete()

        # delete from db
        current_app.mdb.workshop_collections.delete_one({"_id": self.id})

        # delete subscriptions
        self.sub_coll(current_app.mdb).delete_many({"object_id": self.id})

        # delete from elasticsearch
        requests.delete(
            f"{config.ELASTICSEARCH_ENDPOINT}/workshop_collections/_doc/{str(self.id)}"
        )
예제 #16
0
 def add_editor(self, user_id: int):
     """Adds the user to the editor list of this object."""
     if self.is_editor(user_id):
         raise NotAllowed("This user is already an editor.")
     self.sub_coll(current_app.mdb).insert_one({"type": "editor", "subscriber_id": user_id, "object_id": self.id})