Page Menu
Home
Sealhub
Search
Configure Global Search
Log In
Files
F996228
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
53 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/subject/attachments/attachment-assigner.ts b/src/subject/attachments/attachment-assigner.ts
index a06c5dc6..35dfb015 100644
--- a/src/subject/attachments/attachment-assigner.ts
+++ b/src/subject/attachments/attachment-assigner.ts
@@ -1,45 +1,40 @@
import App from "../../app/app";
import Context from "../../context";
import * as Errors from "../../response/errors";
import Collection from "../../chip-types/collection";
-import { LooseObject } from "../types";
-
-export type AssignAttachmentsResult = {
- attachments: LooseObject;
- fieldsWithAttachments: LooseObject;
-};
+import { AttachmentParams, AssignAttachmentsResult } from "../types";
export default async function(
app: App,
context: Context,
- params: any,
+ params: AttachmentParams,
root_collection: Collection,
documents: Array<any>
): Promise<AssignAttachmentsResult> {
const attachments_query = params.attachments;
const attachments = {};
const fieldsWithAttachments = {};
for (let field_name of Object.keys(attachments_query)) {
const field = root_collection.fields[field_name];
if (!field) {
throw new Errors.NotFound(
`Given field ${field_name} is not declared in collection!`
);
}
const loader = await field.get_attachment_loader(context, field_name, {
...field.params,
attachments_query: attachments_query[field_name],
documents,
});
if (!loader) {
throw new Errors.FieldDoesNotSupportAttachments(
`Given field ${field_name} does not support attachments!`
);
}
await loader.loadTo(app, attachments, fieldsWithAttachments);
}
return { attachments, fieldsWithAttachments };
}
diff --git a/src/subject/attachments/index.js b/src/subject/attachments/index.js
deleted file mode 100644
index 69e7119c..00000000
--- a/src/subject/attachments/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-module.exports = {
- ReferenceToCollection: require("./reference-to-collection.js").default,
-};
diff --git a/src/subject/predefined-subjects/users-subject.ts b/src/subject/predefined-subjects/users-subject.ts
index db266d2e..5f6ae45d 100644
--- a/src/subject/predefined-subjects/users-subject.ts
+++ b/src/subject/predefined-subjects/users-subject.ts
@@ -1,67 +1,65 @@
import Subject from "../subject.js";
import * as Errors from "../../response/errors";
import me_synonyms from "../../misc/me-synonyms";
import MeSubject from "./me-subject";
import SuperContext from "../../super-context.js";
import App from "../../app/app.js";
import Context from "../../context.js";
import { CreateActionName, ShowActionName } from "../../action.js";
export default class UsersSubject extends Subject {
getName = () => "users";
- performAction(
+ async performAction(
context: Context,
action_name: CreateActionName | ShowActionName,
params: any
) {
params = params || {};
switch (action_name) {
case "create":
return this.app.run_action(
context,
["collections", "users"],
"create",
params
);
case "show":
return this.app.run_action(
context,
["collections", "users"],
"show",
params
);
default:
- return Promise.reject(
- new Errors.BadSubjectAction(
- `Unknown/unsupported action for UsersSubject: '${action_name}'`
- )
+ throw new Errors.BadSubjectAction(
+ `Unknown/unsupported action for UsersSubject: '${action_name}'`
);
}
}
async getChildSubject(path_element: string) {
if (me_synonyms.indexOf(path_element) !== -1) {
return new MeSubject(this.app);
}
const username = path_element;
const response = await this.app.run_action(
new SuperContext(),
["collections", "users"],
"show",
{
filter: { username: username },
}
);
if (response.empty) {
throw new Errors.BadSubjectPath(`Unknown username: '${username}'`);
}
return this.app.RootSubject.getSubject([
"collections",
"users",
response.id,
]);
}
}
diff --git a/src/subject/subject-types/_batch_action.ts b/src/subject/subject-types/_batch_action.ts
new file mode 100644
index 00000000..95549f45
--- /dev/null
+++ b/src/subject/subject-types/_batch_action.ts
@@ -0,0 +1,78 @@
+import App from "../../app/app";
+import Context from "../../context";
+import ArrayCartesian from "../../utils/array-cartesian.js";
+import PromiseIterateGenerator from "../../utils/promise-iterate-generator.js";
+import { LooseObject } from "../types";
+
+type BatchActionBatchType = "batch";
+type BatchActionCartesianType = "cartesian";
+
+export type BatchActionParams = {
+ __multiple: boolean;
+ mode: BatchActionBatchType | BatchActionCartesianType;
+ sources: Array<Array<any>>;
+};
+
+export default async function batchAction(
+ app: App,
+ context: Context,
+ params: BatchActionParams,
+ callback: Function
+) {
+ // callback is a function that will be called for each instance infered from the description, taking context and inferred body as arguments
+ const mode = params.mode || "batch"; // can be: "cartesian" or "batch";
+ if (mode === "batch") {
+ throw new Error("BATCH mode not implemented yet");
+ }
+ if (mode !== "cartesian") {
+ throw new Error(`Incorrect mode: ${mode}`);
+ }
+
+ const field_names: Array<string> = [];
+ const possible_field_values: Array<any> = [];
+ const to_await = [];
+ for (const source of params.sources) {
+ if (source[0] === "literal") {
+ for (const field_name in source[1]) {
+ field_names.push(field_name);
+ possible_field_values.push([source[1][field_name]]);
+ }
+ } else if (source[0] === "collection_fields") {
+ const collection_name = source[1].collection;
+ const filter = source[1].filter || {};
+ const fields = source[1].fields || [];
+ const map_to = source[1].map_to;
+ const promise = app
+ .run_action(context, ["collections", collection_name], "show", {
+ filter,
+ })
+ .then(function(sealious_response) {
+ for (const i in map_to) {
+ const field_in_collection = fields[i];
+ const field_name = map_to[i];
+ field_names.push(field_name);
+ possible_field_values.push(
+ sealious_response.items.map(
+ (resource: LooseObject) => {
+ return resource[field_in_collection];
+ }
+ )
+ );
+ }
+ });
+ to_await.push(promise);
+ }
+ }
+ await Promise.all(to_await);
+
+ return PromiseIterateGenerator(
+ new ArrayCartesian(possible_field_values),
+ function(values: Array<any>) {
+ const body: LooseObject = {};
+ for (let i = 0; i < field_names.length; ++i) {
+ body[field_names[i]] = values[i];
+ }
+ return callback(context, body);
+ }
+ );
+}
diff --git a/src/subject/subject-types/collection-field-subject.ts b/src/subject/subject-types/collection-field-subject.ts
index 9fb4e8cf..ec42ab0b 100644
--- a/src/subject/subject-types/collection-field-subject.ts
+++ b/src/subject/subject-types/collection-field-subject.ts
@@ -1,52 +1,59 @@
-import Promise from "bluebird";
-
-import Subject from "../subject.js";
-import Errors from "../../response/error.js";
-import Collection from "../../chip-types/collection.js";
-import { ActionName } from "../../action.js";
-import Context from "../../context.js";
+import App from "../../app/app";
+import Subject from "../subject";
+import * as Errors from "../../response/errors";
+import Collection from "../../chip-types/collection";
+import { ActionName } from "../../action";
+import Context from "../../context";
+import { LooseObject } from "../types";
export default class CollectionFieldSubject extends Subject {
name: string;
collection: Collection;
field_name: string;
- field_type: string;
+ field_type: LooseObject;
resource_id: string;
+
constructor(
+ app: App,
collection: Collection,
resource_id: string,
field_name: string
) {
- super();
+ super(app);
this.name = "CollectionFieldSubject";
this.collection = collection;
this.resource_id = resource_id;
this.field_name = field_name;
this.field_type = collection[field_name].type;
}
- perform_action(context: Context, action_name: ActionName, params: any) {
+ async performAction(
+ context: Context,
+ action_name: ActionName,
+ params: any
+ ) {
params = params || {};
params = {
resource_id: this.resource_id,
field_name: this.field_name,
collection: this.collection,
...params,
};
if (this.field_type.actions[action_name]) {
return Promise.resolve(
this.field_type.actions[action_name](context, params)
);
} else {
throw new Errors.DeveloperError(`Unknown action: '${action_name}'`);
}
}
- get_child_subject(key) {
- const self = this;
- return Promise.try(function () {
- return self.field_type.get_child_subject(key);
- });
+ async getChildSubject(path_element: string) {
+ return this.field_type.get_child_subject(path_element);
+ }
+
+ getName() {
+ return "CollectionFieldSubject";
}
}
diff --git a/src/subject/subject-types/collection-subject.ts b/src/subject/subject-types/collection-subject.ts
index e963f1b0..5a8f9581 100644
--- a/src/subject/subject-types/collection-subject.ts
+++ b/src/subject/subject-types/collection-subject.ts
@@ -1,381 +1,372 @@
import assert from "assert";
import clone from "clone";
import shortid from "shortid";
-import SingleItemResponse from "../../../common_lib/response/single-item-response.js";
+import SingleItemResponse from "../../../common_lib/response/single-item-response";
import Collection from "../../chip-types/collection";
import * as Errors from "../../response/errors";
import assignAttachments from "../attachments/attachment-assigner";
-import Subject from "../subject.js";
-import SingleResource from "./single-resource-subject.js";
-import batch_action from "./_batch_action.js";
+import Subject from "../subject";
+import Context from "../../context";
+import SingleResource from "./single-resource-subject";
+import batchAction, { BatchActionParams } from "./_batch_action";
+import { ResourceCreated } from "../../../common_lib/response/responses";
+import {
+ LooseObject,
+ AttachmentParams,
+ AssignAttachmentsResult,
+} from "../types";
+import Item from "../../../common_lib/response/item";
+import CollectionResponse from "../../../common_lib/response/collection-response";
+import {
+ CreateActionName,
+ ShowActionName,
+ DeleteActionName,
+} from "../../action";
//
//
//
//
// !!! many of these methods should be moved to the collection
-// !!! class, this should be a lightweigt controller
+// !!! class, this should be a lightweight controller
//
//
//
//
export default class CollectionSubject extends Subject {
collection: Collection;
- getName = () => "Collection";
- constructor(collection: Collection, ids = []) {
+ named_filters: Array<string>;
+ ids: Array<string>;
+
+ getName() {
+ return "Collection";
+ }
+ constructor(
+ collection: Collection,
+ named_filters: Array<string> = [],
+ ids: Array<string> = []
+ ) {
super(collection.app);
this.collection = collection;
+ this.named_filters = named_filters;
+ this.ids = ids;
+ }
- this.list_resources = async function(context, params) {
- const result = await CollectionSubject.prototype.__list_resources(
- app.Datastore,
- this.collection,
- context,
- params,
- named_filters,
- ids
- );
-
- if (params.attachments && typeof params.attachments === "object") {
- Object.assign(
- result,
- await assignAttachments(
- app,
+ performAction(
+ context: Context,
+ action_name: CreateActionName | ShowActionName | DeleteActionName,
+ params: LooseObject
+ ) {
+ switch (action_name) {
+ case "create":
+ if (params.__multiple) {
+ return this.createMany(
context,
- params,
- this.collection,
- result.items
- )
+ params as BatchActionParams
+ );
+ } else {
+ return this.createResource(context, params);
+ }
+ case "show":
+ return this.listResources(context, params);
+ case "delete":
+ return this.delete(context, params as BatchActionParams);
+ default:
+ throw new Errors.DeveloperError(
+ `Unknown action for '${this.collection.name}' collection: '${action_name}'`
);
- }
- await CollectionSubject.prototype.__exclude_items_not_passing_check(
- context,
- this.collection,
- result
- );
- return new app.Sealious.Responses.CollectionResponse(result);
- };
+ }
+ }
- this.create_many = function(context, params) {
- // test with: http -f POST localhost:8081/api/v1/collections/animals __multiple=true mode=cartesian sources[0][0]=literal sources[0][1][pole]=wartosc sources[0][1][bark]=bork sources[1][0]=collection_fields sources[1][1][collection]=shelters sources[1][1][filter][city]=Poznań sources[1][1][fields][0]=id
+ createMany(context: Context, params: BatchActionParams) {
+ // test with: http -f POST localhost:8081/api/v1/collections/animals __multiple=true mode=cartesian sources[0][0]=literal sources[0][1][pole]=wartosc sources[0][1][bark]=bork sources[1][0]=collection_fields sources[1][1][collection]=shelters sources[1][1][filter][city]=Poznań sources[1][1][fields][0]=id
- /*
+ /*
params.mode = "batch" | "cartesian"
for "cartesian" mode:
* 'sources' - a list of value sources to generate the cartesian product
Each source is a 2-element array, where the first element is the source type, and the second one is the params
source types:
* 'literal': second element needs to be a key->value map
* `collection_fields`: values from a collection
* collection
* filter
* fields[]
* map_to[]
*/
- const self = this;
- return batch_action(app, context, params, function(context, body) {
- return app.run_action(
+ return batchAction(
+ this.app,
+ context,
+ params,
+ (context: Context, body: Item) =>
+ this.app.run_action(
context,
- ["collections", self.collection.name],
+ ["collections", this.collection.name],
"create",
body
- );
- });
- };
+ )
+ );
+ }
- this.delete_many = function(context, params) {
- const self = this;
- return batch_action(app, context, params, function(context, body) {
- return app
+ createResource(context: Context, body: Item) {
+ return this.__createResource(context, body);
+ }
+
+ deleteMany(context: Context, params: BatchActionParams) {
+ return batchAction(
+ this.app,
+ context,
+ params,
+ async (context: Context, body: Item) => {
+ const items = await this.app
.run_action(
context,
- ["collections", self.collection.name],
+ ["collections", this.collection.name],
"show",
{
filter: body,
}
)
- .then(sealious_response => sealious_response.items)
- .each(function(resource) {
- return app.run_action(
+ .then(sealious_response => sealious_response.items);
+
+ const results = [];
+ for (let item of items) {
+ results.push(
+ await this.app.run_action(
context,
- ["collections", self.collection.name, resource.id],
+ ["collections", this.collection.name, item.id],
"delete"
- );
- });
- });
- };
-
- this.delete = function(context, params) {
- if (params.__multiple) {
- return this.delete_many(context, params);
- } else {
- throw new app.Sealious.Errors.NotFound(
- "Cannot delete a collection. Try using the '__multiple: true' mode"
- );
+ )
+ );
+ }
+ return results;
}
- };
+ );
+ }
- this.get_child_subject = async function(key) {
- if (key[0] === "@") {
- return new CollectionSubject(app, collection, [
- ...named_filters,
- key.slice(1),
- ]);
- } else if (key instanceof Array) {
- const ids = key;
- return new CollectionSubject(app, collection, [], ids);
- }
- const resource_id = key;
- return new SingleResource(app, this.collection, resource_id);
- };
+ delete(context: Context, params: BatchActionParams) {
+ if (params.__multiple) {
+ return this.deleteMany(context, params);
+ }
+
+ throw new Errors.NotFound(
+ "Cannot delete a collection. Try using the '__multiple: true' mode"
+ );
}
-}
-CollectionSubject.prototype = Object.create(Subject.prototype);
-
-CollectionSubject.prototype.__create_resource = function(
- datastore,
- collection,
- context,
- body
-) {
- return collection
- .check_if_action_is_allowed(context, "create", body)
- .then(function() {
- return collection.validate_field_values(context, true, body);
- })
- .then(function() {
- return collection.encode_field_values(context, body);
- })
- .then(function(encoded_body) {
- const newID = shortid();
- const resource_data = {
- _metadata: {
- collection: collection.name,
- created_context: context,
- last_modified_context: context,
- },
- sealious_id: newID,
- ...encoded_body,
- };
- return datastore.insert(collection.name, resource_data, {});
- })
- .then(function(database_entry) {
- return collection.get_resource_representation(
- context,
- database_entry
+ async getChildSubject(path_element: string | Array<string>) {
+ if (path_element[0] === "@") {
+ return new CollectionSubject(this.collection, [
+ ...this.named_filters,
+ path_element.slice(1) as string,
+ ]);
+ } else if (path_element instanceof Array) {
+ const ids = path_element;
+ return new CollectionSubject(this.collection, [], ids);
+ }
+ const resource_id = path_element;
+ return new SingleResource(this.app, this.collection, resource_id);
+ }
+
+ async listResources(context: Context, params: LooseObject) {
+ const result = await this.__listResources(context, params);
+
+ if (params.attachments && typeof params.attachments === "object") {
+ Object.assign(
+ result,
+ await assignAttachments(
+ this.app,
+ context,
+ params as AttachmentParams,
+ this.collection,
+ result.items
+ )
);
- })
- .then(function(representation) {
- return new Sealious.Responses.ResourceCreated(representation);
- });
-};
+ }
+ await this.__excludeItemsNotPassingCheck(
+ context,
+ this.collection,
+ result
+ );
+ return new CollectionResponse(result);
+ }
-CollectionSubject.prototype.__preprocess_resource_filter = function(
- collection,
- context,
- filter
-) {
- filter = clone(filter) || {};
- const expanded_filter = expandHash(filter);
- const processed_filter = {};
- for (const field_name in expanded_filter) {
- if (!collection.fields[field_name]) {
- continue;
+ async __listResources(context: Context, params: LooseObject) {
+ if (params === undefined || params === null) {
+ params = {};
}
- const field = collection.fields[field_name];
- const field_filter = expanded_filter[field_name];
- if (field_filter instanceof Array) {
- processed_filter[field_name] = Promise.all(
- field_filter.map(field.encode.bind(field, context))
- ).then(filters => {
- return { $in: filters };
- });
- } else {
- processed_filter[field_name] = field.filter_to_query(
- context,
- field_filter
- );
+
+ if (params.calculate === "false" || params.calculate === false) {
+ params.calculate = false;
+ } else if (typeof params.calculate !== "object") {
+ params.calculate = true;
}
+
+ await this.collection.checkIfActionIsAllowed(context, "show", {});
+ const aggregation_stages = await this.collection.getAggregationStages(
+ context,
+ "show",
+ params,
+ this.named_filters,
+ this.ids
+ );
+
+ const output_options = getOutputOptions(this.collection, params);
+
+ const documents = await this.app.Datastore.aggregate(
+ this.collection.name,
+ aggregation_stages,
+ {},
+ output_options
+ );
+
+ const decoded_items = [];
+
+ for (let document of documents) {
+ try {
+ let item = await this.collection.getResourceRepresentation(
+ context,
+ document,
+ params.format,
+ params.calculate
+ );
+ decoded_items.push(item);
+ } catch (e) {}
+ }
+
+ return {
+ items: decoded_items,
+ attachments: {},
+ fieldsWithAttachments: {},
+ };
}
- return Promise.props(processed_filter);
-};
+
+ async __createResource(context: Context, body: Item) {
+ await this.collection.checkIfActionIsAllowed(context, "create", body);
+ await this.collection.validateFieldValues(context, true, body);
+ const encoded_body = await this.collection.encodeFieldValues(
+ context,
+ body
+ );
+
+ const newID = shortid();
+ const resource_data = {
+ _metadata: {
+ collection: this.collection.name,
+ created_context: context,
+ last_modified_context: context,
+ },
+ sealious_id: newID,
+ ...encoded_body,
+ };
+ const database_entry = await this.app.Datastore.insert(
+ this.collection.name,
+ resource_data,
+ {}
+ );
+ const representation = await this.collection.getResourceRepresentation(
+ this.context,
+ database_entry
+ );
+ return new ResourceCreated(representation);
+ }
+
+ async __excludeItemsNotPassingCheck(
+ context: Context,
+ collection: Collection,
+ result: AssignAttachmentsResult
+ ) {
+ const access_strategy = collection.getAccessStrategy("show");
+ const is_item_sensitive = await access_strategy.isItemSensitive();
+ if (!is_item_sensitive) {
+ return;
+ }
+ result.items = await Promise.filter(result.items, async item => {
+ try {
+ await access_strategy.check(
+ context,
+ new SingleItemResponse({
+ item,
+ attachments: result.attachments,
+ fieldsWithAttachments: result.fieldsWithAttachments,
+ })
+ );
+ return true;
+ } catch (e) {
+ console.error(e);
+ return false;
+ }
+ });
+ }
+}
const sealious_to_mongo_sort_param = {
desc: -1,
descending: -1,
asc: 1,
ascending: 1,
};
-const get_output_options = function(collection, params) {
+const getOutputOptions = function(collection: Collection, params: LooseObject) {
const output_options = {};
if (params.pagination) {
const default_pagination_params = {
page: 1,
items: 10,
};
- const full_pagination_params = merge(
+ const full_pagination_params = Object.assign(
+ {},
default_pagination_params,
params.pagination
);
const must_be_int = ["items", "page"];
must_be_int.forEach(function(attribute_name) {
- if (isNaN(parseInt(full_pagination_params[attribute_name]))) {
+ if (isNaN(parseInt(full_pagination_params[attribute_name], 10))) {
full_pagination_params[attribute_name] =
default_pagination_params[attribute_name];
} else {
full_pagination_params[attribute_name] = parseInt(
full_pagination_params[attribute_name]
);
}
});
output_options.skip =
(full_pagination_params.page - 1) * full_pagination_params.items;
output_options.amount =
- parseInt(full_pagination_params.items) +
- (parseInt(full_pagination_params.forward_buffer) || 0);
+ parseInt(full_pagination_params.items, 10) +
+ (parseInt(full_pagination_params.forward_buffer, 10) || 0);
} else {
if (params.skip) {
output_options.skip = parseInt(params.skip);
}
if (params.amount) {
output_options.amount = parseInt(params.count);
}
}
if (params.sort) {
const full_sort_params = clone(params.sort);
for (const field_name in full_sort_params) {
const mongo_sort_param =
sealious_to_mongo_sort_param[full_sort_params[field_name]];
if (!mongo_sort_param) {
const available_sort_keys = Object.keys(
sealious_to_mongo_sort_param
).join(", ");
throw new Errors.BadSubjectAction(
`Unknown sort key: ${full_sort_params[field_name]}. Available sort keys are: ${available_sort_keys}.`
);
}
full_sort_params[field_name] = mongo_sort_param;
}
output_options.sort = full_sort_params;
}
return output_options;
};
-
-CollectionSubject.prototype.__list_resources = async function(
- datastore,
- collection,
- context,
- params,
- named_filters,
- ids
-) {
- if (params === undefined || params === null) {
- params = {};
- }
-
- if (params.calculate === "false" || params.calculate === false) {
- params.calculate = false;
- } else if (typeof params.calculate !== "object") {
- params.calculate = true;
- }
-
- await collection.check_if_action_is_allowed(context, "show");
- const aggregation_stages = await collection.get_aggregation_stages(
- context,
- "show",
- params,
- named_filters,
- ids
- );
-
- const output_options = get_output_options(this.collection, params);
-
- const documents = await datastore.aggregate(
- collection.name,
- aggregation_stages,
- {},
- output_options
- );
-
- const decoded_items = [];
-
- for (let document of documents) {
- try {
- let item = await collection.get_resource_representation(
- context,
- document,
- params.format,
- params.calculate
- );
- decoded_items.push(item);
- } catch (e) {}
- }
-
- return { items: decoded_items, attachments: {} };
-};
-
-CollectionSubject.prototype.__exclude_items_not_passing_check = async function(
- context,
- collection,
- result
-) {
- const access_strategy = collection.get_access_strategy("show");
- const is_item_sensitive = await access_strategy.is_item_sensitive();
- if (!is_item_sensitive) {
- return;
- }
- result.items = await Promise.filter(result.items, async item => {
- try {
- await access_strategy.check(
- context,
- new SingleItemResponse({
- item,
- attachments: result.attachments,
- fieldsWithAttachments: result.fieldsWithAttachments,
- })
- );
- return true;
- } catch (e) {
- console.error(e);
- return false;
- }
- });
-};
-
-CollectionSubject.prototype.perform_action = function(
- context,
- action_name,
- args
-) {
- switch (action_name) {
- case "create":
- if (args.__multiple) {
- return this.create_many(context, args);
- } else {
- return this.create_resource(context, args);
- }
- case "show":
- return this.list_resources(context, args);
- case "delete":
- return this.delete(context, args);
- default:
- throw new Errors.DeveloperError(
- `Unknown action for '${this.collection.name}' collection: '${action_name}'`
- );
- }
-};
-
-CollectionSubject.subject_name = "collection";
-
-module.exports = CollectionSubject;
diff --git a/src/subject/subject-types/current-session-subject.js b/src/subject/subject-types/current-session-subject.js
deleted file mode 100644
index 86daa1a2..00000000
--- a/src/subject/subject-types/current-session-subject.js
+++ /dev/null
@@ -1,69 +0,0 @@
-const Promise = require("bluebird");
-
-const Response = require("../../response/response.js");
-const Errors = require("../../response/error.js");
-const Subject = require("../subject.js");
-const SuperContext = require("../../super-context.js");
-
-const CurrentSession = function (app) {
- this.app = app;
-};
-
-CurrentSession.prototype = Object.create(Subject.prototype);
-
-CurrentSession.prototype.perform_action = async function (
- context,
- action_name,
- args
-) {
- if (action_name !== "delete") {
- throw new Errors.DeveloperError(
- `Unknown action ${action_name} for CurrentSession subject.`
- );
- }
- try {
- const session_sealious_response = await this.app.run_action(
- new SuperContext(),
- ["collections", "sessions"],
- "show",
- {
- filter: { "session-id": context.session_id },
- }
- );
-
- await Promise.map(session_sealious_response.items, (session) =>
- this.app.run_action(
- new SuperContext(),
- ["collections", "sessions", session.id],
- "delete"
- )
- );
-
- const anonymous_session_sealious_response = await this.app.run_action(
- new SuperContext(),
- ["collections", "anonymous-sessions"],
- "show",
- {
- filter: {
- "anonymous-session-id": context.anonymous_session_id,
- },
- }
- );
-
- await Promise.map(
- anonymous_session_sealious_response.items,
- (session) =>
- this.app.run_action(
- new SuperContext(),
- ["collections", "anonymous-sessions", session.id],
- "delete"
- )
- );
-
- return new Response({}, false, "logged_out", "You've been logged out");
- } catch (e) {
- return Promise.reject(new Errors.BadContext("Invalid session id!"));
- }
-};
-
-module.exports = CurrentSession;
diff --git a/src/subject/subject-types/current-session-subject.ts b/src/subject/subject-types/current-session-subject.ts
new file mode 100644
index 00000000..f632e246
--- /dev/null
+++ b/src/subject/subject-types/current-session-subject.ts
@@ -0,0 +1,75 @@
+import App from "../../app/app";
+import { LeafSubject } from "../subject.js";
+import * as Errors from "../../response/errors";
+import Context from "../../context";
+import { DeleteActionName } from "../../action";
+import Response from "../../response/response";
+import SuperContext from "../../super-context";
+import Item from "../../../common_lib/response/item";
+
+export default class CurrentSession extends LeafSubject {
+ async performAction(
+ context: Context,
+ action_name: DeleteActionName,
+ params: any
+ ) {
+ if (action_name !== "delete") {
+ throw new Errors.DeveloperError(
+ `Unknown action ${action_name} for CurrentSession subject.`
+ );
+ }
+ try {
+ const session_sealious_response = await this.app.run_action(
+ new SuperContext(),
+ ["collections", "sessions"],
+ "show",
+ {
+ filter: { "session-id": context.session_id },
+ }
+ );
+
+ await Promise.all(
+ session_sealious_response.items.map((session: Item) =>
+ this.app.run_action(
+ new SuperContext(),
+ ["collections", "sessions", session.id],
+ "delete"
+ )
+ )
+ );
+
+ const anonymous_session_sealious_response = await this.app.run_action(
+ new SuperContext(),
+ ["collections", "anonymous-sessions"],
+ "show",
+ {
+ filter: {
+ "anonymous-session-id": context.anonymous_session_id,
+ },
+ }
+ );
+
+ await Promise.all(
+ anonymous_session_sealious_response.items.map((session: Item) =>
+ this.app.run_action(
+ new SuperContext(),
+ ["collections", "anonymous-sessions", session.id],
+ "delete"
+ )
+ )
+ );
+
+ return new Response(
+ {},
+ false,
+ "logged_out",
+ "You've been logged out"
+ );
+ } catch (e) {
+ return Promise.reject(new Errors.BadContext("Invalid session id!"));
+ }
+ }
+ getName() {
+ return "CurrentSession";
+ }
+}
diff --git a/src/subject/subject-types/image-format/image-format.js b/src/subject/subject-types/image-format/image-format.js
deleted file mode 100644
index 4ae6ee96..00000000
--- a/src/subject/subject-types/image-format/image-format.js
+++ /dev/null
@@ -1,179 +0,0 @@
-const locreq = require("locreq")(__dirname);
-const Subject = require("../../subject.js");
-const Errors = require("../../../response/error.js");
-const { promisify } = require("util");
-const sharp = require("sharp");
-const fs = require("fs");
-
-const QUALITY = 80;
-
-function format_hash(format_obj) {
- return format_obj.size[0] + ":" + format_obj.size[1] + "(" + QUALITY + ")";
-}
-
-function format_filename(original_filename, format_name) {
- return (
- original_filename.split(".").slice(0, -1).join(".") +
- "-" +
- format_name +
- ".jpg"
- );
-}
-
-const ImageFormat = function (app, file_id, format_name) {
- this.name = "ImageFormats";
- this.file_id = file_id;
-
- function get_hdd_path(file_id) {
- return locreq.resolve(app.FileManager.upload_path + "/" + file_id);
- }
-
- function create_formatted_version(file_id, format_name) {
- const format_obj = app.ConfigManager.get("image_formats")[format_name];
-
- return app.Datastore.find("files", { id: file_id }).then(function (
- matches
- ) {
- const original_file = matches[0];
- const file_path = get_hdd_path(original_file.id);
- const width = format_obj.size[0];
- const height = format_obj.size[1];
- const filename = format_filename(
- original_file.original_name,
- format_name
- );
-
- const temp_file_path =
- "/tmp/" + Math.floor(Math.random() * Math.pow(10, 7)) + ".jpg";
-
- return sharp(file_path)
- .resize(width, height)
- .toFile(temp_file_path)
- .then(function () {
- return promisify(fs.readFile)(temp_file_path);
- })
- .then(function (buffer) {
- return app.FileManager.save_file(
- new app.Sealious.File(
- new app.Sealious.SuperContext(),
- filename,
- buffer
- )
- );
- })
- .then(function (sealious_file) {
- return app
- .run_action(
- new app.Sealious.SuperContext(),
- ["collections", "formatted-images"],
- "create",
- {
- original_photo_file: original_file.id,
- formatted_photo_file: sealious_file.id,
- format: format_hash(format_obj),
- }
- )
- .then(() => sealious_file);
- })
- .then(function (file) {
- return promisify(fs.unlink)(temp_file_path).then(
- () => file
- );
- });
- });
- }
-
- function get_formatted_version(
- file_id,
- file_name,
- format_name,
- format_obj
- ) {
- const random = Math.random();
- const hash = format_hash(format_obj);
- return app.Datastore.aggregate("formatted-images", [
- {
- $match: {
- original_photo_file: { $eq: file_id },
- },
- },
- {
- $match: {
- $or: [{ "format.original": hash }, { "format.safe": hash }],
- },
- },
- ]).then(function (results) {
- return (
- results[0] &&
- results[0] && {
- id: results[0].formatted_photo_file,
- original_name: format_filename(file_name, format_name),
- }
- );
- });
- }
-
- const ImageFormatFile = function (file_id, format_name, file_name) {
- this.name = "SingleFile";
- this.file_id = file_id;
- this.file_name = file_name;
-
- ImageFormatFile.prototype.perform_action = function (
- context,
- action_name,
- args
- ) {
- switch (action_name) {
- case "show":
- const format_obj =
- app.ConfigManager.get("image_formats")[format_name] ||
- null;
-
- if (!format_obj) {
- throw new Errors.BadSubjectPath(
- "Unknown image format: " + format_name
- );
- }
-
- return get_formatted_version(
- file_id,
- file_name,
- format_name,
- format_obj
- )
- .then(function (result) {
- if (result !== undefined) {
- return result;
- } else {
- return create_formatted_version(
- file_id,
- format_name
- );
- }
- })
- .then((file_description) => {
- let ret = new app.Sealious.File.from_db_entry(
- file_description
- );
- ret.path_on_hdd = get_hdd_path(file_description.id);
- ret.mime = "image/jpeg";
- return ret;
- });
- default:
- throw new Errors.DeveloperError(
- `Unknown action for '${this.collection.name}' subject: '${action_name}'`
- );
- }
- };
- };
-
- ImageFormatFile.prototype = Object.create(Subject.prototype);
-
- this.get_child_subject = function (file_name) {
- return new ImageFormatFile(file_id, format_name, file_name);
- };
-};
-
-ImageFormat.prototype = Object.create(Subject.prototype);
-
-module.exports = ImageFormat;
diff --git a/src/subject/subject-types/image-format/image-format.ts b/src/subject/subject-types/image-format/image-format.ts
new file mode 100644
index 00000000..a3104650
--- /dev/null
+++ b/src/subject/subject-types/image-format/image-format.ts
@@ -0,0 +1,209 @@
+const locreq = require("locreq")(__dirname);
+import sharp from "sharp";
+import path from "path";
+import { promises as fs } from "fs";
+import crypto from "crypto";
+import Subject, { NoActionSubject, LeafSubject } from "../../subject";
+import * as Errors from "../../../response/errors";
+import App from "../../../app/app";
+import Context from "../../../context";
+import { ShowActionName } from "../../../action";
+
+const QUALITY = 80;
+
+type FormattedFileVersion = {
+ id: string;
+ original_name?: string;
+ file_name?: string;
+};
+
+type FormatObject = {
+ size: number[];
+};
+
+function formatHash(format_obj: FormatObject) {
+ return format_obj.size[0] + ":" + format_obj.size[1] + "(" + QUALITY + ")";
+}
+
+function formatFilename(
+ original_filename: string,
+ format_name: string
+): string {
+ return (
+ path.basename(original_filename, path.extname(original_filename)) +
+ "-" +
+ format_name +
+ ".jpg"
+ );
+}
+
+class ImageFormatFile extends LeafSubject {
+ file_id: string;
+ file_name: string;
+ format_name: string;
+
+ constructor(
+ app: App,
+ file_id: string,
+ file_name: string,
+ format_name: string
+ ) {
+ super(app);
+ this.file_id = file_id;
+ this.file_name = file_name;
+ this.format_name = format_name;
+ }
+
+ async performAction(
+ context: Context,
+ action_name: ShowActionName,
+ params: any
+ ) {
+ if (action_name !== "show") {
+ throw new Errors.DeveloperError(
+ `Unknown action for '${this.getName()}' subject: '${action_name}'`
+ );
+ }
+
+ const format_obj =
+ this.app.ConfigManager.get("image_formats")[this.format_name] ||
+ null;
+
+ if (!format_obj) {
+ throw new Errors.BadSubjectPath(
+ "Unknown image format: " + this.format_name
+ );
+ }
+
+ let formattedVersion = await this.getFormattedVersion(
+ this.format_name,
+ format_obj
+ );
+
+ if (!formattedVersion) {
+ formattedVersion = await this.createFormattedVersion(
+ this.file_id,
+ this.format_name
+ );
+ }
+
+ const result = new this.app.Sealious.File.from_db_entry(
+ formattedVersion
+ );
+ result.path_on_hdd = this.getHddPath(formattedVersion.id);
+ result.mime = "image/jpeg";
+ return result;
+ }
+
+ async getFormattedVersion(
+ format_name: string,
+ format_obj: FormatObject
+ ): Promise<FormattedFileVersion> {
+ const random = Math.random();
+ const hash = formatHash(format_obj);
+ const results = await this.app.Datastore.aggregate("formatted-images", [
+ {
+ $match: {
+ original_photo_file: { $eq: this.file_id },
+ },
+ },
+ {
+ $match: {
+ $or: [{ "format.original": hash }, { "format.safe": hash }],
+ },
+ },
+ ]);
+
+ return (
+ results[0] && {
+ id: results[0].formatted_photo_file,
+ original_name: formatFilename(this.file_name, format_name),
+ }
+ );
+ }
+
+ getHddPath(file_id: string): string {
+ const { upload_path } = this.app.FileManager;
+ return locreq.resolve(`${upload_path}/${file_id}`);
+ }
+
+ async createFormattedVersion(
+ file_id: string,
+ format_name: string
+ ): Promise<FormattedFileVersion> {
+ const format_obj: FormatObject = this.app.ConfigManager.get(
+ "image_formats"
+ )[format_name];
+
+ const matches = await this.app.Datastore.find("files", { id: file_id });
+
+ const original_file = matches[0];
+ const file_path = this.getHddPath(original_file.id);
+ const [width, height] = format_obj.size;
+ const filename = formatFilename(
+ original_file.original_name,
+ format_name
+ );
+
+ const temp_file_name = crypto.randomBytes(10).toString("hex") + ".jpg";
+ const temp_file_path = `/tmp/${temp_file_name}`;
+
+ const buffer = await sharp(file_path)
+ .resize(width, height)
+ .toFile(temp_file_path)
+ .then(function() {
+ return fs.readFile(temp_file_path);
+ });
+
+ const sealious_file: FormattedFileVersion = await this.app.FileManager.save_file(
+ new this.app.Sealious.File(
+ new this.app.Sealious.SuperContext(),
+ filename,
+ buffer
+ )
+ );
+
+ await this.app.run_action(
+ new this.app.Sealious.SuperContext(),
+ ["collections", "formatted-images"],
+ "create",
+ {
+ original_photo_file: original_file.id,
+ formatted_photo_file: sealious_file.id,
+ format: formatHash(format_obj),
+ }
+ );
+
+ await fs.unlink(temp_file_path);
+ return sealious_file;
+ }
+
+ getName() {
+ return "SingleFile";
+ }
+}
+
+export class ImageFormat extends NoActionSubject {
+ file_id: string;
+ format_name: string;
+
+ constructor(app: App, file_id: string, format_name: string) {
+ super(app);
+ this.file_id = file_id;
+ this.format_name = format_name;
+ }
+
+ async getChildSubject(path_element: string) {
+ const file_name = path_element;
+ return new ImageFormatFile(
+ this.app,
+ this.file_id,
+ file_name,
+ this.format_name
+ );
+ }
+
+ getName() {
+ return "ImageFormats";
+ }
+}
diff --git a/src/subject/subject-types/image-formats.ts b/src/subject/subject-types/image-formats.ts
index 5925e1e8..e257f404 100644
--- a/src/subject/subject-types/image-formats.ts
+++ b/src/subject/subject-types/image-formats.ts
@@ -1,20 +1,19 @@
import Subject, { NoActionSubject } from "../subject";
-
import App from "../../app/app.js";
-const ImageFormat = require("./image-format/image-format.js");
+import { ImageFormat } from "./image-format/image-format.js";
export default class ImageFormats extends NoActionSubject {
file_id: string;
constructor(app: App, file_id: string) {
super(app);
this.file_id = file_id;
}
getName() {
return "ImageFormats";
}
- getChildSubject(path_element: string) {
+ async getChildSubject(path_element: string) {
return new ImageFormat(this.app, this.file_id, path_element);
}
}
diff --git a/src/subject/subject-types/single-file-subject.js b/src/subject/subject-types/single-file-subject.js
deleted file mode 100644
index cc8801f7..00000000
--- a/src/subject/subject-types/single-file-subject.js
+++ /dev/null
@@ -1,42 +0,0 @@
-const Subject = require("../subject.js");
-const Errors = require("../../response/error.js");
-
-const FileHash = function (app, file_id) {
- this.name = "FileHash";
- this.file_id = file_id;
-
- const SingleFileSubject = function (file_id, file_name) {
- this.name = "SingleFile";
- this.file_id = file_id;
- this.file_name = file_name;
-
- SingleFileSubject.prototype.perform_action = function (
- context,
- action_name,
- args
- ) {
- switch (action_name) {
- case "show":
- return app.FileManager.find(context, {
- id: this.file_id,
- }).then(function (results) {
- return results[0];
- });
- default:
- throw new Errors.DeveloperError(
- `Unknown action for '${this.collection.name}' subject: '${action_name}'`
- );
- }
- };
- };
-
- SingleFileSubject.prototype = Object.create(Subject.prototype);
-
- this.get_child_subject = function (file_name) {
- return new SingleFileSubject(this.file_id, file_name);
- };
-};
-
-FileHash.prototype = Object.create(Subject.prototype);
-
-module.exports = FileHash;
diff --git a/src/subject/subject-types/single-file-subject.ts b/src/subject/subject-types/single-file-subject.ts
new file mode 100644
index 00000000..48565beb
--- /dev/null
+++ b/src/subject/subject-types/single-file-subject.ts
@@ -0,0 +1,55 @@
+import App from "../../app/app.js";
+import * as Errors from "../../response/errors";
+import { NoActionSubject, LeafSubject } from "../subject";
+import Context from "../../context";
+import {
+ CreateActionName,
+ ShowActionName,
+ DeleteActionName,
+} from "../../action";
+
+export default class FileHash extends NoActionSubject {
+ file_id: string;
+
+ constructor(app: App, file_id: string) {
+ super(app);
+ this.file_id = file_id;
+ }
+ getName() {
+ return "FileHash";
+ }
+ async getChildSubject(path_element: string) {
+ return new SingleFileSubject(this.app, this.file_id, path_element);
+ }
+}
+
+class SingleFileSubject extends LeafSubject {
+ file_id: string;
+ file_name: string;
+
+ constructor(app: App, file_id: string, file_name: string) {
+ super(app);
+ this.file_id = file_id;
+ this.file_name = file_name;
+ }
+ getName() {
+ return "SingleFile";
+ }
+ async performAction(
+ context: Context,
+ action_name: ShowActionName,
+ params?: any
+ ) {
+ if (action_name !== "show") {
+ throw new Errors.DeveloperError(
+ `Unknown action for '${this.name}' subject: '${action_name}'`
+ );
+ }
+
+ const [result] = await this.app.FileManager.find(context, {
+ id: this.file_id,
+ });
+
+ return result;
+ }
+}
diff --git a/src/subject/subject-types/single-resource-subject.ts b/src/subject/subject-types/single-resource-subject.ts
index 2a494742..e81a0d08 100644
--- a/src/subject/subject-types/single-resource-subject.ts
+++ b/src/subject/subject-types/single-resource-subject.ts
@@ -1,173 +1,165 @@
-import Subject from "../subject.js";
-import * as Errors from "../../response/errors.js";
-import SingleItemResponse from "../../../common_lib/response/single-item-response.js";
+import Subject from "../subject";
+import * as Errors from "../../response/errors";
+import SingleItemResponse from "../../../common_lib/response/single-item-response";
import assignAttachments from "../attachments/attachment-assigner";
-import CollectionFieldSubject from "./collection-field-subject.js";
-import App from "../../app/app.js";
-import Collection from "../../chip-types/collection.js";
-import Context from "../../context.js";
+import CollectionFieldSubject from "./collection-field-subject";
+import App from "../../app/app";
+import Collection from "../../chip-types/collection";
+import Context from "../../context";
+import Item from "../../../common_lib/response/item";
+import { LooseObject, AttachmentParams } from "../types";
+import { ActionName } from "../../action";
export default class SingleResource extends Subject {
collection: Collection;
resource_id: string;
- name = "SingleResource";
- app: App;
+
constructor(app: App, collection: Collection, resource_id: string) {
- super();
+ super(app);
this.collection = collection;
this.resource_id = resource_id;
- this.app = app;
}
+ getName() {
+ return "SingleResource";
+ }
+ async performAction(context: Context, action_name: ActionName, args?: any) {
+ switch (action_name) {
+ case "show":
+ return this.getResource(context, args);
+ case "edit":
+ return this.editResource(context, args, false);
+ case "replace":
+ return this.editResource(context, args, true);
+ case "delete":
+ return this.deleteResource(context, args);
+ default:
+ throw new Errors.DeveloperError(
+ `Unknown action for '${this.collection.name}' resource: '${action_name}'`
+ );
+ }
+ }
+ async getResource(
+ context: Context,
+ args: { format?: {}; attachments?: LooseObject }
+ ) {
+ const item = await this.__getResource(context, args);
- async get_resource(context: Context, { format }: { format?: {} }) {
+ let attachments, fieldsWithAttachments;
+ if (args.attachments && typeof args.attachments === "object") {
+ ({ attachments, fieldsWithAttachments } = await assignAttachments(
+ this.app,
+ context,
+ args as AttachmentParams,
+ this.collection,
+ [item]
+ ));
+ } else {
+ attachments = {};
+ fieldsWithAttachments = {};
+ }
+ return new SingleItemResponse({
+ item,
+ attachments,
+ fieldsWithAttachments,
+ });
+ }
+ async __getResource(context: Context, { format }: { format?: {} }) {
const db_entries = await this.app.Datastore.find(
this.collection.name,
{ sealious_id: this.resource_id },
{}
);
if (db_entries[0] === undefined) {
throw new Errors.NotFound(
`${this.collection.name}: id ${this.resource_id} not found`
);
}
- const resource_representation = await this.collection.get_resource_representation(
+ const resource_representation = await this.collection.getResourceRepresentation(
context,
db_entries[0],
format
);
- await this.collection.check_if_action_is_allowed(
+ await this.collection.checkIfActionIsAllowed(
context,
"show",
- resource_representation
+ resource_representation as Item
);
- let attachments, fieldsWithAttachments;
- if (args.attachments && typeof args.attachments === "object") {
- ({ attachments, fieldsWithAttachments } = await assignAttachments(
- app,
- context,
- args,
- this.collection,
- [item]
- ));
- } else {
- attachments = {};
- fieldsWithAttachments = {};
- }
- return new SingleItemResponse({
- item,
- attachments,
- fieldsWithAttachments,
- });
+ return resource_representation as Item;
}
async getChildSubject(key: string) {
if (this.collection.fields[key].type.is_subject) {
return new CollectionFieldSubject(
+ this.app,
this.collection,
this.resource_id,
key
);
}
return null;
}
- async edit_resource(
+ async editResource(
context: Context,
values_to_patch: any,
delete_empty_values: boolean
) {
// replaces just the provided values. Equivalent of PATCH request
+ const resource_representation = await this.__getResource(context, {});
- delete_empty_values =
- delete_empty_values === undefined ? false : delete_empty_values;
-
- let resource_representation: {};
-
- const resource_representation = await this.get_resource(context, {});
- await this.collection.check_if_action_is_allowed(
+ await this.collection.checkIfActionIsAllowed(
context,
"edit",
resource_representation
);
- await this.collection.validate_field_values(
+ await this.collection.validateFieldValues(
context,
delete_empty_values,
values_to_patch,
resource_representation
);
- const encoded_values = await this.collection.encode_field_values(
+ const encoded_values = await this.collection.encodeFieldValues(
context,
values_to_patch,
resource_representation
);
- const query = { _metadata: resource_representation._metadata };
+ const query: LooseObject = {
+ _metadata: resource_representation._metadata,
+ };
query._metadata.last_modified_context = context;
for (const field_name in encoded_values) {
query[field_name] = encoded_values[field_name];
}
const patch_result = await this.app.Datastore.update(
this.collection.name,
- { sealious_id: resource_id },
+ { sealious_id: this.resource_id },
{ $set: query }
);
if (patch_result.result.n !== 1) {
throw new Error("Wrong amount of resources (!=1) modified");
}
- return this.get_resource(context, {});
+ return this.getResource(context, {});
}
-}
-SingleResource.prototype.SingleResource.prototype.__delete_resource = function(
- datastore,
- collection,
- resource_id,
- context,
- args
-) {
- // abstraction seems to be leaking here: should we use context or SuperContext here?
+ async deleteResource(context: Context, args: LooseObject) {
+ const resource_representation = await this.__getResource(context, {});
- return SingleResource.prototype
- .__get_resource(datastore, collection, resource_id, context, {})
- .then(function(resource_representation) {
- return collection.check_if_action_is_allowed(
- context,
- "delete",
- resource_representation
- );
- })
- .then(function() {
- return datastore.remove(
- collection.name,
- {
- sealious_id: resource_id,
- },
- {}
- );
- })
- .then(function(data) {
- return Promise.resolve();
- });
-};
+ await this.collection.checkIfActionIsAllowed(
+ context,
+ "delete",
+ resource_representation
+ );
-SingleResource.prototype.perform_action = function(context, action_name, args) {
- switch (action_name) {
- case "show":
- return this.get_resource(context, args);
- case "edit":
- return this.edit_resource(context, args, false);
- case "replace":
- return this.edit_resource(context, args, true);
- case "delete":
- return this.delete_resource(context, args);
- default:
- throw new Errors.DeveloperError(
- `Unknown action for '${this.collection.name}' resource: '${action_name}'`
- );
+ await this.app.Datastore.remove(
+ this.collection.name,
+ { sealious_id: this.resource_id },
+ true
+ );
}
-};
+}
diff --git a/src/subject/subject-types/single-specification-subject.js b/src/subject/subject-types/single-specification-subject.js
deleted file mode 100644
index 49ee94fe..00000000
--- a/src/subject/subject-types/single-specification-subject.js
+++ /dev/null
@@ -1,32 +0,0 @@
-const Subject = require("../subject.js");
-const Errors = require("../../response/error.js");
-
-const SingleSpecificationsSubject = function (app, collection_name) {
- const actions = {
- show: function (params) {
- const collection = app.ChipManager.get_chip(
- "collection",
- collection_name
- );
- return collection.get_specification(false);
- },
- };
-
- this.perform_action = function (context, action_name, params) {
- try {
- return actions[action_name](params);
- } catch (e) {
- return Promise.reject(
- new Errors.BadSubjectAction(
- `Unknown action for SingleSpecificationsSubject: '${action_name}'`
- )
- );
- }
- };
-};
-
-SingleSpecificationsSubject.prototype = Object.create(Subject.prototype);
-
-SingleSpecificationsSubject.subject_name = "specifications";
-
-module.exports = SingleSpecificationsSubject;
diff --git a/src/subject/subject-types/single-specification-subject.ts b/src/subject/subject-types/single-specification-subject.ts
new file mode 100644
index 00000000..8ba0caf0
--- /dev/null
+++ b/src/subject/subject-types/single-specification-subject.ts
@@ -0,0 +1,35 @@
+import App from "../../app/app";
+import { LeafSubject } from "../subject";
+import * as Errors from "../../response/errors";
+import Context from "../../context";
+import Collection from "../../chip-types/collection";
+import { ShowActionName } from "../../action";
+
+export default class SingleSpecificationsSubject extends LeafSubject {
+ collection_name: string;
+
+ constructor(app: App, collection_name: string) {
+ super(app);
+ this.collection_name = collection_name;
+ }
+ getName() {
+ return "specifications";
+ }
+ async performAction(
+ context: Context,
+ action_name: ShowActionName,
+ params: any
+ ) {
+ if (action_name !== "show") {
+ throw new Errors.BadSubjectAction(
+ `Unknown action for SingleSpecificationsSubject: '${action_name}'`
+ );
+ }
+
+ const collection = this.app.ChipManager.getChip(
+ "collection",
+ this.collection_name
+ ) as Collection;
+ return collection.getSpecification(false);
+ }
+}
diff --git a/src/subject/types.ts b/src/subject/types.ts
index 0c592aad..e635ece6 100644
--- a/src/subject/types.ts
+++ b/src/subject/types.ts
@@ -1,3 +1,13 @@
export type LooseObject = {
[key: string]: any;
};
+
+export type AttachmentParams = {
+ attachments: LooseObject;
+};
+
+export type AssignAttachmentsResult = {
+ attachments: LooseObject;
+ fieldsWithAttachments: LooseObject;
+ items: Array<LooseObject>;
+};
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Tue, Dec 24, 14:02 (1 d, 1 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
557121
Default Alt Text
(53 KB)
Attached To
Mode
rS Sealious
Attached
Detach File
Event Timeline
Log In to Comment