{"id":27392204,"url":"https://github.com/samt/acpjs","last_synced_at":"2025-07-06T01:31:27.287Z","repository":{"id":19605757,"uuid":"22856776","full_name":"samt/acpjs","owner":"samt","description":"Admin Interface Generator for Node.js","archived":false,"fork":false,"pushed_at":"2014-08-11T22:19:26.000Z","size":116,"stargazers_count":4,"open_issues_count":0,"forks_count":1,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-04-13T21:18:14.556Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/samt.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":"license","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-08-11T22:18:52.000Z","updated_at":"2020-07-09T08:27:01.000Z","dependencies_parsed_at":"2022-08-23T20:40:11.769Z","dependency_job_id":null,"html_url":"https://github.com/samt/acpjs","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samt%2Facpjs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samt%2Facpjs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samt%2Facpjs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samt%2Facpjs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/samt","download_url":"https://codeload.github.com/samt/acpjs/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248782256,"owners_count":21160717,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-04-13T21:18:16.815Z","updated_at":"2025-04-13T21:18:17.392Z","avatar_url":"https://github.com/samt.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Admin Control Panel [![Build Status](https://travis-ci.org/samt/acpjs.png)](https://travis-ci.org/samt/acpjs)\n\nData-agnostic admin interface generator for CRUD applications\n\n**Note from Sam**:\nThis idea exists almost totally in concept. There is alpha code being written,\nbut it's very rudimentary at this point and is not guaranteed to work at all.\nI've published to NPM only so that the name is reserved.\n\n## Abstract\n\nThe problem with crud apps is they are all are basically the same, yet we find\nit necessary to always rewrite the same code over and over. We have a set of\nrequirements that remain relatively constant:\n\n- Views Types\n  - Summary (Dashboard) view of data for graphing or general display\n  - Unordered List-view (or table) of items in a collection\n  - Ordred list-view (or table) of items in a collection for sorting\n  - Log views (uneditable display of entries)\n  - Insert/update Item view within a collection\n- Functionality\n  - View all collections in appropriate view types\n  - Validation of data prior to database\n    - Deny badly formed data (email addresses must have an \"@\" sign)\n    - Deny duplicate entries (i.e. duplicate username fields)\n    - Deny baed upon custom, applicaiton-specific criteria\n  - Pagination\n  - Filter/Search\n  - Form controls in editing views\n\nIn addition, we always have data coming in from somewhere. Sometimes it's all\nfrom a database, sometimes it's from flat files, and even sometimes it's from\nthe network. The problem with other frameworks is they are all dependent (or\ncompletely revolve around) on one type of data retrieval, usually a database.\n\n\n## Requirements\n\n- node \u003e= 0.8.0\n- express \u003e= 4.x.x (if using with existing app)\n\n## Installation\n\n```\nnpm install acp\n```\n\n## Usage\n\n### Basic\n\n```javascript\nvar acp = require('acp');\nvar adm = acp();\n\nadm.listen(3000); // identical to express's app.listen()\n```\n\n### With an existing express app\n\nBecause acp is an express application, we can simply mount it on top of another\napplication currently being set up. This allows you to write your own custom\nuser-facing front-end and acp to mange your data behind.\n\n```javascript\nvar adm = acp();\nvar app = express();\n\n// ...\n\napp.use('/admin', adm); // mount on \"admin\"\n\napp.listen(3000); // do not listen with adm\n```\n\n### Define Collections\n\nA collection is any set of data that you wish to manage. You can pull it from\nyour database, flat files, remote systems, etc.\n\n```javascript\nvar acp = require('acp');\nvar adm = acp();\n\nvar userCollection = adm.define('Users', {\n  primaryKey: 'id', // defaults to 'id'\n  mount: 'auto', // defaults to 'auto. Mounts CRUD pages on url-safe version of the collection name\n  create: createUser,// function(record, cb) , cb(err, record),\n  readOne: getOneUser,// function ( primaryKey ),\n  read: getUsers, // function( { start: 0, limit: 10 }),\n  update: updateUser, // function ( primaryKey, record ),\n  delete: deleteUser, // function ( primaryKey | record ), cb(Error err, bool deleted)\n  count: countUsers, // function (cb), cb(Error err, number count),\n  fields: {\n    id: { type: 'auto', primary: true  }, // auto_int sets nice defaults\n    slug: { type: 'string', filter: [ ACP.Filter.urlSafe,  },\n    name: { type: 'string', validate: function (n) {\n      return /[a-z0-9_\\-]{4,10}/i.test(n);\n    }},\n    email: { type: 'string', validate: [ ACP.Validate.email, function (email, next) { // validate can take an array\n      db.query('SELECT * FROM users WHERE email = ?', [ email ], function (err, rows) {\n        if (err) throw err;\n        next( !rows.length );\n      });\n    }]}\n  }\n});\n\nadm.listen(3000);\n```\n\n### Arbitrary Page\n\n```javascript\nvar acp = require('acp');\nvar admin = acp();\n\n// same arguments as .define()\nadmin.page('Dashboard', {\n  mount: '/',\n  widgets: [      // table of widgets\n    [w1, w2, w3],\n    [w4, w5]\n  ]\n});\n```\n\n## API\n\n### Class `ACP`\n\nThis is the main class of the Admin Control Panel Interface.\n\nExtends `EventEmiter`\n\n#### `ACP.define(name, options)`\n\nDefines a collection of data to keep track of\n\n- `name` String: Name of the collection, usually pular\n- `options` Object:\n  - `primaryKey` String: Field name of the primary key.\n  - `disableCrud` Boolean: (optional) Disable crud functionality? default:\n     false\n  - `mount` String: (optional) 'auto' to mount on root with generated slug\n     (default), '/routname' otherwise.\n  - `orderedBy` String: (optional) Field name to order by if ordered list.\n    false or null otherwise. Default: false\n  - `create` Function: callback to store a new record.\n     Params: `record`, `callback(err, record`)\n  - `update` Function: callback to update a record.\n    Params: `record`, `callback (err, record)`\n  - `readOne` Function: callback to read one record.\n    Params: `primaryKey`, `callback(err, record)`\n  - `read` Function: callback to get the collection.\n    Params: `filter` Array\u003c _ACP.Filter_ \u003e, `callback(err, recs)`\n  - `delete` Function callback to delete a record.\n    Params: `primaryKey`, `callback(err, deleted?)`\n  - `count` Function callback to count all records.\n    Params: `Array\u003cACP.Filter\u003e, callback(err, count)`\n  - `fields` Array\u003cACP.Field\u003e: Field List\n\n#### `ACP.page(name, options)`\n\n- `name` String: Name of the page\n- `options` Object:\n  - `mount` String: (optional) 'auto' to mount on root with generated slug\n    (default), '/routname' otherwise.\n  - `widgets` Array\u003c Array\u003cACP.Widget\u003e \u003e: Widget table. Outer Array: Rows;\n    inner Array: Columns\n\n### Class `ACP.Filter`\n\nPlain object.\n\n- `field_name` Object:\n  - `eq` Mixed: Returned values must be equal.\n  - `ne` Mixed: Returned values must not be equal\n  - `gt` Mixed: Returned values must be greater than\n  - `lt` Mixed: Returned values must be less than\n  - `gte` Mixed: Returned values must be greater than or equals\n  - `lte` Mixed: Returns values must be less than or equals\n\nFor example:\n\n```javascript\n{ email: { ne: 'user@example.com '} }\n```\n\n### Class `ACP.Field`\n\nIs just a plain object with the following properties:\n\n- `type` String: 'auto', 'number', 'string', 'text', 'bool', 'datetime', 'date'\n- `primary` Boolean: Is primary key? Default: false. Overridden if primaryKey is set in collection\n- `editable` Boolean: Can the user edit this field?\n- `filter` Function|Array\u003cFunction\u003e: Filter (modify values) of the record\n- `validate` Function|Array\u003cFunction\u003e: Validate a field. performed AFTER filters\n- `options` Array\u003cString\u003e|Plain Object: (Optional) if this is a dropdown/radio/checkbox list, any\n- `input` Flag: ACP.TEXT, ACP.PASSWORD, ACP.EMAIL, ACP.DROPDOWN,\n  ACP.DROPDOWN_MULI, ACP.DATE_PICKER, ACP.DATETIME_PICKER, ACP.RADIO, ACP.CHECKBOX\n\n### Class `ACP.Widget`\n\nPlain object with properties:\n\n- `size` Number: 1-12, grid width. Default: 3\n- `template` String: Either a built in template or user-defined custom path\n- `read` Function: Callback to read data to populate the widget width\n- `title` String: Widget title\n\n### Class `ACP.Filter`\n\nContains the functions to validate automatically\n\n- `ACP.Filter.urlSafe`: Makes a URL safe string (RFC 3986)\n- `ACP.Filter.slug`: Makes the string into a clean URL-safe sting [a-z0-9\\-_]\n- `ACP.Filter.boolean`: Converts strings from \u003cform\u003es \"1\" or \"0\" into true boolean values\n- `ACP.Filter.datetime`: Parses the date to an ISO 8601 compliant string\n- `ACP.Filter.jsDate`: Turns field into a JavaScript `Date` object\n\n### Class `ACP.Validate`\n\nContains the functions to validate automatically\n\n- `ACP.Validate.email`: Validates an email\n- `ACP.Validate.url`: Validates a URL\n- `ACP.Validate.foreignKey(collectionName)`: Validates that there exists a primary key in the collection\n- `ACP.Validate.unique`: Validates that there are no records with the same value in this field\n\n## Concepts\n\n### Data Agnostic\n\nWhen you create a collection, you specify where the data comes from, how it is\ninserted, how it is updated, and what validations need to take place. There are\nvalidation macros you can use for extremely common validations, such as email\naddresses, url-safe strings, and checking for duplicates in other models. You\nmay also specify filters which take data pre-validation and operate on it.\n\n### Reusable interfaces\n\nYou don't have to write your own views. You specify where and when you\nwant things to appear and it just makes it for you.\n\n### Built with express\n\nTo make hooking it into your stack easier, it is made to either piggy-back off\nof your current express application or you can specify to run it standalone.\n\n### Security\n\nBecause this is going to give a user a one-stop-shop for mangling the database,\nwe wanted to ensure only the people you give access will have access. There are\nfew layers of security we've added on:\n\n- Specify what session data must be set before access\n- Optionally enable \"reauth\" if the user hasn't been active within a\n  specified amount of time\n- Session keys in the URL as query vars for additional session validation\n- Referrer checking\n\n## License\n\nThe MIT License\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsamt%2Facpjs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsamt%2Facpjs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsamt%2Facpjs/lists"}