diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..f1f96a5
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,7 @@
+root = true
+
+[*]
+end_of_line = lf
+insert_final_newline = false
+indent_style = tab
+indent_size = 2
diff --git a/.eslintrc.backend.js b/.eslintrc.backend.js
new file mode 100644
index 0000000..ed1c7a9
--- /dev/null
+++ b/.eslintrc.backend.js
@@ -0,0 +1,8 @@
+module.exports = {
+ $schema: 'https://json.schemastore.org/eslintrc',
+ env: {
+ es6: true,
+ node: true,
+ },
+ extends: ['eslint:recommended', 'plugin:node/recommended', 'prettier'],
+};
diff --git a/.eslintrc.frontend.js b/.eslintrc.frontend.js
new file mode 100644
index 0000000..f1f308c
--- /dev/null
+++ b/.eslintrc.frontend.js
@@ -0,0 +1,26 @@
+module.exports = {
+ $schema: 'https://json.schemastore.org/eslintrc',
+ parser: '@babel/eslint-parser',
+ env: {
+ browser: true,
+ es6: true,
+ },
+ plugins: ['react'],
+ extends: ['eslint:recommended', 'plugin:react/recommended', 'prettier'],
+ parserOptions: {
+ requireConfigFile: false,
+ ecmaVersion: 2018,
+ ecmaFeatures: {
+ jsx: true,
+ },
+ sourceType: 'module',
+ babelOptions: {
+ presets: ['@babel/preset-react'],
+ },
+ },
+ settings: {
+ react: {
+ version: 'detect',
+ },
+ },
+};
diff --git a/.eslintrc.js b/.eslintrc.js
new file mode 100644
index 0000000..3fd35f4
--- /dev/null
+++ b/.eslintrc.js
@@ -0,0 +1,28 @@
+const frontendESLint = require('./.eslintrc.frontend.js');
+const backendESLint = require('./.eslintrc.backend.js');
+
+module.exports = {
+ $schema: 'https://json.schemastore.org/eslintrc',
+ parserOptions: {
+ ecmaVersion: 2018,
+ },
+ rules: {
+ indent: ['error', 'tab'],
+ 'linebreak-style': ['error', 'unix'],
+ quotes: ['error', 'single'],
+ semi: ['error', 'always'],
+ },
+ globals: {
+ strapi: 'readonly',
+ },
+ overrides: [
+ {
+ files: ['server/**/*'],
+ ...backendESLint,
+ },
+ {
+ files: ['admin/**/*'],
+ ...frontendESLint,
+ },
+ ],
+};
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..777c307
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,99 @@
+# From https://github.com/Danimoth/gitattributes/blob/master/Web.gitattributes
+
+# Handle line endings automatically for files detected as text
+# and leave all files detected as binary untouched.
+* text=auto
+
+#
+# The above will handle all files NOT found below
+#
+
+#
+## These files are text and should be normalized (Convert crlf => lf)
+#
+
+# source code
+*.php text
+*.css text
+*.sass text
+*.scss text
+*.less text
+*.styl text
+*.js text eol=lf
+*.coffee text
+*.json text
+*.htm text
+*.html text
+*.xml text
+*.svg text
+*.txt text
+*.ini text
+*.inc text
+*.pl text
+*.rb text
+*.py text
+*.scm text
+*.sql text
+*.sh text
+*.bat text
+
+# templates
+*.ejs text
+*.hbt text
+*.jade text
+*.haml text
+*.hbs text
+*.dot text
+*.tmpl text
+*.phtml text
+
+# git config
+.gitattributes text
+.gitignore text
+.gitconfig text
+
+# code analysis config
+.jshintrc text
+.jscsrc text
+.jshintignore text
+.csslintrc text
+
+# misc config
+*.yaml text
+*.yml text
+.editorconfig text
+
+# build config
+*.npmignore text
+*.bowerrc text
+
+# Documentation
+*.md text
+LICENSE text
+AUTHORS text
+
+
+#
+## These files are binary and should be left untouched
+#
+
+# (binary is a macro for -text -diff)
+*.png binary
+*.jpg binary
+*.jpeg binary
+*.gif binary
+*.ico binary
+*.mov binary
+*.mp4 binary
+*.mp3 binary
+*.flv binary
+*.fla binary
+*.swf binary
+*.gz binary
+*.zip binary
+*.7z binary
+*.ttf binary
+*.eot binary
+*.woff binary
+*.pyc binary
+*.pdf binary
diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml
new file mode 100644
index 0000000..70abde2
--- /dev/null
+++ b/.github/workflows/npm-publish.yml
@@ -0,0 +1,31 @@
+# This workflow will publish a package to NPM when a release is created
+
+name: Publish to NPM
+
+on:
+ release:
+ types: [published]
+
+jobs:
+ publish-npm:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout branch
+ uses: actions/checkout@v2
+
+ - name: Install Node v14
+ uses: actions/setup-node@v2
+ with:
+ node-version: '14.x'
+ registry-url: 'https://registry.npmjs.org'
+
+ - name: Install Yarn
+ run: npm install -g yarn
+
+ - name: Clean install deps
+ run: yarn install --frozen-lockfile
+
+ - name: Publish to NPM
+ run: npm publish
+ env:
+ NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ee5654a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+# Don't check auto-generated stuff into git
+coverage
+node_modules
+stats.json
+
+# Cruft
+.DS_Store
+npm-debug.log
+.idea
diff --git a/.npmignore b/.npmignore
new file mode 100644
index 0000000..646f218
--- /dev/null
+++ b/.npmignore
@@ -0,0 +1,22 @@
+# npm
+npm-debug.log
+
+# git
+.git
+.gitattributes
+.gitignore
+
+# vscode
+.vscode
+
+# RC files
+.eslintrc.js
+.eslintrc.backend.js
+.eslintrc.frontend.js
+.prettierrc.json
+
+# config files
+.editorconfig
+
+# github
+.github
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000..483a9c4
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1 @@
+package-lock.json
\ No newline at end of file
diff --git a/.prettierrc.json b/.prettierrc.json
new file mode 100644
index 0000000..a866370
--- /dev/null
+++ b/.prettierrc.json
@@ -0,0 +1,11 @@
+{
+ "$schema": "http://json.schemastore.org/prettierrc",
+ "trailingComma": "es5",
+ "tabWidth": 2,
+ "semi": true,
+ "singleQuote": true,
+ "useTabs": true,
+ "arrowParens": "always",
+ "endOfLine": "lf",
+ "printWidth": 100
+}
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..bbeb617
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022 @ComfortablyCoding
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..a7fe1cc
--- /dev/null
+++ b/README.md
@@ -0,0 +1,59 @@
+# strapi-plugin-notes
+
+A plugin for [Strapi](https://github.com/strapi/strapi) that provides the ability to add notes to entity records.
+
+## Requirements
+
+The installation requirements are the same as Strapi itself and can be found in the documentation on the [Quick Start](https://strapi.io/documentation/developer-docs/latest/getting-started/quick-start.html) page in the Prerequisites info card.
+
+### Supported Strapi versions
+
+- v4.x.x
+
+**NOTE**: While this plugin may work with the older Strapi versions, they are not supported, it is always recommended to use the latest version of Strapi.
+
+## Installation
+
+```sh
+npm install strapi-plugin-notes
+```
+
+**or**
+
+```sh
+yarn add strapi-plugin-notes
+```
+
+## Configuration
+
+The plugin configuration is stored in a config file located at `./config/plugins.js`.
+
+```javascript
+module.exports = ({ env }) => ({
+ 'entity-notes': {
+ enabled: true,
+ },
+});
+```
+
+**IMPORTANT NOTE**: Make sure any sensitive data is stored in env files.
+
+## Usage
+
+Once the plugin has been installed, configured a notes section will be added to the `informations` sections of the edit view for all content types.
+
+### Adding a note
+
+Navigate to the entity record that a note needs to be added to and click the `Add new note` text button. A modal will appear with input areas to add the note information. Once completed select save to create the note. Clicking the `x` icon on the top right or cancel on the bottom left will abort the note creation.
+
+### Editing a note
+
+Navigate to the entity record that the note belongs to, and find the note in the notes list. Click on the pen can icon and a modal will appear with input areas to add the note information. Once completed select save to permanently save any changes that have been made. Clicking the `x` icon on the top right or cancel on the bottom left will abort all edits.
+
+### Deleting a note
+
+Navigate to the entity record that the note belongs to, and find the note in the notes list. Click on the trash can icon and the record will be deleted.
+
+## Bugs
+
+If any bugs are found please report them as a [Github Issue](https://github.com/ComfortablyCoding/strapi-plugin-notes/issues)
diff --git a/admin/src/components/Initializer/index.js b/admin/src/components/Initializer/index.js
new file mode 100644
index 0000000..322df44
--- /dev/null
+++ b/admin/src/components/Initializer/index.js
@@ -0,0 +1,26 @@
+/**
+ *
+ * Initializer
+ *
+ */
+
+import { useEffect, useRef } from 'react';
+import PropTypes from 'prop-types';
+import { pluginId } from '../../pluginId';
+
+const Initializer = ({ setPlugin }) => {
+ const ref = useRef();
+ ref.current = setPlugin;
+
+ useEffect(() => {
+ ref.current(pluginId);
+ }, []);
+
+ return null;
+};
+
+Initializer.propTypes = {
+ setPlugin: PropTypes.func.isRequired,
+};
+
+export default Initializer;
diff --git a/admin/src/components/NoteCreateModal/index.js b/admin/src/components/NoteCreateModal/index.js
new file mode 100644
index 0000000..3565c9d
--- /dev/null
+++ b/admin/src/components/NoteCreateModal/index.js
@@ -0,0 +1,100 @@
+import React, { useState } from 'react';
+import PropTypes from 'prop-types';
+import { useMutation, useQueryClient } from 'react-query';
+import {
+ ModalLayout,
+ ModalBody,
+ ModalHeader,
+ ModalFooter,
+} from '@strapi/design-system/ModalLayout';
+import { TextInput } from '@strapi/design-system/TextInput';
+import { Textarea } from '@strapi/design-system/Textarea';
+import { Button } from '@strapi/design-system/Button';
+import { Typography } from '@strapi/design-system/Typography';
+import { Stack } from '@strapi/design-system/Stack';
+import { requestPluginEndpoint } from '../../utils/requestPluginEndpoint';
+
+const setInitialState = (note = {}) => ({
+ ModalTitle: note.id ? 'Edit Note' : 'Create Note',
+ title: note.title || '',
+ content: note.content || '',
+});
+
+const NoteModalCreate = ({ toggleModal, note = {}, entity }) => {
+ const [values, setValues] = useState(setInitialState(note));
+ const queryClient = useQueryClient();
+
+ const handleNoteUpsert = () => {
+ const method = note.id ? 'PUT' : 'POST';
+ let endpoint = 'notes';
+ if (note.id) {
+ endpoint += '/' + note.id;
+ }
+ requestPluginEndpoint(endpoint, {
+ method,
+ body: {
+ title: values.title,
+ content: values.content,
+ entityId: entity.id,
+ entitySlug: entity.slug,
+ },
+ });
+ toggleModal();
+ };
+
+ const updateState = (key, value) => {
+ setValues((prev) => ({
+ ...prev,
+ [key]: value,
+ }));
+ };
+
+ const mutation = useMutation(handleNoteUpsert, {
+ onSuccess: () => {
+ queryClient.invalidateQueries('entity-notes');
+ },
+ });
+
+ return (
+
+
+
+ {values.ModalTitle}
+
+
+
+
+ updateState('title', e.target.value)}
+ defaultValue={values.title}
+ />
+
+
+
+
+ Cancel
+
+ }
+ endActions={}
+ />
+
+ );
+};
+
+NoteModalCreate.propTypes = {
+ toggleModal: PropTypes.func.isRequired,
+ note: PropTypes.object,
+ entity: PropTypes.object.isRequired,
+};
+
+export { NoteModalCreate };
diff --git a/admin/src/components/NoteListItem/index.js b/admin/src/components/NoteListItem/index.js
new file mode 100644
index 0000000..9fbe495
--- /dev/null
+++ b/admin/src/components/NoteListItem/index.js
@@ -0,0 +1,57 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Box } from '@strapi/design-system/Box';
+import { Flex } from '@strapi/design-system/Flex';
+import Trash from '@strapi/icons/Trash';
+import Pencil from '@strapi/icons/Pencil';
+import { Typography } from '@strapi/design-system/Typography';
+import { IconButton, IconButtonGroup } from '@strapi/design-system/IconButton';
+import { requestPluginEndpoint } from '../../utils/requestPluginEndpoint';
+
+import { useMutation, useQueryClient } from 'react-query';
+
+const deleteNote = (id) => {
+ requestPluginEndpoint('notes/' + id, {
+ method: 'DELETE',
+ });
+};
+
+const NoteListItem = ({ note, setActiveNote, toggleModal }) => {
+ const queryClient = useQueryClient();
+
+ const openNoteCreateModel = () => {
+ setActiveNote(note);
+ toggleModal();
+ };
+ const mutation = useMutation(deleteNote, {
+ onSuccess: () => {
+ queryClient.invalidateQueries('entity-notes');
+ },
+ });
+
+ const handleNoteDelete = (note) => {
+ mutation.mutate(note.id);
+ };
+
+ return (
+
+
+
+ {note.title}
+
+
+ openNoteCreateModel(note)} label="Edit" icon={} />
+ handleNoteDelete(note)} label="Delete" icon={} />
+
+
+
+ );
+};
+
+NoteListItem.propTypes = {
+ note: PropTypes.object.isRequired,
+ setActiveNote: PropTypes.func.isRequired,
+ toggleModal: PropTypes.func.isRequired,
+};
+
+export { NoteListItem };
diff --git a/admin/src/components/NoteListLayout/components/NoteListLayoutContent/index.js b/admin/src/components/NoteListLayout/components/NoteListLayoutContent/index.js
new file mode 100644
index 0000000..3f428ec
--- /dev/null
+++ b/admin/src/components/NoteListLayout/components/NoteListLayoutContent/index.js
@@ -0,0 +1,51 @@
+import React, { useState } from 'react';
+import { useQuery } from 'react-query';
+import PropTypes from 'prop-types';
+import { Stack } from '@strapi/design-system/Stack';
+import { NoteModalCreate } from '../../../NoteCreateModal';
+import { requestPluginEndpoint } from '../../../../utils/requestPluginEndpoint';
+import { NoteListItem } from '../../../NoteListItem';
+
+const fetchEntityNotes = async (entitySlug, entityId) => {
+ let params = {
+ 'filters[entitySlug][$eq]': entitySlug,
+ 'sort[createdAt]': 'ASC',
+ };
+ if (entityId) {
+ params['filters[entityId][$eq]'] = entityId;
+ }
+ return requestPluginEndpoint('notes', {
+ params,
+ });
+};
+
+const NoteListLayoutContent = ({ entity }) => {
+ const [isVisible, setIsVisible] = useState(false);
+ const [activeNote, setActiveNote] = useState({});
+ const toggleModal = () => setIsVisible((prev) => !prev);
+
+ const query = useQuery('entity-notes', () => fetchEntityNotes(entity.slug, entity.id));
+
+ return (
+
+
+ {!query.isLoading &&
+ query.data.data.notes.map((n) => (
+
+ ))}
+
+ {isVisible && }
+
+ );
+};
+
+NoteListLayoutContent.propTypes = {
+ entity: PropTypes.object.isRequired,
+};
+
+export { NoteListLayoutContent };
diff --git a/admin/src/components/NoteListLayout/components/NoteListLayoutFooter/index.js b/admin/src/components/NoteListLayout/components/NoteListLayoutFooter/index.js
new file mode 100644
index 0000000..639debb
--- /dev/null
+++ b/admin/src/components/NoteListLayout/components/NoteListLayoutFooter/index.js
@@ -0,0 +1,25 @@
+import React, { useState } from 'react';
+import PropTypes from 'prop-types';
+import { TextButton } from '@strapi/design-system/TextButton';
+import Plus from '@strapi/icons/Plus';
+import { NoteModalCreate } from '../../../NoteCreateModal';
+const NoteListLayoutFooter = ({ entity }) => {
+ const [isVisible, setIsVisible] = useState(false);
+
+ const toggleModal = () => setIsVisible((prev) => !prev);
+
+ return (
+
+ } onClick={toggleModal}>
+ Add a note
+
+ {isVisible && }
+
+ );
+};
+
+NoteListLayoutFooter.propTypes = {
+ entity: PropTypes.object.isRequired,
+};
+
+export { NoteListLayoutFooter };
diff --git a/admin/src/components/NoteListLayout/components/NoteListLayoutHeader/index.js b/admin/src/components/NoteListLayout/components/NoteListLayoutHeader/index.js
new file mode 100644
index 0000000..6d1ca67
--- /dev/null
+++ b/admin/src/components/NoteListLayout/components/NoteListLayoutHeader/index.js
@@ -0,0 +1,19 @@
+import React from 'react';
+import { Box } from '@strapi/design-system/Box';
+import { Divider } from '@strapi/design-system/Divider';
+import { Typography } from '@strapi/design-system/Typography';
+
+const NoteListLayoutHeader = () => {
+ return (
+
+
+ Notes
+
+
+
+
+
+ );
+};
+
+export { NoteListLayoutHeader };
diff --git a/admin/src/components/NoteListLayout/index.js b/admin/src/components/NoteListLayout/index.js
new file mode 100644
index 0000000..816ee92
--- /dev/null
+++ b/admin/src/components/NoteListLayout/index.js
@@ -0,0 +1,37 @@
+import React from 'react';
+import { useParams } from 'react-router-dom';
+import _get from 'lodash/get';
+import { useCMEditViewDataManager } from '@strapi/helper-plugin';
+import { Box } from '@strapi/design-system/Box';
+import { NoteListLayoutHeader } from './components/NoteListLayoutHeader';
+import { NoteListLayoutFooter } from './components/NoteListLayoutFooter';
+import { NoteListLayoutContent } from './components/NoteListLayoutContent';
+import { QueryClient, QueryClientProvider } from 'react-query';
+
+const client = new QueryClient({
+ defaultOptions: {
+ queries: {
+ retry: false,
+ },
+ },
+});
+
+const NoteListLayout = () => {
+ const params = useParams();
+ const id = _get(params, 'id', null);
+ const currentEntityId = id;
+ const { slug } = useCMEditViewDataManager();
+
+ const entity = { id: currentEntityId, slug };
+ return (
+
+
+
+
+
+
+
+ );
+};
+
+export { NoteListLayout };
diff --git a/admin/src/components/PluginIcon/index.js b/admin/src/components/PluginIcon/index.js
new file mode 100644
index 0000000..a7c98f7
--- /dev/null
+++ b/admin/src/components/PluginIcon/index.js
@@ -0,0 +1,12 @@
+/**
+ *
+ * PluginIcon
+ *
+ */
+
+import React from 'react';
+import Puzzle from '@strapi/icons/Puzzle';
+
+const PluginIcon = () => ;
+
+export default PluginIcon;
diff --git a/admin/src/index.js b/admin/src/index.js
new file mode 100644
index 0000000..70fc686
--- /dev/null
+++ b/admin/src/index.js
@@ -0,0 +1,24 @@
+import pluginPkg from '../../package.json';
+import { pluginId } from './pluginId';
+import Initializer from './components/Initializer';
+import { NoteListLayout } from './components/NoteListLayout';
+
+const name = pluginPkg.strapi.name;
+
+export default {
+ register(app) {
+ app.registerPlugin({
+ id: pluginId,
+ initializer: Initializer,
+ isReady: false,
+ name,
+ });
+ },
+
+ bootstrap(app) {
+ app.injectContentManagerComponent('editView', 'informations', {
+ name: 'note-list',
+ Component: NoteListLayout,
+ });
+ },
+};
diff --git a/admin/src/pluginId.js b/admin/src/pluginId.js
new file mode 100644
index 0000000..ade3cfc
--- /dev/null
+++ b/admin/src/pluginId.js
@@ -0,0 +1,3 @@
+import pluginPkg from '../../package.json';
+
+export const pluginId = pluginPkg.strapi.name;
diff --git a/admin/src/translations/en.json b/admin/src/translations/en.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/admin/src/translations/en.json
@@ -0,0 +1 @@
+{}
diff --git a/admin/src/translations/fr.json b/admin/src/translations/fr.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/admin/src/translations/fr.json
@@ -0,0 +1 @@
+{}
diff --git a/admin/src/utils/getPluginEndpointURL.js b/admin/src/utils/getPluginEndpointURL.js
new file mode 100644
index 0000000..1bb862c
--- /dev/null
+++ b/admin/src/utils/getPluginEndpointURL.js
@@ -0,0 +1,9 @@
+import { pluginId } from '../pluginId';
+
+/**
+ * Auto prefix URLs with the plugin id
+ *
+ * @param {String} endpoint plugin specific endpoint
+ * @returns {String} plugin id prefixed endpoint
+ */
+export const getPluginEndpointURL = (endpoint) => `/${pluginId}/${endpoint}`;
diff --git a/admin/src/utils/getTrad.js b/admin/src/utils/getTrad.js
new file mode 100644
index 0000000..d0a071b
--- /dev/null
+++ b/admin/src/utils/getTrad.js
@@ -0,0 +1,5 @@
+import pluginId from '../pluginId';
+
+const getTrad = (id) => `${pluginId}.${id}`;
+
+export default getTrad;
diff --git a/admin/src/utils/requestPluginEndpoint.js b/admin/src/utils/requestPluginEndpoint.js
new file mode 100644
index 0000000..7b3d630
--- /dev/null
+++ b/admin/src/utils/requestPluginEndpoint.js
@@ -0,0 +1,7 @@
+import { request } from '@strapi/helper-plugin';
+import { getPluginEndpointURL } from './getPluginEndpointURL';
+
+export const requestPluginEndpoint = (endpoint, data) => {
+ const url = getPluginEndpointURL(endpoint);
+ return request(url, data);
+};
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..e433841
--- /dev/null
+++ b/package.json
@@ -0,0 +1,60 @@
+{
+ "$schema": "https://json.schemastore.org/package",
+ "name": "strapi-plugin-notes",
+ "version": "1.0.0",
+ "description": "A plugin for Strapi Headless CMS that provides the ability to add notes to entity records.",
+ "scripts": {
+ "lint": "eslint . --fix",
+ "format": "prettier --write **/*.{ts,js,json,yml}"
+ },
+ "author": {
+ "name": "@ComfortablyCoding",
+ "url": "https://github.com/ComfortablyCoding"
+ },
+ "maintainers": [
+ {
+ "name": "@ComfortablyCoding",
+ "url": "https://github.com/ComfortablyCoding"
+ }
+ ],
+ "homepage": "https://github.com/ComfortablyCoding/strapi-plugin-notes#readme",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/ComfortablyCoding/strapi-plugin-notes.git"
+ },
+ "bugs": {
+ "url": "https://github.com/ComfortablyCoding/strapi-plugin-notes/issues"
+ },
+ "dependencies": {},
+ "devDependencies": {
+ "@babel/core": "^7.17.2",
+ "@babel/eslint-parser": "^7.17.0",
+ "@babel/preset-react": "^7.16.7",
+ "eslint": "^8.9.0",
+ "eslint-config-prettier": "^8.3.0",
+ "eslint-plugin-node": "^11.1.0",
+ "eslint-plugin-react": "^7.28.0",
+ "prettier": "^2.5.1"
+ },
+ "peerDependencies": {
+ "@strapi/strapi": "^4.0.7"
+ },
+ "strapi": {
+ "displayName": "Notes",
+ "description": "A plugin for Strapi Headless CMS that provides the ability to add notes to entity records.",
+ "name": "entity-notes",
+ "kind": "plugin"
+ },
+ "engines": {
+ "node": ">=12.x. <=16.x.x",
+ "npm": ">=6.0.0"
+ },
+ "keywords": [
+ "strapi",
+ "strapi-plugin",
+ "plugin",
+ "strapi plugin",
+ "notes"
+ ],
+ "license": "MIT"
+}
diff --git a/server/content-types/index.js b/server/content-types/index.js
new file mode 100644
index 0000000..1170755
--- /dev/null
+++ b/server/content-types/index.js
@@ -0,0 +1,7 @@
+'use strict';
+
+const noteContentType = require('./note-content-type');
+
+module.exports = {
+ note: { schema: noteContentType },
+};
diff --git a/server/content-types/note-content-type/index.js b/server/content-types/note-content-type/index.js
new file mode 100644
index 0000000..3c5b6b1
--- /dev/null
+++ b/server/content-types/note-content-type/index.js
@@ -0,0 +1,37 @@
+'use strict';
+
+module.exports = {
+ kind: 'collectionType',
+ collectionName: 'notes',
+ info: {
+ singularName: 'note',
+ pluralName: 'notes',
+ displayName: 'notes',
+ },
+ pluginOptions: {
+ 'content-manager': {
+ visible: false,
+ },
+ 'content-type-builder': {
+ visible: false,
+ },
+ },
+ options: {
+ draftAndPublish: false,
+ comment: '',
+ },
+ attributes: {
+ title: {
+ type: 'string',
+ },
+ content: {
+ type: 'text',
+ },
+ entityId: {
+ type: 'integer',
+ },
+ entitySlug: {
+ type: 'string',
+ },
+ },
+};
diff --git a/server/controllers/index.js b/server/controllers/index.js
new file mode 100644
index 0000000..65cc022
--- /dev/null
+++ b/server/controllers/index.js
@@ -0,0 +1,7 @@
+'use strict';
+
+const noteController = require('./note-controller');
+
+module.exports = {
+ noteController,
+};
diff --git a/server/controllers/note-controller.js b/server/controllers/note-controller.js
new file mode 100644
index 0000000..a3ef4a6
--- /dev/null
+++ b/server/controllers/note-controller.js
@@ -0,0 +1,65 @@
+'use strict';
+
+const { getPluginService } = require('../utils/getPluginService');
+
+module.exports = ({ strapi }) => ({
+ /**
+ * Fetch the current notes
+ *
+ * @return {Array} notes
+ */
+ async find(ctx) {
+ const notes = await getPluginService(strapi, 'noteService').find(ctx.query);
+
+ ctx.send({ data: { notes } });
+ },
+
+ /**
+ * Create a note
+ *
+ * @return {Object} note
+ */
+ async create(ctx) {
+ const { body } = ctx.request;
+ const createdNote = await getPluginService(strapi, 'noteService').create(body);
+
+ ctx.send({ data: { note: createdNote } });
+ },
+
+ /**
+ * Delete a note
+ *
+ * @return {Object} note
+ */
+ async delete(ctx) {
+ const { id } = ctx.params;
+ const note = await getPluginService(strapi, 'noteService').findOne(id);
+
+ if (!note) {
+ return ctx.notFound('note not found');
+ }
+
+ const deletedNote = await getPluginService(strapi, 'noteService').delete(id);
+
+ ctx.send({ data: { note: deletedNote } });
+ },
+
+ /**
+ * Edit a note
+ *
+ * @return {Object} note
+ */
+ async update(ctx) {
+ const { id } = ctx.params;
+ const { body } = ctx.request;
+ const note = await getPluginService(strapi, 'noteService').findOne(id);
+
+ if (!note) {
+ return ctx.notFound('note not found');
+ }
+
+ const updatedNote = await getPluginService(strapi, 'noteService').update(id, body);
+
+ ctx.send({ data: { note: updatedNote } });
+ },
+});
diff --git a/server/index.js b/server/index.js
new file mode 100644
index 0000000..586cca5
--- /dev/null
+++ b/server/index.js
@@ -0,0 +1,13 @@
+'use strict';
+
+const contentTypes = require('./content-types');
+const controllers = require('./controllers');
+const routes = require('./routes');
+const services = require('./services');
+
+module.exports = {
+ controllers,
+ routes,
+ services,
+ contentTypes,
+};
diff --git a/server/routes/index.js b/server/routes/index.js
new file mode 100644
index 0000000..cbd5e2d
--- /dev/null
+++ b/server/routes/index.js
@@ -0,0 +1,5 @@
+'use strict';
+
+const noteRoutes = require('./note-routes');
+
+module.exports = [...noteRoutes];
diff --git a/server/routes/note-routes.js b/server/routes/note-routes.js
new file mode 100644
index 0000000..52932ef
--- /dev/null
+++ b/server/routes/note-routes.js
@@ -0,0 +1,24 @@
+'use strict';
+
+module.exports = [
+ {
+ method: 'GET',
+ path: '/notes',
+ handler: 'noteController.find',
+ },
+ {
+ method: 'POST',
+ path: '/notes',
+ handler: 'noteController.create',
+ },
+ {
+ method: 'DELETE',
+ path: '/notes/:id',
+ handler: 'noteController.delete',
+ },
+ {
+ method: 'PUT',
+ path: '/notes/:id',
+ handler: 'noteController.update',
+ },
+];
diff --git a/server/services/index.js b/server/services/index.js
new file mode 100644
index 0000000..3b187c2
--- /dev/null
+++ b/server/services/index.js
@@ -0,0 +1,7 @@
+'use strict';
+
+const noteService = require('./note-service');
+
+module.exports = {
+ noteService,
+};
diff --git a/server/services/note-service.js b/server/services/note-service.js
new file mode 100644
index 0000000..8fec6c0
--- /dev/null
+++ b/server/services/note-service.js
@@ -0,0 +1,52 @@
+'use strict';
+
+const { pluginId } = require('../utils/pluginId');
+
+const uid = `plugin::${pluginId}.note`;
+
+module.exports = ({ strapi }) => ({
+ /**
+ * Returns the currently stored notes
+ *
+ * @return {Promise} notes
+ */
+ find(options = {}) {
+ return strapi.entityService.findMany(uid, options);
+ },
+
+ /**
+ * Returns the a specific stored note
+ *
+ * @return {Promise