bloggify-cli
Version:
We make publishing easy.
943 lines (670 loc) • 20.5 kB
Markdown
# Bloggify Utils
The [`bloggify-utils`](https://github.com/Bloggify/bloggify-utils) module is a utility module that provides some useful functions and objects for Bloggify applications. It provides util `actions`, authentication handlers, CSS assets, common error codes, geolocation utilities, common models, React components and common `services`.
## Installation
```sh
npm i -S bloggify-utils
```
## Usage
```js
// In the app/actions/db.js
module.exports = require("bloggify-utils/lib/actions/db");
```
## Configuration
The following configuration is mandatory:
- `imports.dir`: The directory where the imported files will be saved (such as csv files).
- `uploads.dir`: The directory where the uploaded files will be saved.
## Errors
- MISSING_DATA (`<1>`)
- NOT_AUTHORIZED
- NOT_FOUND
- VALIDATION_FAILED (`<1>`)
## Models
### ActivityLogItem
- `action`: `STRING`
- `model`: `STRING`
- `item_id`: `INTEGER`
- `metadata`: `JSON`
- `actor_email`: `STRING`
### File
- `originalname`: `STRING
- `encoding`: `STRING
- `mimetype`: `STRING
- `filename`: `STRING
- `extension`: `STRING
- `size`: `INTEGER`
### Settings
- `settings`: `JSON`
## Services
### `ActivityLog`
#### `ActivityLog.log(data)`
Logs an activity.
```js
Bloggify.services.ActivityLog.log.call(ctx, {
action: "create",
model: "User",
item_id: 42,
metadata: {
name: "John"
}
})
```
#### `ActivityLog.getLogs()`
Gets a list of activity logs.
```js
const logs = await Bloggify.services.ActivityLog.getLogs({
where: {
action: "create"
}
});
```
### `Auth`
#### `Auth.beforeActionsRequestRole(role)`
Verifies if the user has the required role to access the action.
```js
exports.before = Bloggify.services.Auth.beforeActionsRequestRole("admin")
```
### `Database`
#### `Database.importCsv(data)`
Imports a CSV file.
##### Params
- `data` (Object): An object containing:
- `model` (String): The model name.
- `path` (String): The path to the CSV file.
##### Example
```js
Bloggify.services.Database.importCsv({
model: "User",
path: "/path/to/file.csv"
})
```
#### `Database.exportCsv(data)`
Export the records of a model to a CSV file.
##### Params
- `data` (Object): An object containing:
- `model` (String): The model name.
- `path` (String): The path to the CSV file.
##### Example
```js
const exportedFilePath = await Bloggify.services.Database.exportCsv({
model: "User"
})
```
#### `Database.query(data)`
Query the database.
##### Params
- `data` (Object): An object containing:
- `page` (Number): The page number.
- `perPage` (Number): The number of items per page.
- `search` (String): The search query.
- `model` (String): The model name.
- `sorting` (Array): An array of objects containing:
- `where` (Object): The where condition.
- `include` (Array): The included models.
##### Returns
An object containing:
- `count` (Number): The total number of records.
- `rows` (Array): The records.
- `pageCount` (Number): The total number of pages.
- `model` (String): The model name.
##### Example
```js
const data = await Bloggify.services.Database.query({
model: "User",
page: 1,
perPage: 10,
search: "John",
where: { age: { $gt: 42 } },
sorting: [
{ field: "name", direction: "ASC" }
],
});
```
#### `Database.beforeCreateOrUpdate(data)`
A hook that is called before creating or updating a record.
##### Example
```js
Bloggify.services.Database.beforeCreateOrUpdate = async ({ instance, operationType, model, ctx }) => {
// ...
});
```
#### `Database.delete(data)`
Delete a record from the database.
##### Params
- `data` (Object): An object containing:
- `model` (String): The model name.
- `id` (Number): The record ID.
##### Example
```js
const deletedRecord = await Bloggify.services.Database.delete({
model: "User",
id: 42
});
```
#### `Database.getExistingColumnValues(data)`
Get the existing column values.
##### Params
- `data` (Object): An object containing:
- `model` (String): The model name.
- `column` (String): The column name.
##### Returns
An object containing:
- `values` (Array): The existing column values.
- `model` (String): The model name.
- `column` (String): The column name.
##### Example
```js
const existingColumnValues = await Bloggify.services.Database.getExistingColumnValues({
model: "User",
column: "name"
});
```
#### `Database.getSchema(data)`
Get the schema of a model.
##### Params
- `data` (Object): An object containing:
- `model` (String): The model name.
##### Returns
An object containing:
- `model` (String): The model name.
- `fields` (Array): The model fields. Each field is an object containing:
- `name` (String): The field name.
- `type` (String): The field type.
- `field_metadata` (Object): The field metadata.
- `privileges` (Object): The field privileges.
- `read` (Boolean): If the field can be read.
- `write` (Boolean): If the field can be written.
##### Example
```js
const schema = await Bloggify.services.Database.getSchema({
model: "User"
});
```
#### `Database.bulkDelete(data)`
Delete multiple records.
##### Params
- `data` (Object): An object containing:
- `model` (String): The model name.
- `ids` (Array): The record IDs to delete.
##### Example
```js
const deletedItems = await Bloggify.services.Database.bulkDelete({
model: "User",
ids: [ 42, 43, 44 ]
});
```
### `Settings`
#### Events
The following events are emitted:
- `settings_init`: Emitted when the settings are initialized.
- `settings_updated`: Emitted when the settings are updated.
#### `Settings.set(data)`
Set the settings.
##### Params
- `data` (Object): The settings data.
##### Example
```js
Bloggify.services.Settings.set({
siteName: "My Site"
});
```
#### `Settings.get()`
Get the settings.
##### Returns
The settings object.
##### Example
```js
const settings = await Bloggify.services.Settings.get();
```
#### `Settings.saveSettingsToDisk()`
Save the settings to disk.
##### Example
```js
await Bloggify.services.Settings.saveSettingsToDisk();
```
#### `Settings.loadSettingsFromDisk()`
Load the settings from disk.
##### Example
```js
await Bloggify.services.Settings.loadSettingsFromDisk();
```
### `Util`
#### `Util.handleFriendlyError(fn)`
Handle friendly errors.
##### Params
- `fn` (Function): The function to be executed.
##### Returns
A promise that resolves to the result of the function.
##### Example
```js
const result = await Bloggify.services.Util.handleFriendlyError(() => {
// ...
});
```
## Actions
Below are the available actions. These actions are used in the `app/actions` directory.
### `db` – Database Actions
#### `db.query`
Queries a model. The body should contain the following fields:
- `model` (string): The model name.
- `page` (number): The page number.
- `perPage` (number): The number of items per page.
- `search` (string): The search term.
- `sorting` (object): The sort object.
- `where` (object): The where object.
- `include` (array): The included models.
```js
const Actions = require("bloggify/actions")
Actions.post("db.query", {
model: "User",
page: 1,
perPage: 10,
search: "John",
where: { age: { $gt: 42 } },
})
```
#### `db.save`
Saves a record. The body should contain the following fields:
- `model` (string): The model name.
- `data` (object): The data to save. If the `id` is present, the record will be updated. Otherwise, it will be created.
```js
const Actions = require("bloggify/actions")
Actions.post("db.save", {
model: "User",
data: {
name: "John",
age: 42
}
})
```
#### `db.getExistingColumnValues`
Gets the existing column values uniquely. The body should contain the following fields:
- `model` (string): The model name.
- `column` (string): The column name.
```js
const Actions = require("bloggify/actions")
Actions.post("db.getExistingColumnValues", {
model: "User",
column: "name"
})
```
#### `db.delete`
Deletes a record. The body should contain the following fields:
- `model` (string): The model name.
- `id` (number): The record ID.
```js
const Actions = require("bloggify/actions")
Actions.post("db.delete", {
model: "User",
id: 42
})
```
#### `db.bulkDelete`
Deletes multiple records. The body should contain the following fields:
- `model` (string): The model name.
- `ids` (array): The record IDs.
```js
const Actions = require("bloggify/actions")
Actions.post("db.bulkDelete", {
model: "User",
ids: [ 42, 43, 44 ]
})
```
#### `db.getSchema`
Gets the schema for a model. The body should contain the following fields:
- `model` (string): The model name.
```js
const Actions = require("bloggify/actions")
Actions.post("db.getSchema", {
model: "User"
})
```
#### `db.exportCsv`
Exports a model to a CSV file. The body should contain the following fields:
- `model` (string): The model name.
The form should post the data to the `Actions.url("db.exportCsv")` route and the CSV file will be downloaded.
#### `db.importCsv`
Imports a CSV file. The body should contain the following fields:
- `model` (string): The model name.
- `file` (file): The CSV file.
### `files` – File Actions
#### `files.fileUpload`
Uploads a file. The body should contain the following fields:
- `file` (file): The file to upload.
#### `files.fileList`
Lists files.
```js
Actions.get("files.fileList").then(files => {
// ...
})
```
#### `files.fileDelete`
Deletes a file. The body should contain the following fields:
- `id` (string): The file ID.
#### `files.fileFetch`
Fetches a file. The body should contain the following fields:
- `id` (string): The file ID, or
- `path` (string): The file path.
### `settings` – Settings Actions
#### `settings.get`
Gets the settings.
#### `settings.set`
Sets the settings. The body should contain the following fields:
- `settings` (object): The settings object.
## Authentication Handlers
### `hasRole (expectedRole, existingRoles)`
Checks if the user has the expected role.
```js
const { hasRole } = require("bloggify-utils/lib/auth")
if (hasRole("admin", user.roles)) {
// user is admin
}
```
## CSS Assets
### `application-container.css`
The CSS for the application container (useful for dashboards).
```css
@import "bloggify-utils/lib/assets/css/application-container.css";
```
### Geolocation Utilities
#### `countries.js`
The list of countries.
```js
const {
// All countries
COUNTRIES,
// EU countries
EU_COUNTRIES,
// Check if a country is a certain country
countryIs
} = require("bloggify-utils/lib/geo/countries")
if (countryIs("US", "USA")) {
// check aliases
}
if (countryIs("Romania", "RO")) {
// check aliases
}
```
## React Components
### `createRoot(App)`
Creates a root component.
#### Params
- `App` (React Component): The root component to render.
#### Example
```jsx
const createRoot = require("bloggify-utils/lib/react/create-root");
const App = () => {
return <div>Hello World</div>;
};
createRoot(App);
```
### CreatableSelect
Creates a select component that allows creating new options.
#### Params
- `value` (String): The current value.
- `onChange` (Function): The change handler.
- `schemaField` (Object): The schema field.
- `record` (Object): The current record.
- `disabled` (Boolean): If the component is disabled.
- `model` (String): The model name.
#### Example
```jsx
<CreatableSelect
value={value}
onChange={onChange}
schemaField={schemaField}
record={record}
disabled={disabled}
model="User"
/>
```
### DashboardCard
A dashboard card component.
#### Params
- `title` (String): The card title.
- `image` (String): The card image.
- `link` (String): The card link.
- other props to be passed to the card.
#### Example
```jsx
<DashboardCard
title="My Card"
image="/path/to/image.jpg"
link="/path/to/page"
/>
```
### EntityToChoose
A component to choose an entity.
#### Params
- `model` (String): The model name.
- `onChange` (Function): The change handler.
- `idField` (String): The ID field.
- `value` (String): The current value.
- `filterQuery` (Function): The filter query.
- `disabled` (Boolean): If the component is disabled.
- `formatLabel` (Function): The label formatter.
#### Example
```jsx
<EntityToChoose
model="User"
onChange={onChange}
idField="id"
value={value}
filterQuery={filterQuery}
disabled={disabled}
formatLabel={formatLabel}
/>
```
### FileInput
A file input component.
#### Params
- `label` (String): The label.
- `uploadAction` (String): The upload action.
- `fileUploaded` (Function): The file uploaded handler.
- `mediaPath` (String): The media path.
#### Example
```jsx
<FileInput
label="Upload a file"
uploadAction={Actions.url("files.fileUpload")}
fileUploaded={data => console.log(data)}
mediaPath="/media"
/>
```
### IframeWebView
An iframe web view component.
#### Params
- `content` (String): The content.
#### Example
```jsx
<IframeWebView content="<h1>Hello World</h1>" />
```
### ModelTable
A table component for a model.
#### Params
- `model` (String): The model name.
- `tableColumns` (Array): The table columns to display.
- `listSlug` (String): The list slug.
- `defaultSorting` (Array): The default sorting.
- `bulkDelete` (Boolean): If bulk delete is enabled.
- `onRowSelectionChange` (Function): The row selection change handler.
- `where` (Object): The where condition.
- `include` (Array): The included models.
- `isLinkedTable` (Boolean): If the table is linked.
#### Example
```jsx
<ModelTable
model="User"
tableColumns={[
"name",
"age",
]}
listSlug="users"
defaultSorting={[{ id: "name", desc: true }]}
bulkDelete
onRowSelectionChange={selectedRows => console.log(selectedRows)}
where={{ age: { $gt: 42 } }}
include={[ "Role" ]}
isLinkedTable
/>
```
### Page Alerts
A component to display page alerts.
#### Example
```jsx
const { PageAlert, AccessDeniedAlert, NotImplementedAlert } = require("bloggify-utils/lib/react/page-alerts");
<PageAlert
title="Title"
description="Description"
/>
<AccessDeniedAlert />
<NotImplementedAlert />
```
### Spinner
A spinner component.
#### Example
```jsx
const { Spinner } = require("bloggify-utils/lib/react/spinner");
<Spinner />
```
### UserToChoose
A component to choose a user.
#### Params
All that the `EntityToChoose` component accepts.
#### Example
```jsx
<UserToChoose
onChange={onChange}
disabled={disabled}
formatLabel={formatLabel}
/>
```
### UniversalCrudRecord
A universal CRUD record component.
#### Params
- `model` (String): The model name.
- `listSlug` (String): The list slug.
- `renderer` (Function): The renderer function. Default: `defaultRenderer`.
- `allowedFields` (Array): The allowed fields.
- `EditRecordForm` (React Component): The edit record form component. Default: `EditRecordForm`.
- `viewTitle` (String): The view title.
- `customButtonGroup` (React Component): The custom button group.
- `availableControls` (Array): The available controls. Default: `["create", "edit", "delete", "export_csv", "import_csv"]`.
- `isLinkedTable` (Boolean): If the table is linked.
#### Example
```jsx
<UniversalCrudRecord
model="User"
listSlug="users"
renderer={record => <div>{record.name}</div>}
allowedFields={[ "name", "age" ]}
EditRecordForm={EditRecordForm}
viewTitle="Users"
customButtonGroup={CustomButtonGroup}
availableControls={[ "create", "edit", "delete", "export_csv", "import_csv" ]}
/>
```
### UniversalModelTable
A universal model table component.
#### Params
- `model` (String): The model name.
- `listTitle` (String): The list title.
- `listSlug` (String): The list slug.
- `afterHeader` (React Component): The after header component.
- `availableControls` (Array): The available controls. Default: `["create", "edit", "delete", "export_csv", "import_csv"]`.
- `isLinkedTable` (Boolean): If the table is linked.
#### Example
```jsx
<UniversalModelPage
model="Section"
listTitle="Sections"
listSlug="sections"
viewTitle={c => <span>Section {c ? <strong>{c.full_name}</strong> : null}</span>}
tableColumns={[
{ header: "Name", accessorKey: "full_name", cell: s => <SectionBadge section={s.row.original} /> },
{ header: "Status", accessorKey: "status" },
{ header: "Year", accessorKey: "year" },
]}
linkedTables={[
{
content: (c) => <SectionCourseworkEditor section={c} />,
},
{
model: "Coursework",
listTitle: "Coursework",
listSlug: "coursework",
viewTitle: (c) => <span></span>,
tableColumns: [
{
header: "Name", accessorKey: "coursework.name", cell: s => {
return <LinkContainer to={`/dashboard/section-coursework/${s.row.original.id}`}>
<a>{s.row.original.coursework.name}</a>
</LinkContainer>
}
},
{ header: "Description", accessorKey: "coursework.description" },
{ header: "Value", accessorKey: "value" },
{
header: "Actions",
accessorKey: "section_coursework",
cell: s => {
return <ButtonToolbar>
<ButtonGroup className="me-2">
<LinkContainer
to={{
pathname: "/dashboard/instructor-tools/grade-coursework",
search: `?sectionCourseworkId=${s.row.original.id}§ionId=${s.row.original.section_id}`
}}
>
<Button bsStyle="primary" size="sm">
<i className="fa fa-pencil" /> Grade
</Button>
</LinkContainer>
</ButtonGroup>
<SectionCourseworkGradeProgress sectionCoursework={s.row.original} />
</ButtonToolbar>
}
},
],
include: [{
model: "Coursework",
as: "coursework",
}],
through: "SectionCoursework",
filter: s => ({ section_id: s.id }),
availableControls: []
},
{
model: "Student",
listTitle: "Students",
listSlug: "students",
viewTitle: (c) => <span></span>,
tableColumns: [
{ header: "Number", accessorKey: "student.number", cell: s => <UserBadge user={s.row.original.student} /> },
{ header: "Name", accessorKey: "student.full_name", cell: s => <LinkContainer to={`/dashboard/student-attended-sections/${s.row.original.id}`}><a>{s.row.original.student.full_name}</a></LinkContainer> },
{ header: "Email", accessorKey: "student.email" },
{ header: "Current Grade", accessorKey: "grade", cell: s => s.row.original.grade ? `${s.row.original.grade} / 100` : "N/A" }
],
include: [{
model: "User",
as: "student",
}],
through: "StudentAttendedSection",
filter: s => ({ section_id: s.id }),
availableControls: [{
content: () => {
if (!hasRole(["ADMIN", "SUPERADMIN"], BloggifyPage.user.roles)) {
return null
}
const sectionId = location.pathname.split("/").pop()
return <a href={`/dashboard/student-attended-sections/new?data.section_id=${sectionId}`} target="_blank">
<Button variant="primary" className="ml-2">
<i className="fas fa-plus-circle"></i> Add Student
</Button>
</a>
}
}]
}
]}
{...otherProps}
/>
```