appwrite-server-wrapper
Version:
Wrapper library to handle Appwrite methods including server handling using SSR with NextJS v15 (useActionState, useAction,...)
1,255 lines • 67.4 kB
JavaScript
"use server";
import { getSchema, attributesEqual, createAttribute, updateAttribute,
//getAttributeFromKey,
} from "../collections";
import { handleApwError } from "../exceptions";
import { createAdminClient } from "../appwriteClients";
import { ID, Query, } from "node-appwrite";
import { databaseId, usersCollectionId } from "../appwriteConfig";
import { generateMigrationId, toLogs } from "../ssr-utils";
import { isCollectionSchema, schemaToFile } from "../collections/schema";
const createBooleanAttribute = async ({ ...args }) => {
try {
const { databases } = await createAdminClient();
// Use provided databaseId/collectionId if available; otherwise use defaults.
const finalDatabaseId = args.databaseId ?? databaseId;
const finalCollectionId = args.collectionId ?? usersCollectionId;
const newArgs = {
...args,
databaseId: finalDatabaseId,
collectionId: finalCollectionId,
};
const createBooleanAttributeParams = [
newArgs.databaseId,
newArgs.collectionId,
newArgs.key,
newArgs.required,
newArgs.xdefault,
newArgs.array,
];
const data = await databases.createBooleanAttribute(...createBooleanAttributeParams);
return { data, error: null };
}
catch (error) {
return {
data: null,
error: await handleApwError({ error }),
};
}
};
const createCollection = async ({ ...args }) => {
try {
const { databases } = await createAdminClient();
// Use provided databaseId/collectionId if available; otherwise use defaults.
const finalDatabaseId = args.databaseId ?? databaseId;
const finalCollectionId = args.collectionId ?? ID.unique();
const newArgs = {
...args,
databaseId: finalDatabaseId,
collectionId: finalCollectionId,
};
const createCollectionParams = [
newArgs.databaseId,
newArgs.collectionId,
newArgs.name,
newArgs.permissions,
newArgs.documentSecurity,
newArgs.enabled,
];
const data = await databases.createCollection(...createCollectionParams);
return { data, error: null };
}
catch (error) {
return {
data: null,
error: await handleApwError({ error }),
};
}
};
const createCollectionWithSchema = async (args) => {
// Use provided databaseId/collectionId if available; otherwise use defaults.
const finalDatabaseId = args.databaseId ?? databaseId;
const finalCollectionId = args.collectionId ?? ID.unique();
// Initialize a log object.
const logTopic = "migration";
const logDetails = "schemaCreate";
const logContent = {
id: await generateMigrationId(args.name),
executed_at: new Date().toISOString(),
status: "success",
databaseId: finalDatabaseId,
collectionId: finalCollectionId,
changes: [],
};
try {
const { databases } = await createAdminClient();
const newArgs = {
...args,
databaseId: finalDatabaseId,
collectionId: finalCollectionId,
};
logContent.changes.push({
action: "listCollections",
information: `Listing collections in database '${newArgs.databaseId}'.`,
});
const collList = await databases.listCollections(newArgs.databaseId);
let coll = collList.collections.find((collection) => collection.name === newArgs.name);
if (coll) {
logContent.changes.push({
action: "listCollections",
information: `Collection '${newArgs.name}' already exists.`,
});
throw new Error(`Collection '${newArgs.name}' already exists`);
}
else {
logContent.changes.push({
action: "listCollections",
information: `Collection '${newArgs.name}' not found; proceeding to create.`,
});
const schema = await getSchema(newArgs.name, logContent);
if (!schema || !isCollectionSchema(schema)) {
logContent.changes.push({
action: "getSchema",
information: `No valid schema found for collection '${newArgs.name}'.`,
});
throw new Error(`No schema found for collection '${newArgs.name}'`);
}
if (!schema.attributes || schema.attributes.length < 1) {
logContent.changes.push({
action: "getSchema",
information: `No attributes found in schema '${schema.collectionName}'.`,
});
throw new Error(`No attributes found in schema for collection '${newArgs.name}'`);
}
logContent.changes.push({
action: "getSchema",
information: `Schema '${schema.collectionName}' loaded for collection creation.`,
});
coll = await databases.createCollection(newArgs.databaseId, newArgs.collectionId, schema.collectionName, schema.permissions, schema.documentSecurity, schema.enabled);
logContent.changes.push({
action: "createCollection",
information: `Collection '${schema.collectionName}' created.`,
});
for (const attr of schema.attributes) {
logContent.changes.push({
action: "createAttribute",
information: `Creating attribute '${attr.key}'.`,
});
// Validate that xdefault is not defined if required is set to true
const hasRequiredTrue = "required" in attr && attr.required === true;
const hasXdefault = attr.type !== "relationship" &&
"xdefault" in attr &&
attr.xdefault !== undefined;
if (hasRequiredTrue && hasXdefault) {
const msg = `Not able to create Attribute '${attr.key}' for Collection '${newArgs.name}' (id: '${newArgs.collectionId}'), because both 'xdefault' and 'required: true' are set. Appwrite does not allow xdefault to be defined while required is true.`;
logContent.changes.push({
action: "createAttribute",
information: msg,
});
throw new Error(msg);
}
// Check relationshipAttributeData prop
if (attr.key === "relationship") {
if (newArgs.relationshipAttributeData?.length) {
newArgs.relationshipAttributeData.map((relAttr) => {
if (!relAttr.relationshipKey.length ||
!relAttr.relatedCollectionId.length) {
logContent.changes.push({
action: "createAttribute (relationship)",
information: `Not able to create Attribute '${newArgs.name}' (id: '${newArgs.collectionId}'), since relationship information for key '${attr.key}' is missing (relationshipKey or relatedCollectionId or both, passed with param 'relationshipAttributeData' are are not valid)`,
});
throw new Error(`Not able to create Attribute '${newArgs.name}' (id: '${newArgs.collectionId}'), since relationship information for key '${attr.key}' is missing (relationshipKey or relatedCollectionId or both, passed with param 'relationshipAttributeData' are are not valid)`);
}
});
}
else {
logContent.changes.push({
action: "createAttribute (relationship)",
information: `Not able to create Attribute '${newArgs.name}' (id: '${newArgs.collectionId}'), since relationship information for key '${attr.key}' is missing (relationshipAttributeData)`,
});
throw new Error(`Not able to create Attribute '${newArgs.name}' (id: '${newArgs.collectionId}'), since relationship information for key '${attr.key}' is missing (relationshipAttributeData)`);
}
}
try {
await createAttribute(newArgs.databaseId, newArgs.collectionId, attr, newArgs.relationshipAttributeData?.find((relAttr) => relAttr.relationshipKey === attr.key)?.relatedCollectionId);
logContent.changes.push({
action: "createAttribute",
information: `Attribute '${attr.key}' created.`,
});
}
catch (error) {
throw new Error(`Couldn't create attribute '${attr.key}': ${error}`);
}
}
for (const index of schema.indexes) {
logContent.changes.push({
action: "createIndex",
information: `Creating index '${index.key}' of type '${index.type}'.`,
});
try {
await databases.createIndex(newArgs.databaseId, newArgs.collectionId, index.key, index.type, index.attributes, index.orders);
logContent.changes.push({
action: "createIndex",
information: `Index '${index.key}' created.`,
});
}
catch (error) {
throw new Error(`Couldn't create index '${index.key}': ${error}`);
}
}
logContent.executed_at = new Date().toISOString();
logContent.status = "success";
await toLogs(logTopic, logDetails, logContent);
return { data: coll, error: null };
}
}
catch (error) {
logContent.executed_at = new Date().toISOString();
logContent.status = "failure";
await toLogs(logTopic, logDetails, logContent);
return {
data: null,
error: await handleApwError({ error }),
};
}
};
const createDatabase = async ({ ...args }) => {
try {
const { databases } = await createAdminClient();
// Use provided databaseId/collectionId if available; otherwise use defaults.
const finalDatabaseId = args.databaseId ?? databaseId;
const newArgs = {
...args,
databaseId: finalDatabaseId,
};
const createDatabaseParams = [
newArgs.databaseId,
newArgs.name,
newArgs.enabled,
];
const data = await databases.create(...createDatabaseParams);
return { data, error: null };
}
catch (error) {
return {
data: null,
error: await handleApwError({ error }),
};
}
};
const createDatetimeAttribute = async ({ ...args }) => {
try {
const { databases } = await createAdminClient();
// Use provided databaseId/collectionId if available; otherwise use defaults.
const finalDatabaseId = args.databaseId ?? databaseId;
const finalCollectionId = args.collectionId ?? usersCollectionId;
const newArgs = {
...args,
databaseId: finalDatabaseId,
collectionId: finalCollectionId,
};
const createDatetimeAttributeParams = [
newArgs.databaseId,
newArgs.collectionId,
newArgs.key,
newArgs.required,
newArgs.xdefault,
newArgs.array,
];
const data = await databases.createDatetimeAttribute(...createDatetimeAttributeParams);
return { data, error: null };
}
catch (error) {
return {
data: null,
error: await handleApwError({ error }),
};
}
};
const createDocument = async ({ ...args }) => {
try {
const { databases } = await createAdminClient();
// Use provided databaseId/collectionId if available; otherwise use defaults.
const finalDatabaseId = args.databaseId ?? databaseId;
const finalCollectionId = args.collectionId ?? usersCollectionId;
const newArgs = {
...args,
databaseId: finalDatabaseId,
collectionId: finalCollectionId,
documentId: ID.unique(),
};
const createDocumentParams = [
newArgs.databaseId,
newArgs.collectionId,
newArgs.documentId,
newArgs.data,
newArgs.permissions,
];
const data = await databases.createDocument(...Object.values(newArgs));
return { data, error: null };
}
catch (error) {
return {
data: null,
error: await handleApwError({ error }),
};
}
};
const createEmailAttribute = async ({ ...args }) => {
try {
const { databases } = await createAdminClient();
// Use provided databaseId/collectionId if available; otherwise use defaults.
const finalDatabaseId = args.databaseId ?? databaseId;
const finalCollectionId = args.collectionId ?? usersCollectionId;
const newArgs = {
...args,
databaseId: finalDatabaseId,
collectionId: finalCollectionId,
};
const createEmailAttributeParams = [
newArgs.databaseId,
newArgs.collectionId,
newArgs.key,
newArgs.required,
newArgs.xdefault,
newArgs.array,
];
const data = await databases.createEmailAttribute(...createEmailAttributeParams);
return { data, error: null };
}
catch (error) {
return {
data: null,
error: await handleApwError({ error }),
};
}
};
const createEnumAttribute = async ({ ...args }) => {
try {
const { databases } = await createAdminClient();
// Use provided databaseId/collectionId if available; otherwise use defaults.
const finalDatabaseId = args.databaseId ?? databaseId;
const finalCollectionId = args.collectionId ?? usersCollectionId;
const newArgs = {
...args,
databaseId: finalDatabaseId,
collectionId: finalCollectionId,
};
const createEnumAttributeParams = [
newArgs.databaseId,
newArgs.collectionId,
newArgs.key,
newArgs.elements,
newArgs.required,
newArgs.xdefault,
newArgs.array,
];
const data = await databases.createEnumAttribute(...createEnumAttributeParams);
return { data, error: null };
}
catch (error) {
return {
data: null,
error: await handleApwError({ error }),
};
}
};
const createFloatAttribute = async ({ ...args }) => {
try {
const { databases } = await createAdminClient();
// Use provided databaseId/collectionId if available; otherwise use defaults.
const finalDatabaseId = args.databaseId ?? databaseId;
const finalCollectionId = args.collectionId ?? usersCollectionId;
const newArgs = {
...args,
databaseId: finalDatabaseId,
collectionId: finalCollectionId,
};
const createFloatAttributeParams = [
newArgs.databaseId,
newArgs.collectionId,
newArgs.key,
newArgs.required,
newArgs.min,
newArgs.max,
newArgs.xdefault,
newArgs.array,
];
const data = await databases.createFloatAttribute(...createFloatAttributeParams);
return { data, error: null };
}
catch (error) {
return {
data: null,
error: await handleApwError({ error }),
};
}
};
const createIndex = async ({ ...args }) => {
try {
const { databases } = await createAdminClient();
// Use provided databaseId/collectionId if available; otherwise use defaults.
const finalDatabaseId = args.databaseId ?? databaseId;
const finalCollectionId = args.collectionId ?? usersCollectionId;
const newArgs = {
...args,
databaseId: finalDatabaseId,
collectionId: finalCollectionId,
};
const createIndexParams = [
newArgs.databaseId,
newArgs.collectionId,
newArgs.key,
newArgs.type,
newArgs.attributes,
newArgs.orders,
];
const data = await databases.createIndex(...createIndexParams);
return { data, error: null };
}
catch (error) {
return {
data: null,
error: await handleApwError({ error }),
};
}
};
const createIntegerAttribute = async ({ ...args }) => {
try {
const { databases } = await createAdminClient();
// Use provided databaseId/collectionId if available; otherwise use defaults.
const finalDatabaseId = args.databaseId ?? databaseId;
const finalCollectionId = args.collectionId ?? usersCollectionId;
const newArgs = {
...args,
databaseId: finalDatabaseId,
collectionId: finalCollectionId,
};
const createIntegerAttributeParams = [
newArgs.databaseId,
newArgs.collectionId,
newArgs.key,
newArgs.required,
newArgs.min,
newArgs.max,
newArgs.xdefault,
newArgs.array,
];
const data = await databases.createIntegerAttribute(...createIntegerAttributeParams);
return { data, error: null };
}
catch (error) {
return {
data: null,
error: await handleApwError({ error }),
};
}
};
const createIpAttribute = async ({ ...args }) => {
try {
const { databases } = await createAdminClient();
// Use provided databaseId/collectionId if available; otherwise use defaults.
const finalDatabaseId = args.databaseId ?? databaseId;
const finalCollectionId = args.collectionId ?? usersCollectionId;
const newArgs = {
...args,
databaseId: finalDatabaseId,
collectionId: finalCollectionId,
};
const createIpAttributeParams = [
newArgs.databaseId,
newArgs.collectionId,
newArgs.key,
newArgs.required,
newArgs.xdefault,
newArgs.array,
];
const data = await databases.createIpAttribute(...createIpAttributeParams);
return { data, error: null };
}
catch (error) {
return {
data: null,
error: await handleApwError({ error }),
};
}
};
const createRelationshipAttribute = async ({ ...args }) => {
try {
const { databases } = await createAdminClient();
// Use provided databaseId/collectionId if available; otherwise use defaults.
const finalDatabaseId = args.databaseId ?? databaseId;
const finalCollectionId = args.collectionId ?? usersCollectionId;
const newArgs = {
...args,
databaseId: finalDatabaseId,
collectionId: finalCollectionId,
};
const createRelationshipAttributeParams = [
newArgs.databaseId,
newArgs.collectionId,
newArgs.relatedCollectionId,
newArgs.type,
newArgs.twoWay,
newArgs.key,
newArgs.twoWayKey,
newArgs.onDelete,
];
const data = await databases.createRelationshipAttribute(...createRelationshipAttributeParams);
return { data, error: null };
}
catch (error) {
return {
data: null,
error: await handleApwError({ error }),
};
}
};
const createStringAttribute = async ({ ...args }) => {
try {
const { databases } = await createAdminClient();
// Use provided databaseId/collectionId if available; otherwise use defaults.
const finalDatabaseId = args.databaseId ?? databaseId;
const finalCollectionId = args.collectionId ?? usersCollectionId;
const newArgs = {
...args,
databaseId: finalDatabaseId,
collectionId: finalCollectionId,
};
const createStringAttributeParams = [
newArgs.databaseId,
newArgs.collectionId,
newArgs.key,
newArgs.size,
newArgs.required,
newArgs.xdefault,
newArgs.array,
newArgs.encrypt,
];
const data = await databases.createStringAttribute(...createStringAttributeParams);
return { data, error: null };
}
catch (error) {
return {
data: null,
error: await handleApwError({ error }),
};
}
};
const createUrlAttribute = async ({ ...args }) => {
try {
const { databases } = await createAdminClient();
// Use provided databaseId/collectionId if available; otherwise use defaults.
const finalDatabaseId = args.databaseId ?? databaseId;
const finalCollectionId = args.collectionId ?? usersCollectionId;
const newArgs = {
...args,
databaseId: finalDatabaseId,
collectionId: finalCollectionId,
};
const createUrlAttributeParams = [
newArgs.databaseId,
newArgs.collectionId,
newArgs.key,
newArgs.required,
newArgs.xdefault,
newArgs.array,
];
const data = await databases.createUrlAttribute(...createUrlAttributeParams);
return { data, error: null };
}
catch (error) {
return {
data: null,
error: await handleApwError({ error }),
};
}
};
const deleteAttribute = async ({ ...args }) => {
try {
const { databases } = await createAdminClient();
// Use provided databaseId/collectionId if available; otherwise use defaults.
const finalDatabaseId = args.databaseId ?? databaseId;
const finalCollectionId = args.collectionId ?? usersCollectionId;
const newArgs = {
...args,
databaseId: finalDatabaseId,
collectionId: finalCollectionId,
};
const deleteAttributeParams = [
newArgs.databaseId,
newArgs.collectionId,
newArgs.key,
];
const data = await databases.deleteAttribute(...deleteAttributeParams);
return { data, error: null };
}
catch (error) {
return {
data: null,
error: await handleApwError({ error }),
};
}
};
const deleteCollection = async ({ ...args }) => {
try {
const { databases } = await createAdminClient();
// Use provided databaseId/collectionId if available; otherwise use defaults.
const finalDatabaseId = args.databaseId ?? databaseId;
const newArgs = {
...args,
databaseId: finalDatabaseId,
};
const deleteCollectionParams = [
newArgs.databaseId,
newArgs.collectionId,
];
const data = await databases.deleteCollection(...deleteCollectionParams);
return { data, error: null };
}
catch (error) {
return {
data: null,
error: await handleApwError({ error }),
};
}
};
const deleteDatabase = async ({ ...args }) => {
try {
const { databases } = await createAdminClient();
const newArgs = {
...args,
};
const deleteDatabaseParams = [newArgs.databaseId];
const data = await databases.delete(...deleteDatabaseParams);
return { data, error: null };
}
catch (error) {
return {
data: null,
error: await handleApwError({ error }),
};
}
};
const deleteDocument = async ({ ...args }) => {
try {
const { databases } = await createAdminClient();
// Use provided databaseId/collectionId if available; otherwise use defaults.
const finalDatabaseId = args.databaseId ?? databaseId;
const finalCollectionId = args.collectionId ?? usersCollectionId;
const newArgs = {
...args,
databaseId: finalDatabaseId,
collectionId: finalCollectionId,
};
const deleteDocumentParams = [
newArgs.databaseId,
newArgs.collectionId,
newArgs.documentId,
];
const data = await databases.deleteDocument(...deleteDocumentParams);
return { data, error: null };
}
catch (error) {
return {
data: null,
error: await handleApwError({ error }),
};
}
};
const deleteIndex = async ({ ...args }) => {
try {
const { databases } = await createAdminClient();
// Use provided databaseId/collectionId if available; otherwise use defaults.
const finalDatabaseId = args.databaseId ?? databaseId;
const finalCollectionId = args.collectionId ?? usersCollectionId;
const newArgs = {
...args,
databaseId: finalDatabaseId,
collectionId: finalCollectionId,
};
const deleteIndexParams = [
newArgs.databaseId,
newArgs.collectionId,
newArgs.key,
];
const data = await databases.deleteIndex(...deleteIndexParams);
return { data, error: null };
}
catch (error) {
return {
data: null,
error: await handleApwError({ error }),
};
}
};
const getAttribute = async ({ ...args }) => {
try {
const { databases } = await createAdminClient();
// Use provided databaseId/collectionId if available; otherwise use defaults.
const finalDatabaseId = args.databaseId ?? databaseId;
const finalCollectionId = args.collectionId ?? usersCollectionId;
const newArgs = {
...args,
databaseId: finalDatabaseId,
collectionId: finalCollectionId,
};
const getAttributeParams = [
newArgs.databaseId,
newArgs.collectionId,
newArgs.key,
];
const data = await databases.getAttribute(...getAttributeParams);
return { data, error: null };
}
catch (error) {
return {
data: null,
error: await handleApwError({ error }),
};
}
};
const getCollection = async ({ ...args }) => {
try {
const { databases } = await createAdminClient();
// Use provided databaseId/collectionId if available; otherwise use defaults.
const finalDatabaseId = args.databaseId ?? databaseId;
const newArgs = {
...args,
databaseId: finalDatabaseId,
};
const getCollectionParams = [
newArgs.databaseId,
newArgs.collectionId,
];
const data = await databases.getCollection(...getCollectionParams);
return { data, error: null };
}
catch (error) {
return {
data: null,
error: await handleApwError({ error }),
};
}
};
const getDatabase = async ({ ...args }) => {
try {
const { databases } = await createAdminClient();
const newArgs = {
...args,
};
const getDatabaseParams = [newArgs.databaseId];
const data = await databases.get(...getDatabaseParams);
return { data, error: null };
}
catch (error) {
return {
data: null,
error: await handleApwError({ error }),
};
}
};
const getDocument = async ({ ...args }) => {
try {
const { databases } = await createAdminClient();
// Use provided databaseId/collectionId if available; otherwise use defaults.
const finalDatabaseId = args.databaseId ?? databaseId;
const finalCollectionId = args.collectionId ?? usersCollectionId;
const newArgs = {
...args,
databaseId: finalDatabaseId,
collectionId: finalCollectionId,
};
const getDocumentParams = [
newArgs.databaseId,
newArgs.collectionId,
newArgs.documentId,
newArgs.queries,
];
const data = await databases.getDocument(...getDocumentParams);
return { data, error: null };
}
catch (error) {
return {
data: null,
error: await handleApwError({ error }),
};
}
};
const getIndex = async ({ ...args }) => {
try {
const { databases } = await createAdminClient();
// Use provided databaseId/collectionId if available; otherwise use defaults.
const finalDatabaseId = args.databaseId ?? databaseId;
const finalCollectionId = args.collectionId ?? usersCollectionId;
const newArgs = {
...args,
databaseId: finalDatabaseId,
collectionId: finalCollectionId,
};
const getIndexParams = [
newArgs.databaseId,
newArgs.collectionId,
newArgs.key,
];
const data = await databases.getIndex(...getIndexParams);
return { data, error: null };
}
catch (error) {
return {
data: null,
error: await handleApwError({ error }),
};
}
};
const listAttributes = async ({ ...args }) => {
try {
const { databases } = await createAdminClient();
// Use provided databaseId/collectionId if available; otherwise use defaults.
const finalDatabaseId = args.databaseId ?? databaseId;
const finalCollectionId = args.collectionId ?? usersCollectionId;
const newArgs = {
...args,
databaseId: finalDatabaseId,
collectionId: finalCollectionId,
};
const listAttributesParams = [
newArgs.databaseId,
newArgs.collectionId,
newArgs.queries,
];
const data = await databases.listAttributes(...listAttributesParams);
return { data, error: null };
}
catch (error) {
return {
data: null,
error: await handleApwError({ error }),
};
}
};
const listCollections = async ({ ...args }) => {
try {
const { databases } = await createAdminClient();
// Use provided databaseId/collectionId if available; otherwise use defaults.
const finalDatabaseId = args.databaseId ?? databaseId;
const newArgs = {
...args,
databaseId: finalDatabaseId,
};
const listCollectionsParams = [
newArgs.databaseId,
newArgs.queries,
newArgs.search,
];
const data = await databases.listCollections(...listCollectionsParams);
return { data, error: null };
}
catch (error) {
return {
data: null,
error: await handleApwError({ error }),
};
}
};
const listDatabases = async ({ ...args }) => {
try {
const { databases } = await createAdminClient();
const newArgs = {
...args,
};
const listDatabasesParams = [
newArgs.queries,
newArgs.search,
];
const data = await databases.list(...listDatabasesParams);
return { data, error: null };
}
catch (error) {
return {
data: null,
error: await handleApwError({ error }),
};
}
};
const listDocuments = async (args) => {
try {
const { databases } = await createAdminClient();
const finalDatabaseId = args.databaseId ?? databaseId;
const finalCollectionId = args.collectionId ?? usersCollectionId;
const newArgs = {
...args,
databaseId: finalDatabaseId,
collectionId: finalCollectionId,
};
const listDocumentsParams = [
newArgs.databaseId,
newArgs.collectionId,
newArgs.queries,
];
const data = await databases.listDocuments(...listDocumentsParams);
// Defensive null check
if (!data || !Array.isArray(data.documents)) {
return {
data: { documents: [], total: 0 },
error: null,
};
}
let filteredDocuments = data.documents;
const getMatchingId = (val) => {
if (val === null || val === undefined)
return null;
if (typeof val === "object" && "$id" in val)
return val.$id;
if (typeof val === "string")
return val;
return null;
};
const matchesValue = (docVal, target) => {
if (Array.isArray(docVal)) {
return docVal.some((item) => getMatchingId(item) === target);
}
return getMatchingId(docVal) === target;
};
const matchesValues = (docVal, targets) => {
if (Array.isArray(docVal)) {
return docVal.some((item) => targets.includes(getMatchingId(item)));
}
const docId = getMatchingId(docVal);
return docId ? targets.includes(docId) : false;
};
if (newArgs.relationshipQueries?.length) {
filteredDocuments = data.documents.filter((doc) => {
return newArgs.relationshipQueries.every((query) => {
try {
const parsedQuery = JSON.parse(query);
const evaluateQuery = (parsedQuery, doc) => {
const { attribute, method, values } = parsedQuery;
switch (method) {
case "equal":
return matchesValue(doc[attribute], values[0]);
case "notEqual":
return !matchesValue(doc[attribute], values[0]);
case "lessThan":
return getMatchingId(doc[attribute]) < values[0];
case "lessThanEqual":
return getMatchingId(doc[attribute]) <= values[0];
case "greaterThan":
return getMatchingId(doc[attribute]) > values[0];
case "greaterThanEqual":
return getMatchingId(doc[attribute]) >= values[0];
case "search":
return getMatchingId(doc[attribute])
.toLowerCase()
.includes(values[0].toLowerCase());
case "isIn":
return matchesValues(doc[attribute], values);
case "isNotIn":
return !matchesValues(doc[attribute], values);
case "contains":
return Array.isArray(values)
? values.some((val) => matchesValue(doc[attribute], val))
: false;
case "between":
const id = getMatchingId(doc[attribute]);
return id !== null && id >= values[0] && id <= values[1];
case "startsWith":
return String(getMatchingId(doc[attribute])).startsWith(String(values[0]));
case "endsWith":
return String(getMatchingId(doc[attribute])).endsWith(String(values[0]));
case "isNull":
return doc[attribute] === null;
case "isNotNull":
return doc[attribute] !== null;
case "or":
return values.some((subQuery) => evaluateQuery(subQuery, doc));
case "and":
return values.every((subQuery) => evaluateQuery(subQuery, doc));
default:
return false;
}
};
return evaluateQuery(parsedQuery, doc);
}
catch (e) {
console.error("Error parsing relationship query:", query, e);
return false;
}
});
});
}
return {
data: {
total: filteredDocuments.length,
documents: filteredDocuments,
},
error: null,
};
}
catch (error) {
return {
data: null,
error: await handleApwError({ error }),
};
}
};
const listIndexes = async ({ ...args }) => {
try {
const { databases } = await createAdminClient();
// Use provided databaseId/collectionId if available; otherwise use defaults.
const finalDatabaseId = args.databaseId ?? databaseId;
const finalCollectionId = args.collectionId ?? usersCollectionId;
const newArgs = {
...args,
databaseId: finalDatabaseId,
collectionId: finalCollectionId,
};
const listIndexesParams = [
newArgs.databaseId,
newArgs.collectionId,
newArgs.queries,
];
const data = await databases.listIndexes(...listIndexesParams);
return { data, error: null };
}
catch (error) {
return {
data: null,
error: await handleApwError({ error }),
};
}
};
const updateBooleanAttribute = async ({ ...args }) => {
try {
const { databases } = await createAdminClient();
// Use provided databaseId/collectionId if available; otherwise use defaults.
const finalDatabaseId = args.databaseId ?? databaseId;
const finalCollectionId = args.collectionId ?? usersCollectionId;
const newArgs = {
...args,
databaseId: finalDatabaseId,
collectionId: finalCollectionId,
};
const updateBooleanAttributeParams = [
newArgs.databaseId,
newArgs.collectionId,
newArgs.key,
newArgs.required,
newArgs.xdefault,
newArgs.newKey,
];
const data = await databases.updateBooleanAttribute(...updateBooleanAttributeParams);
return { data, error: null };
}
catch (error) {
return {
data: null,
error: await handleApwError({ error }),
};
}
};
const updateCollection = async ({ ...args }) => {
try {
const { databases } = await createAdminClient();
// Use provided databaseId/collectionId if available; otherwise use defaults.
const finalDatabaseId = args.databaseId ?? databaseId;
const newArgs = {
...args,
databaseId: finalDatabaseId,
};
const updateCollectionParams = [
newArgs.databaseId,
newArgs.collectionId,
newArgs.name,
newArgs.permissions,
newArgs.documentSecurity,
newArgs.enabled,
];
const collList = await databases.listCollections(newArgs.databaseId, [
Query.and([
Query.equal("name", newArgs.name),
Query.equal("$id", newArgs.collectionId),
]),
]);
if (collList.total < 1) {
throw new Error(`Collection with name: '${newArgs.name}' / id:'${newArgs.collectionId}' not found`);
}
if (collList.total > 1) {
throw new Error(`Collection with name: '${newArgs.name}' / id:'${newArgs.collectionId}' not unique, multiple collections with the same name and/or id found`);
}
const data = await databases.updateCollection(...updateCollectionParams);
return { data, error: null };
}
catch (error) {
return {
data: null,
error: await handleApwError({ error }),
};
}
};
const updateCollectionWithSchema = async (args) => {
// Use provided databaseId/collectionId if available; otherwise use defaults.
const finalDatabaseId = args.databaseId ?? databaseId;
const finalCollectionId = args.collectionId ?? ID.unique();
// Initialize a log object.
const logTopic = "migration";
const logDetails = "schemaUpdate";
const logContent = {
id: await generateMigrationId(args.name),
executed_at: new Date().toISOString(),
status: "success",
databaseId: finalDatabaseId,
collectionId: finalCollectionId,
changes: [],
};
try {
const { databases } = await createAdminClient();
const newArgs = {
...args,
databaseId: finalDatabaseId,
collectionId: finalCollectionId,
};
// List collections.
logContent.changes.push({
action: "listCollections",
information: `Listing collection '${newArgs.name}' (id: '${newArgs.collectionId}') in database '${newArgs.databaseId}'.`,
});
const collList = await databases.listCollections(newArgs.databaseId, [
Query.and([
Query.equal("name", newArgs.name),
Query.equal("$id", newArgs.collectionId),
]),
]);
if (collList.total < 1) {
logContent.changes.push({
action: "listCollections",
information: `Collection '${newArgs.name}' (id: '${newArgs.collectionId}') not found.`,
});
throw new Error(`Collection '${newArgs.name}' (id: '${newArgs.collectionId}') not found`);
}
if (collList.total > 1) {
logContent.changes.push({
action: "listCollections",
information: `Collection '${newArgs.name}' (id: '${newArgs.collectionId}') not unique.`,
});
throw new Error(`Collection '${newArgs.name}' (id: '${newArgs.collectionId}') not unique`);
}
logContent.changes.push({
action: "listCollections",
information: `Found collection '${newArgs.name}' (id: '${newArgs.collectionId}').`,
});
const currentCollection = collList.collections[0];
// Retrieve the schema.
const schema = await getSchema(newArgs.name, logContent);
if (!schema || !isCollectionSchema(schema)) {
logContent.changes.push({
action: "getSchema",
information: `No valid schema found for collection '${newArgs.name}'.`,
});
throw new Error(`No schema found for collection '${newArgs.name}'`);
}
// Now schema is of type CollectionSchema
if (!schema.attributes || schema.attributes.length < 1) {
logContent.changes.push({
action: "getSchema",
information: `No attributes found in schema '${schema.collectionName}'.`,
});
throw new Error(`No attributes found in schema for collection '${newArgs.name}'`);
}
logContent.changes.push({
action: "getSchema",
information: `Schema '${schema.collectionName}' loaded.`,
});
// Update newArgs with schema values.
newArgs.name = schema.collectionName;
newArgs.permissions = schema.permissions;
newArgs.documentSecurity = schema.documentSecurity;
newArgs.enabled = schema.enabled;
// Compare collection keys: permissions, documentSecurity, enabled.
let coll;
if (JSON.stringify(newArgs.permissions) !==
JSON.stringify(currentCollection.$permissions) ||
newArgs.documentSecurity !== currentCollection.documentSecurity ||
newArgs.enabled !== currentCollection.enabled) {
const updateCollectionParams = [
newArgs.databaseId,
newArgs.collectionId,
newArgs.name,
newArgs.permissions,
newArgs.documentSecurity,
newArgs.enabled,
];
coll = await databases.updateCollection(...updateCollectionParams);
logContent.changes.push({
action: "updateCollection",
information: `Collection updated with new schema values.`,
});
}
else {
coll = currentCollection;
logContent.changes.push({
action: "updateCollection",
information: `No update necessary for permissions, documentSecurity, or enabled.`,
});
}
// Process attributes.
const currentAttributeKeys = new Set(coll.attributes.map((attr) => attr.key));
const schemaAttributeKeys = new Set(schema.attributes.map((attr) => attr.key));
for (const schemaAttr of schema.attributes) {
if (!currentAttributeKeys.has(schemaAttr.key)) {
logContent.changes.push({
action: "createAttribute",
information: `Attribute '${schemaAttr.key}' not found; creating it.`,
});
// Validate that xdefault is not defined if required is set to true
const hasRequiredTrue = "required" in schemaAttr && schemaAttr.required === true;
const hasXdefault = schemaAttr.type !== "relationship" &&
"xdefault" in schemaAttr &&
schemaAttr.xdefault !== undefined;
if (hasRequiredTrue && hasXdefault) {
const msg = `Not able to create Attribute '${schemaAttr.key}' for Collection '${newArgs.name}' (id: '${newArgs.collectionId}'), because both 'xdefault' and 'required: true' are set. Appwrite does not allow xdefault to be defined while required is true.`;
logContent.changes.push({
action: "createAttribute",
information: msg,
});
throw new Error(msg);
}
// Check relationshipAttributeData prop
if (schemaAttr.key === "relationship") {
if (newArgs.relationshipAttributeData?.length) {
newArgs.relationshipAttributeData.map((relAttr) => {
if (!relAttr.relationshipKey.length ||
!relAttr.relatedCollectionId.length) {
logContent.changes.push({
action: "createAttribute (relationship)",
information: `Not able to create Attribute '${newArgs.name}' (id: '${newArgs.collectionId}'), since relationship information for key '${schemaAttr.key}' is missing (relationshipKey or relatedCollectionId or both, passed with param 'relationshipAttributeData' are are not valid)`,
});
throw new Error(`Not able to create Attribute '${newArgs.name}' (id: '${newArgs.collectionId}'), since relationship information for key '${schemaAttr.key}' is missing (relationshipKey or relatedCollectionId or both, passed with param 'relationshipAttributeData' are are not valid)`);
}
});
}
else {
logContent.changes.push({
action: "createAttribute (relationship)",
information: `Not able to create Attribute '${newArgs.name}' (id: '${newArgs.collectionId}'), since relationship information for key '${schemaAttr.key}' is missing (relationshipAttributeData)`,
});
throw new Error(`Not able to create Attribute '${newArgs.name}' (id: '${newArgs.collectionId}'), since relationship information for key '${schemaAttr.key}' is missing (relationshipAttributeData)`);
}
}
try {
await createAttribute(newArgs.databaseId, newArgs.collectionId, schemaAttr, newArgs.relationshipAttributeData?.find((relAttr) => relAttr.relationshipKey === schemaAttr.key)?.relatedCollectionId);
logContent.changes.push({
action: "createAttribute",
information: `Attribute '${schemaAttr.key}' created.`,
});
}
catch (error) {
throw new Error(`Couldn't create attribute '${schemaAttr}': ${error}`);
}
}
else if (args.destructive) {
const existingAttr = coll.attributes.find((attr) => attr.key === schemaAttr.key);
if (!attributesEqual(existingAttr, schemaAttr)) {
logContent.changes.push({
action: "updateAttribute",
infor