import mongoose from "mongoose"; export const PROPOSAL_STATUSES = ["pending", "approved", "rejected"]; /* When a member saves the wiki the change does not land on it: it is held here as a request for an admin to approve or reject. A request changes a page that exists (`kind: "edit"`), or asks for one that does not yet (`kind: "create"`) — an article, or a new language version of an existing one — and carries no article until an admin's approval writes it. One pending request per article and account — saving again while one waits revises it rather than stacking a second copy, and the same draft a create editor holds revises itself the same way. The account's name is kept beside its id so a request stays readable after the account itself is closed. */ export const PROPOSAL_KINDS = ["edit", "create"]; const editProposalSchema = new mongoose.Schema( { // Whether the request rewrites a page or asks for a new one to exist. kind: { type: String, enum: { values: PROPOSAL_KINDS, message: `Kind is one of: ${PROPOSAL_KINDS.join(", ")}.`, }, default: "edit", }, // The document the request wants to change, by id rather than slug: a // retitle moves the slug out from under it. Null while the page is only // being asked for; approval fills it in, so a decided request still names // the article it brought about. article: { type: mongoose.Schema.Types.ObjectId, ref: "Article", default: null, index: true, }, // What a create asks the new page to be: the address it wants (the title // slugified, or `-` for a translation — the address is // what tells one draft from another while both wait), and the identity of // the article it joins as a version, if it translates one. slug: { type: String, default: null, lowercase: true, trim: true }, baseSlug: { type: String, default: null, lowercase: true, trim: true }, // The text the editor was looking at — the other half of every diff on the // dashboard, and how a reviewer spots an article that moved underneath. // Empty for a creation: the page it asks for has nothing written on it. base: { title: { type: String, default: "" }, content: { type: String, default: "" }, tags: { type: [String], default: [] }, }, // The whole state being proposed, so approving is one application, not a patch title: { type: String, required: [true, "Title is required"], trim: true, }, content: { type: String, default: "" }, tags: { type: [String], default: [] }, language: { type: String, default: "en", lowercase: true, trim: true }, status: { type: String, enum: { values: PROPOSAL_STATUSES, message: `Status is one of: ${PROPOSAL_STATUSES.join(", ")}.`, }, default: "pending", index: true, }, createdBy: { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true }, createdByUsername: { type: String, default: "" }, decidedBy: { type: mongoose.Schema.Types.ObjectId, ref: "User", default: null }, decidedByUsername: { type: String, default: "" }, decidedAt: { type: Date, default: null }, }, { timestamps: true } ); // who has what waiting on which article — the filter the upsert saves through editProposalSchema.index({ article: 1, createdBy: 1, status: 1 }); // ... and which address, for a request that names no article yet: the slug it // asks for tells one member's waiting drafts apart from another's editProposalSchema.index({ createdBy: 1, status: 1, kind: 1, slug: 1 }); const EditProposal = mongoose.model("EditProposal", editProposalSchema); export default EditProposal;