Esempio n. 1
0
// We never update drafts, we always insert a new one.
// A draft will have the next fields: id, type, created, up_to_date, parent_content/draft_content/none/both, data.
// The saved input resides in the data.
func SaveDraft(db *mgo.Database, content_rules map[string]interface{}, inp map[string][]string) (bson.ObjectId, error) {
	for i, _ := range content_rules {
		content_rules[i] = 1
	}
	content_rules["type"] = "must"
	content_rules["id"] = "must"       // Content id.
	content_rules["draft_id"] = "must" // Draft id, no empty if we save draft from a draft, empty if draft is saved from a content.
	dat, err := extract.New(content_rules).Extract(inp)
	if err != nil {
		return "", err
	}
	live_id_s := dat["id"].(string)
	draft_id_s := dat["draft_id"].(string)
	ins := m{
		"created": time.Now().Unix(),
		"data":    dat,
		"type":    dat["type"],
	}
	var parent, root bson.ObjectId
	if len(draft_id_s) > 0 { // Coming from a draft.
		draft_id := patterns.ToIdWithCare(draft_id_s)
		var last_version bson.ObjectId
		parent, root, last_version, err = basic.GetDraftParent(db, "contents", draft_id)
		if parent == "" || root == "" {
			return "", fmt.Errorf("Coming from draft but still can not extract parent or root.")
		}
		// Has no version of it is a draft/descendant of a draft which connects to no saved content.
		if last_version != "" {
			ins["draft_of_version"] = last_version
		}
	} else if len(live_id_s) > 0 { // Coming from a content.
		live_id := patterns.ToIdWithCare(live_id_s)
		// This way draft saves coming from any versions will be saved to the version pointed to by the head anyway...
		parent, root, err = basic.GetParentTroughContent(db, "contents", live_id)
		// If we have a live content saved, we must have a version to point to too.
		if parent == "" || root == "" {
			return "", fmt.Errorf("Coming from content but still can not extract parent or root.")
		}
		ins["draft_of_version"] = parent
	}
	if err != nil {
		return "", err
	}
	if len(live_id_s) > 0 {
		live_id := patterns.ToIdWithCare(live_id_s)
		ins["draft_of"] = live_id // Just so we can display the content id for saving content immediately from a draft.
	}
	draft_id := bson.NewObjectId()
	if parent != "" { // If there is a parent there is a root...
		ins["-parent"] = parent
		ins["root"] = root
	} else {
		ins["root"] = draft_id
	}
	// Note: we dont store the last version here, building the draft will be harder.
	ins["_id"] = draft_id
	err = db.C(Cname + Draft_collection_postfix).Insert(ins)
	return draft_id, err
}
Esempio n. 2
0
func (v *V) editDraft(typ, id string) (interface{}, error) {
	hasid := len(id) > 0
	uni := v.uni
	uni.Dat["is_draft"] = true
	if hasid {
		built, err := content_model.BuildDraft(uni.Db, typ, id)
		if err != nil {
			return nil, err
		}
		d := built["data"].(map[string]interface{})
		if _, draft_of_sg := built["draft_of"]; draft_of_sg {
			uni.Dat["content_parent"] = true
			fresher, err := content_model.IsDraftUpToDate(uni.Db, built, d)
			if err != nil {
				return nil, err
			}
			uni.Dat["up_to_date"] = fresher
			uni.Dat["op"] = "update"
		} else { // It's possible that it has no parent at all, then it is a fresh new draft, first version.
			uni.Dat["op"] = "insert"
		}
		resolver.ResolveOne(uni.Db, d, nil)
		uni.Dat["content"] = d
		timeline, err := content_model.DraftTimeline(uni.Db, patterns.ToIdWithCare(id))
		if err != nil {
			return nil, err
		}
		uni.Dat["timeline"] = timeline
		uni.Dat["draft"] = built
		return d, nil
	}
	uni.Dat["op"] = "insert"
	return map[string]interface{}{}, nil
}
Esempio n. 3
0
func (a *A) allowsContent(op string) (bson.ObjectId, string, error) {
	uni := a.uni
	var typ string
	if op == "insert" {
		typ = uni.Req.Form["type"][0] // See TODO below.
	} else {
		content_id := patterns.ToIdWithCare(uni.Req.Form["id"][0]) // TODO: Don't let it panic if id does not exists, return descriptive error message.
		_typ, err := content_model.TypeOf(uni.Db, content_id)
		if err != nil {
			return "", "", err
		}
		typ = _typ
	}
	auth_opts, ignore := user.AuthOpts(uni, "content.types."+typ, op)
	if ignore {
		return "", "", fmt.Errorf("Auth options should not be ignored.")
	}
	err, _ := user.AuthAction(uni, auth_opts)
	if err != nil {
		return "", "", err
	}
	uid_i, has_uid := jsonp.Get(uni.Dat, "_user._id")
	if !has_uid {
		return "", "", fmt.Errorf("Can't %v content, you have no id.", op)
	}
	uid := uid_i.(bson.ObjectId)
	user_level := scut.Ulev(uni.Dat["_user"])
	allowed_err := content_model.CanModifyContent(uni.Db, uni.Req.Form, 300, uid, user_level)
	if allowed_err != nil {
		return "", "", allowed_err
	}
	return uid, typ, nil
}
Esempio n. 4
0
// Implementation of versioning is in basic.InudVersion.
func ChangeHead(db *mgo.Database, ev ifaces.Event, inp map[string][]string, non_versioned_fields []string) error {
	rule := map[string]interface{}{
		"version_id": "must",
		"id":         "must",
	}
	dat, err := extract.New(rule).Extract(inp)
	if err != nil {
		return err
	}
	version_id_str := basic.StripId(dat["version_id"].(string))
	version_id := bson.ObjectIdHex(version_id_str)
	var v interface{}
	err = db.C(Cname + "_version").Find(bson.M{"_id": version_id}).One(&v)
	if err != nil {
		return err
	}
	revert_to := v.(bson.M)
	id := patterns.ToIdWithCare(dat["id"].(string))
	for _, v := range non_versioned_fields {
		delete(revert_to, v)
	}
	revert_to["points_to"] = revert_to["_id"]
	delete(revert_to, "id")
	return db.C(Cname).Update(bson.M{"_id": id}, bson.M{"$set": revert_to})
}
Esempio n. 5
0
// Checks if unvotes are approved at all. Returns error if not.
// Checks if vote option is legal.
// Checks if user indeed voted to that option. Return error if not.
// Decreases the counter field of the given vote option, and pulls the user_id from the field of the given vote option.
func Unvote(db *mgo.Database, user, action map[string]interface{}, inp map[string][]string) error {
	can_unvote, has_cu := action["can_unvote"]
	if !has_cu || can_unvote.(bool) == false {
		return fmt.Errorf("Can't unvote.")
	}
	dat, collection, _, input_vote, err := sharedProc(action, inp)
	if err != nil {
		return err
	}
	doc_id := patterns.ToIdWithCare(dat["document_id"].(string))
	user_id := user["_id"].(bson.ObjectId)
	q := m{"_id": doc_id, input_vote: user_id}
	count, err := db.C(collection).Find(q).Count()
	if err != nil {
		return err
	}
	if count != 1 {
		return fmt.Errorf("Can't unvote a doc which you haven't vote on yet.")
	}
	q = m{"_id": doc_id}
	upd := m{
		"$inc": m{
			input_vote + "_count": -1,
		},
		"$pull": m{
			input_vote: user_id,
		},
	}
	return db.C(collection).Update(q, upd)
}
Esempio n. 6
0
// Queries a draft and rebuilds it. Queries its parent too, and merges it with the input fields saved in "data".
// The returned draft will be like a simple draft in the database, but in the data field it will contain fields of the parent plus the fresher saved input data.
// draft_typ example: blog_draft
// Only
func BuildDraft(db *mgo.Database, draft_typ, draft_id_str string) (map[string]interface{}, error) {
	draft_id := patterns.ToIdWithCare(draft_id_str)
	q := m{"_id": draft_id}
	var v interface{}
	err := db.C(Cname + Draft_collection_postfix).Find(q).One(&v)
	if err != nil {
		return nil, err
	}
	draft := basic.Convert(v).(map[string]interface{})
	typ_i, has_typ := draft["type"]
	if !has_typ {
		return nil, fmt.Errorf("Draft has no type.")
	}
	typ, is_str := typ_i.(string)
	if !is_str {
		return nil, fmt.Errorf("Draft type is not a string.")
	}
	if typ != draft_typ {
		return nil, fmt.Errorf("Draft type is not the expected one: %v instead if %v.", typ, draft_typ)
	}
	parent, err := GetParent(db, "contents", draft)
	if err != nil {
		return nil, err
	}
	draft["data"] = mergeWithParent(draft["data"].(map[string]interface{}), parent)
	return draft, nil
}
Esempio n. 7
0
// Example:
// {
//		"type":					"choose_child",
//		"c": 					"contents",
//		"choose_fieldname":		"accepted",			// This field will be set to true when the child document is chosen.
//		"parent_fieldname":		"parent",			// The name of the field in which the parent's id resides.
//		"max_choices":			1					// Optional, defaults to 1. Describes how many chosen children can exists per parent.
// }
//
// Gets chosen document to find out parent.
// Gets parent document, checks if user is the author of it. Return error if not.
// Sets choose_fieldname to the number of already chosen children + 1 in the chosen document.
// Increases "has_" + choose_fieldname by 1 in parent document.
func ChooseChild(db *mgo.Database, user, action map[string]interface{}, inp map[string][]string) error {
	choose_fieldname := action["choose_fieldname"].(string)
	parent_fieldname := action["parent_fieldname"].(string)
	coll := action["c"].(string)
	max_choices := 1
	mc, has_mc := action["max_choices"]
	if has_mc {
		max_choices = int(mc.(float64))
	}
	rule := m{
		"chosen_doc_id": "must",
	}
	dat, err := extract.New(rule).Extract(inp)
	if err != nil {
		return err
	}
	user_id := user["_id"].(bson.ObjectId)
	chosen_doc_id := patterns.ToIdWithCare(dat["chosen_doc_id"])
	parent_id, auth_err := IsAuthor(db, coll, parent_fieldname, user_id, chosen_doc_id)
	if auth_err != nil {
		return err
	}
	current_count, exc_err := Exceeded(db, coll, parent_fieldname, choose_fieldname, parent_id, max_choices)
	if exc_err != nil {
		return err
	}
	err = MarkAsChosen(db, coll, choose_fieldname, chosen_doc_id, current_count)
	if err != nil {
		return err
	}
	return IncrementParent(db, coll, choose_fieldname, parent_id, 1)
}
Esempio n. 8
0
// RespondContent could be called "insert child content".
// Example:
// {
// 		"type": 									"respond",
//		"parent_fieldname":							"parent",
//		"counter_fieldname":						"replies",
//		"can_delete":								true,								// Optional, defaults to false
//		"delete_unless":							{"$exists": "accepted"}				// Optional, possible to delete response, unless this query on the response evaluates to true.
//		"update_parent_on_delete_if_child":			[[{"$exists":"accepted"},			// Optional, run this update query on parent.
//													{"$unset": {"has_accepted": 1}}], ...]
//
// }
//
// If the extract module could convert a string to bson.ObjectId we would not need this.
//
// Checks if parent exists. Returns error if not.
// Inserts new content with the id of the parent.
// Increments field named "counter_fieldname" in parent.
func RespondContent(db *mgo.Database, user, action map[string]interface{}, inp map[string][]string, response_content_type_options map[string]interface{}) error {
	parent_fieldname := action["parent_fieldname"].(string)
	counter_fieldname := action["counter_fieldname"].(string)
	rcto := response_content_type_options
	parent, has_parent := inp["response_parent"]
	if !has_parent {
		return fmt.Errorf("No parent given.")
	}
	parent_id := patterns.ToIdWithCare(parent)
	q := m{"_id": parent_id}
	count, err := db.C("contents").Find(q).Count()
	if err != nil {
		return err
	}
	if count == 0 {
		return fmt.Errorf("Can't find parent with id %v.", parent_id)
	}
	fixval := m{parent_fieldname: parent_id}
	_, err = content_model.InsertWithFix(db, nil, rcto, inp, user["_id"].(bson.ObjectId), fixval)
	if err != nil {
		return err
	}
	upd := m{
		"$inc": m{
			counter_fieldname: 1,
		},
	}
	return db.C("contents").Update(q, upd)
}
Esempio n. 9
0
// You don't actually edit anything on a past version...
func (v *V) editVersion(typ, id string) (interface{}, error) {
	uni := v.uni
	uni.Dat["is_version"] = true
	version_id := patterns.ToIdWithCare(id)
	version, err := content_model.FindVersion(uni.Db, version_id)
	if err != nil {
		return nil, err
	}
	resolver.ResolveOne(uni.Db, version, nil)
	timeline, err := content_model.DraftTimeline(uni.Db, version_id)
	if err != nil {
		return nil, err
	}
	uni.Dat["timeline"] = timeline
	uni.Dat["op"] = "update"
	uni.Dat["content"] = version
	return nil, nil
}
Esempio n. 10
0
// Vote options are mutually exclusive.
// Example:
// {
//		"type": 		"vote",
//		"c": 			"contents",
//		"doc_type":		"blog",					// Optional
//		"can_unvote":	true,					// Optional, handled as false if unset or set and false.
//		"vote_options": ["like", "dislike"]
// }
//
// Checks if the vote option sent from UI is legal.
// Makes sure user have not voted yet, pushes user_id into a field named equal to a member of "vote_options", and increments a field named equal to a member of "vote_options" + "_count".
func Vote(db *mgo.Database, user, action map[string]interface{}, inp map[string][]string) error {
	doc_type, has_dt := action["doc_typ"]
	dat, collection, vote_options, input_vote, err := sharedProc(action, inp)
	if err != nil {
		return err
	}
	doc_id := patterns.ToIdWithCare(dat["document_id"].(string))
	user_id := user["_id"].(bson.ObjectId)
	q := generateVoteQuery(doc_id, user_id, vote_options)
	if has_dt {
		q["type"] = doc_type.(string)
	}
	upd := m{
		"$addToSet": m{
			input_vote: user_id,
		},
		"$inc": m{
			input_vote + "_count": 1,
		},
	}
	return db.C(collection).Update(q, upd)
}
Esempio n. 11
0
// We must somehow forbid the deletion of a given ContentResponse, because we have to do these actions at deletion too.
// TODO: we have to find a solution to this question.
// Update has no problems, no additional processing needs to be done when updating some fields like title, content, etc.
// See https://github.com/opesun/hypecms/issues/29
// (Or operate with events/hooks?)
//
// Checks if it is possible to delete a response.
// Checks if the response satisfies the "delete_unless" query, returns error if it does.
// Deletes response.
// Decrements parent "counter_fieldname" field by one.
// Does "update_parent_on_delete_if_child" optional operation.
func DeleteContentResponse(db *mgo.Database, user, action map[string]interface{}, inp map[string][]string) error {
	can_delete := false
	if can_delete_i, has := action["can_delete"]; has {
		can_delete = can_delete_i.(bool)
	}
	if !can_delete {
		return fmt.Errorf("Can't delete response.")
	}
	response_id := patterns.ToIdWithCare(inp["respone_id"][0])
	delete_unless, has_du := action["delete_unless"]
	if has_du {
		sat, err := patterns.Satisfies(db, "contents", response_id, delete_unless.(map[string]interface{}))
		if err != nil {
			return err
		}
		if sat {
			return fmt.Errorf("Can't delete response, satisfies \"unless\" query.")
		}
	}
	//counter_fieldname 	:= action["counter_fieldname"].(string)
	//parent_update, has_pu := action["update_parent"]
	return nil
}
Esempio n. 12
0
// Gets chosen document to find out parent.
// Gets parent document, checks if user is the author of it. Return error if not.
// Unsets choose_fieldname in chosen document.
// Decreases "has_" + choose_fieldname by 1 in parent document.
func UnchooseChild(db *mgo.Database, user, action map[string]interface{}, inp map[string][]string) error {
	choose_fieldname := action["choose_fieldname"].(string)
	parent_fieldname := action["parent_fieldname"].(string)
	coll := action["c"].(string)
	return nil
	rule := m{
		"chosen_doc_id": "must",
	}
	dat, err := extract.New(rule).Extract(inp)
	if err != nil {
		return err
	}
	user_id := user["_id"].(bson.ObjectId)
	chosen_doc_id := patterns.ToIdWithCare(dat["chosen_doc_id"])
	parent_id, auth_err := IsAuthor(db, coll, parent_fieldname, user_id, chosen_doc_id)
	if auth_err != nil {
		return auth_err
	}
	err = UnmarkAsChosen(db, coll, choose_fieldname, chosen_doc_id)
	if err != nil {
		return err
	}
	return IncrementParent(db, coll, choose_fieldname, parent_id, -1)
}
Esempio n. 13
0
func PullTagFromAll(db *mgo.Database, tag_id string) error {
	return patterns.PullFromAll(db, Tag_cname, Tag_fieldname, patterns.ToIdWithCare(tag_id))
}