88 lines
3.7 KiB
JavaScript
88 lines
3.7 KiB
JavaScript
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 `<identity>-<language>` 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;
|