More things
This commit is contained in:
@@ -44,8 +44,37 @@ const articleSchema = new mongoose.Schema(
|
||||
{ timestamps: true }
|
||||
);
|
||||
|
||||
// Text index for search across title and tags
|
||||
articleSchema.index({ title: "text", tags: "text" });
|
||||
/* Text index for search across title and tags.
|
||||
`language` means something else to MongoDB: a text index reads that field as
|
||||
the language to analyse the document in, and refuses to write a document
|
||||
whose value it does not recognise — which rules out Catalan, Polish, Korean,
|
||||
Arabic and the rest of the codes this wiki writes articles in. Pointing the
|
||||
override at a field no article carries keeps the wiki's own `language` out
|
||||
of the index's way, so an article in any language saves. */
|
||||
articleSchema.index({ title: "text", tags: "text" }, { language_override: "articleLanguage" });
|
||||
|
||||
/* An index's options cannot be changed in place — MongoDB answers the second
|
||||
request with "an equivalent index already exists" — so a database built
|
||||
before the override above carries the old text index, whose writes still
|
||||
fail. Drop those and let the schema's own indexes be rebuilt. Answers the
|
||||
names it removed: nothing at all when the database is already up to date. */
|
||||
articleSchema.statics.replaceReservedLanguageIndex = async function replaceReservedLanguageIndex() {
|
||||
let existing;
|
||||
try {
|
||||
existing = await this.collection.indexes();
|
||||
} catch (err) {
|
||||
// a database with no articles collection yet has no index to replace, and
|
||||
// asking for its list is MongoDB's `ns does not exist` — nothing to do here
|
||||
if (err.codeName === "NamespaceNotFound" || err.code === 26) return [];
|
||||
throw err;
|
||||
}
|
||||
const stale = existing.filter(
|
||||
(index) => index.key?._fts === "text" && (index.language_override ?? "language") === "language"
|
||||
);
|
||||
for (const index of stale) await this.collection.dropIndex(index.name);
|
||||
if (stale.length) await this.createIndexes();
|
||||
return stale.map((index) => index.name);
|
||||
};
|
||||
|
||||
const Article = mongoose.model("Article", articleSchema);
|
||||
|
||||
|
||||
87
backend/src/models/EditProposal.js
Normal file
87
backend/src/models/EditProposal.js
Normal file
@@ -0,0 +1,87 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user