// Switches from one template to another. // Fails if the template we want to switch does not exist. func SwitchToTemplate(db *mgo.Database, inp map[string][]string, root, host string) error { rule := map[string]interface{}{ "template_name": "must", "template_type": "must", } dat, e_err := extract.New(rule).Extract(inp) if e_err != nil { return e_err } template_type := dat["template_type"].(string) template_name := dat["template_name"].(string) var e bool var err error if template_type == "public" { e, err = Exists(filepath.Join(root, "templates/public", template_name)) } else { e, err = Exists(filepath.Join(root, "templates/private", host, template_name)) } if err != nil { return fmt.Errorf("Can't determine if template exists.") } if !e { return fmt.Errorf("%v template named %v does not exist.", template_type, template_name) } return switchToTemplateDb(db, template_type, template_name) }
func insert(db *mgo.Database, ev ifaces.Event, rule map[string]interface{}, dat map[string][]string, user_id bson.ObjectId, fixvals map[string]interface{}) (bson.ObjectId, error) { // Could check for id here, alert if we found one. rule["type"] = "must" rule["draft_id"] = "must" // Can be draft, or version. ins_dat, extr_err := extract.New(rule).Extract(dat) if extr_err != nil { return "", extr_err } typ := ins_dat["type"].(string) basic.DateAndAuthor(rule, ins_dat, user_id, false) _, has_tags := ins_dat[Tag_fieldname_displayed] if has_tags { addTags(db, ins_dat, "", "insert", typ) } basic.Slug(rule, ins_dat) mergeMaps(ins_dat, fixvals) err := basic.InudVersion(db, ev, ins_dat, "contents", "insert", "") if err != nil { return "", err } ret_id := ins_dat["_id"].(bson.ObjectId) _, has_fulltext := rule["fulltext"] if has_fulltext { saveFulltext(db, ret_id) } return ret_id, nil }
// 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) }
// Only called when doing update or delete. // At inserting, user.OkayToDoAction is sufficient. // This needs additional action: you can only update or delete a given comment only if it's yours (under a certain level). func CanModifyComment(db *mgo.Database, inp map[string][]string, correction_level int, user_id bson.ObjectId, user_level int) error { rule := map[string]interface{}{ "content_id": "must", "comment_id": "must", "type": "must", } dat, err := extract.New(rule).Extract(inp) if err != nil { return err } // Even if he has the required level, and he is below correction_level, he can't modify other people's comment, only his owns. // So we query here the comment and check who is the owner of it. if user_level < correction_level { content_id_str := basic.StripId(dat["content_id"].(string)) comment_id_str := basic.StripId(dat["comment_id"].(string)) auth, err := findCommentAuthor(db, content_id_str, comment_id_str) if err != nil { return err } if auth.Hex() != user_id.Hex() { return fmt.Errorf("You are not the rightous owner of the comment.") } } return nil }
// Publish a private template, so others can use it too. // Copies the whole directory of /templates/private/{host}/{current_template} to /templates/public/{input:public_name} // Fails if a public template with the chosen name already exists. func PublishPrivate(db *mgo.Database, opt map[string]interface{}, inp map[string][]string, root, host string) error { if scut.TemplateType(opt) == "public" { return fmt.Errorf("You can't publish your current template, because it is already public.") } rule := map[string]interface{}{ "public_name": map[string]interface{}{ "must": 1, "type": "string", "min": 2, }, } dat, ex_err := extract.New(rule).Extract(inp) if ex_err != nil { return ex_err } public_name := dat["public_name"].(string) from := filepath.Join(root, "templates", "private", host, scut.TemplateName(opt)) to := filepath.Join(root, "templates", "public", public_name) // copyrecur.CopyDir checks for existence too, but for safety reasons we check here in case copyrecur semantics change. exis, exis_err := Exists(to) if exis { return fmt.Errorf("Public template with name " + public_name + " already exists.") } if exis_err != nil { return exis_err } copy_err := copyrecur.CopyDir(from, to) if copy_err != nil { return fmt.Errorf("There was an error while copying.") } return nil }
// Apart from rule, there are two mandatory field which must come from the UI: "content_id" and "comment_id" func UpdateComment(db *mgo.Database, ev ifaces.Event, rule map[string]interface{}, inp map[string][]string, user_id bson.ObjectId) error { dat, err := extract.New(rule).Extract(inp) if err != nil { return err } basic.DateAndAuthor(rule, dat, user_id, true) ids, err := basic.ExtractIds(inp, []string{"content_id", "comment_id"}) if err != nil { return err } comment_id := bson.ObjectIdHex(ids[1]) q := bson.M{ "_id": bson.ObjectIdHex(ids[0]), "comments.comment_id": comment_id, } upd := bson.M{ "$set": bson.M{ "comments.$": dat, }, } err = db.C("contents").Update(q, upd) if err != nil { return err } return db.C("comments").Remove(m{"_id": comment_id}) }
// Install and Uninstall hooks all have the same signature: func (a *A)(bson.ObjectId) error // InstallB handles both installing and uninstalling. func InstallB(uni *context.Uni, mode string) error { if !requireLev(uni.Dat["_user"], 300) { return fmt.Errorf("No rights to install or uninstall a module.") } dat, err := extract.New(map[string]interface{}{"module": "must"}).Extract(uni.Req.Form) if err != nil { return err } modn := dat["module"].(string) uni.Dat["_cont"] = map[string]interface{}{"module": modn} obj_id, ierr := admin_model.InstallB(uni.Db, uni.Ev, uni.Opt, modn, mode) if ierr != nil { return ierr } if !uni.Caller.Has("hooks", modn, strings.Title(mode)) { return fmt.Errorf("Module %v does not export the Hook %v.", modn, mode) } //hook, ok := h.(func(*context.Uni, bson.ObjectId) error) //if !ok { // return fmt.Errorf("%v hook of module %v has bad signature.", mode, modn) //} ret_rec := func(e error) { err = e } uni.Caller.Call("hooks", modn, strings.Title(mode), ret_rec, obj_id) return err }
func update(db *mgo.Database, ev ifaces.Event, rule map[string]interface{}, dat map[string][]string, user_id bson.ObjectId, fixvals map[string]interface{}) error { rule["id"] = "must" rule["type"] = "must" rule["draft_id"] = "must" upd_dat, extr_err := extract.New(rule).Extract(dat) if extr_err != nil { return extr_err } id := upd_dat["id"].(string) typ := upd_dat["type"].(string) basic.DateAndAuthor(rule, upd_dat, user_id, true) upd_dat["type"] = typ _, has_tags := upd_dat[Tag_fieldname_displayed] if has_tags { addTags(db, upd_dat, id, "update", typ) } basic.Slug(rule, upd_dat) mergeMaps(upd_dat, fixvals) err := basic.InudVersion(db, ev, upd_dat, Cname, "update", id) if err != nil { return err } _, has_fulltext := rule["fulltext"] id_bson := bson.ObjectIdHex(basic.StripId(id)) if has_fulltext { saveFulltext(db, id_bson) } return nil }
func SolveTimer(secret string, inp map[string][]string, puzzle_opts map[string]interface{}) error { min_diff, ok := puzzle_opts["min_diff"].(int) if !ok { min_diff = 10 } current := int(time.Now().Unix()) r := map[string]interface{}{ "__t": "must", } dat, err := extract.New(r).Extract(inp) if err != nil { return err } decrypted_v, err := decryptStr([]byte(secret_salt+secret), dat["__t"].(string)) if err != nil { return err } stamp, err := strconv.Atoi(decrypted_v) if err != nil { return err } if current-stamp < min_diff { return fmt.Errorf("You submitted the form too quickly, wait %v seconds please.", min_diff) } return nil }
// 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}) }
func igniteReadOps(session *mgo.Session, db *mgo.Database, boots_opt map[string]interface{}, inp map[string][]string) (map[string]interface{}, string, error) { if session == nil { return nil, "", fmt.Errorf("This is not an admin instance.") } r := newSiteRules() dat, err := extract.New(r).Extract(inp) if err != nil { return nil, "", err } // Need to check it here too, not just at admin_model.RegFirstAdmin! if dat["password"].(string) != dat["password_again"].(string) { return nil, "", fmt.Errorf("Passwords do not match.") } max_cap := numcon.IntP(boots_opt["max_cap"]) hasroom, err := hasRoom(db, max_cap) if err != nil { return nil, "", err } if !hasroom { return nil, "", fmt.Errorf("Server is at full capacity.") } sitename := dat["sitename"].(string) root_db := boots_opt["root_db"].(string) if sitename == root_db || strings.HasPrefix(sitename, "www") { return nil, "", fmt.Errorf("Sitename cant equal to root sitename or start with www.") } default_must := boots_opt["default_must"].(bool) def_opts, err := defaultOpts(db) if default_must && err != nil { return nil, "", fmt.Errorf("Cant read default option document.") } return def_opts, sitename, sitenameIsOk(db, sitename) }
// Delete a file OR dir. See NewFile for controversy about filenames and extensions. func DeleteFile(opt map[string]interface{}, inp map[string][]string, root, host string) error { if !CanModifyTemplate(opt) { return fmt.Errorf(cant_mod_public) } rule := map[string]interface{}{ "filepath": "must", } dat, e_err := extract.New(rule).Extract(inp) if e_err != nil { return e_err } fp := dat["filepath"].(string) full_p := filepath.Join(root, scut.GetTPath(opt, host), fp) var err error if IsDir(fp) { err = os.RemoveAll(full_p) } else { err = os.Remove(full_p) } if err != nil { fmt.Println(err) return fmt.Errorf("Can't delete file or directory. Probably it does not exist.") } return nil }
// Fork current private template into an other private one. // Copies the whole directory from /templates/private/{host}/{current_template} to /templates/private/{host}/{inp:new_private_name} func ForkPrivate(db *mgo.Database, opt map[string]interface{}, inp map[string][]string, root, host string) error { if scut.TemplateType(opt) != "private" { return fmt.Errorf("Your current template is not a private one.") // Kinda unsensical error message but ok... } rule := map[string]interface{}{ "new_template_name": "must", } dat, e_err := extract.New(rule).Extract(inp) if e_err != nil { return e_err } new_template_name := dat["new_template_name"].(string) to := filepath.Join(root, "templates", "private", host, new_template_name) e, e_err := Exists(to) if e_err != nil { return fmt.Errorf("Can't determine if private template exists.") } if e { return fmt.Errorf("Private template named %v already exists.", new_template_name) } from := filepath.Join(root, "templates", "private", host, scut.TemplateName(opt)) copy_err := copyrecur.CopyDir(from, to) if copy_err != nil { return fmt.Errorf("There was an error while copying.") } id := basic.CreateOptCopy(db) q := m{"_id": id} upd := m{ "$set": m{ "Template": new_template_name, }, } return db.C("options").Update(q, upd) }
// 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 }
// Currently we wonly delete a site from the sites collection of the root database, for safety reasons. // The deletion will only take effect at next restart. func DeleteSite(db *mgo.Database, inp map[string][]string) error { r := map[string]interface{}{ "sitename": "must", } dat, err := extract.New(r).Extract(inp) if err != nil { return err } return deleteSite(db, dat["sitename"].(string)) }
// MoveToFinal with extract. func MoveToFinalWE(db *mgo.Database, inp map[string][]string) error { r := map[string]interface{}{ "comment_id": "must", } dat, err := extract.New(r).Extract(inp) if err != nil { return err } comment_id := basic.ToIdWithCare(dat["comment_id"]) return MoveToFinal(db, comment_id) }
func FromWeb(uni *context.Uni) error { dat, err := extract.New(map[string]interface{}{"commands": "must"}).Extract(uni.Req.Form) if err != nil { return err } res, err := Run(uni, dat["commands"].(string)) if err != nil { return err } uni.Dat["_cont"] = map[string]interface{}{"output": res} return nil }
func SaveTypeConfig(db *mgo.Database, inp map[string][]string) error { rule := map[string]interface{}{ "type": "must", "safe_delete": "must", } _, err := extract.New(rule).Extract(inp) // _ = dat if err != nil { return err } // TODO: finish. return nil }
// Save an existing file. func SaveFile(opt map[string]interface{}, inp map[string][]string, root, host string) error { if !CanModifyTemplate(opt) { return fmt.Errorf(cant_mod_public) } rule := map[string]interface{}{ "filepath": "must", "content": "must", } dat, e_err := extract.New(rule).Extract(inp) if e_err != nil { return e_err } fp := dat["filepath"].(string) content := dat["content"].(string) return ioutil.WriteFile(filepath.Join(root, scut.GetTPath(opt, host), fp), []byte(content), os.ModePerm) }
// Everyone uses this to log in, admins, users, guest users and their mom. func FindLogin(db *mgo.Database, inp map[string][]string) (map[string]interface{}, bson.ObjectId, error) { rule := map[string]interface{}{ "name": "must", "password": "******", } d, err := extract.New(rule).Extract(inp) if err != nil { return nil, "", err } name := d["name"].(string) pass := EncodePass(d["password"].(string)) user, err := namePass(db, name, pass) if err != nil { return nil, "", err } return user, user["_id"].(bson.ObjectId), nil }
// Most return values ever. func sharedProc(action map[string]interface{}, inp map[string][]string) (map[string]interface{}, string, []string, string, error) { collection := action["c"].(string) vote_options := jsonp.ToStringSlice(action["vote_options"].([]interface{})) rules := map[string]interface{}{ "document_id": "must", "vote_option": "must", } dat, err := extract.New(rules).Extract(inp) if err != nil { return nil, "", nil, "", err } input_vote := dat["vote_option"].(string) if !isLegalOption(vote_options, input_vote) { return nil, "", nil, "", fmt.Errorf("Not a legal option.") } return dat, collection, vote_options, input_vote, nil }
// Delete a whole private template. func DeletePrivate(opt map[string]interface{}, inp map[string][]string, root, host string) error { rule := map[string]interface{}{ "template_name": "must", } dat, e_err := extract.New(rule).Extract(inp) if e_err != nil { return e_err } template_name := dat["template_name"].(string) if template_name == scut.TemplateName(opt) { return fmt.Errorf("For safety reasons you can only delete private templates not in use.") } full_p := filepath.Join(root, "templates", "private", host, template_name) err := os.RemoveAll(full_p) if err != nil { return fmt.Errorf("Can't delete private template named %v. It probably does not exist.", template_name) } return nil }
// mode: 1 first admin of site, named "admin" // mode: 2 admin with a name // mode: 3 a generic user // Database must have a unique index on slugs to avoid user slug duplications. func regUser(db *mgo.Database, post map[string][]string, mode int) error { subr := map[string]interface{}{ "must": 1, "type": "string", "min": 1, } rule := map[string]interface{}{ "password": subr, "password_again": subr, } if mode != first_admin { rule["name"] = subr } dat, err := extract.New(rule).Extract(post) if err != nil { return err } pass := dat["password"].(string) pass_again := dat["password_again"].(string) if pass != pass_again { return fmt.Errorf("Password and password confirmation differs.") } a := map[string]interface{}{"password": user_model.EncodePass(pass)} switch mode { // Redundant in places for better readability. case first_admin: a["name"] = "admin" a["level"] = 300 case admin_with_name: a["name"] = dat["name"] a["level"] = 300 case generic_user: a["name"] = dat["name"] a["level"] = 100 } err = db.C("users").Insert(a) if err != nil { return fmt.Errorf("Name is not unique.") } return nil }
// Quickly register someone when he does an action as a guest. // guest_rules can be nil. func RegisterGuest(db *mgo.Database, ev ifaces.Event, guest_rules map[string]interface{}, inp map[string][]string, solved_puzzle bool) (bson.ObjectId, error) { if guest_rules == nil { guest_rules = map[string]interface{}{} guestDefaults(guest_rules) } user, err := extract.New(guest_rules).Extract(inp) if err != nil { return "", err } if solved_puzzle { user["level"] = 2 } else { user["level"] = 1 } user_id := bson.NewObjectId() user["_id"] = user_id err = db.C("users").Insert(user) if err != nil { return "", fmt.Errorf("Name is not unique.") // Not true, errors can occur for other reasons too. } return user_id, nil }
// Registers a normal user with password and level 100. // See RegisterGuest for an other kind of registration. // See admin_model for registrations of admins. func RegisterUser(db *mgo.Database, ev ifaces.Event, rules map[string]interface{}, inp map[string][]string) (bson.ObjectId, error) { userDefaults(rules) user, err := extract.New(rules).Extract(inp) if err != nil { return "", err } if user["password"].(string) != user["password_again"].(string) { return "", fmt.Errorf("Password and password confirmation differs.") } delete(user, "password_again") user["password"] = EncodePass(user["password"].(string)) user["slug"] = slugify.S(user["slug"].(string)) user["level"] = 100 user_id := bson.NewObjectId() user["_id"] = user_id err = db.C("users").Insert(user) if err != nil { return "", fmt.Errorf("Name is not unique.") } delete(user, "password") ev.Trigger("user.register", user) return user_id, nil }
// Returns true if one is entitled to modify the given content. func CanModifyContent(db *mgo.Database, inp map[string][]string, correction_level int, user_id bson.ObjectId, user_level int) error { rule := map[string]interface{}{ "id": "must", } dat, err := extract.New(rule).Extract(inp) if err != nil { return err } if user_level < correction_level { content := find(db, dat["id"].(string)) if content == nil { return fmt.Errorf("Can't find content.") } auth, err := contentAuthor(content) if err != nil { return err } if auth.Hex() != user_id.Hex() { return fmt.Errorf("You can not modify this type of content if it is not yours.") } } return nil }
// 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) }
// Apart from rule, there is one mandatory field which must come from the UI: "content_id" // moderate_first should be read as "moderate first if it is a valid, spam protection passed comment" // Spam protection happens outside of this anyway. func InsertComment(db *mgo.Database, ev ifaces.Event, rule map[string]interface{}, inp map[string][]string, user_id bson.ObjectId, typ string, moderate_first bool) error { dat, err := extract.New(rule).Extract(inp) if err != nil { return err } dat["type"] = typ basic.DateAndAuthor(rule, dat, user_id, false) ids, err := basic.ExtractIds(inp, []string{"content_id"}) if err != nil { return err } content_id := bson.ObjectIdHex(ids[0]) comment_id := bson.NewObjectId() if moderate_first { err = insertModeration(db, dat, comment_id, content_id, typ) } else { err = insertToFinal(db, dat, comment_id, content_id) } // This will be made optional, a facebook style app does not need it, only a bloglike site. if err == nil { err = insertToVirtual(db, content_id, comment_id, user_id, typ, moderate_first) } return err }