├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .vscode └── settings.json ├── README.md ├── api ├── .gitkeep └── events │ ├── config │ └── routes.json │ ├── controllers │ └── events.js │ ├── models │ ├── events.js │ └── events.settings.json │ └── services │ └── events.js ├── config ├── database.js ├── env │ └── production │ │ ├── database.js │ │ └── server.js ├── functions │ ├── bootstrap.js │ ├── cron.js │ └── responses │ │ └── 404.js ├── plugins.js └── server.js ├── extensions ├── .gitkeep └── users-permissions │ ├── config │ └── jwt.js │ └── models │ └── User.settings.json ├── favicon.ico ├── package-lock.json ├── package.json └── public ├── robots.txt └── uploads └── .gitkeep /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [{package.json,*.yml}] 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | .cache 2 | build 3 | **/node_modules/** 4 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": "eslint:recommended", 4 | "env": { 5 | "commonjs": true, 6 | "es6": true, 7 | "node": true, 8 | "browser": false 9 | }, 10 | "parserOptions": { 11 | "ecmaFeatures": { 12 | "experimentalObjectRestSpread": true, 13 | "jsx": false 14 | }, 15 | "sourceType": "module" 16 | }, 17 | "globals": { 18 | "strapi": true 19 | }, 20 | "rules": { 21 | "indent": ["error", 2, { "SwitchCase": 1 }], 22 | "linebreak-style": ["error", "unix"], 23 | "no-console": 0, 24 | "quotes": ["error", "single"], 25 | "semi": ["error", "always"] 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ############################ 2 | # OS X 3 | ############################ 4 | 5 | .DS_Store 6 | .AppleDouble 7 | .LSOverride 8 | Icon 9 | .Spotlight-V100 10 | .Trashes 11 | ._* 12 | 13 | 14 | .vscode 15 | yarn.lock 16 | package-lock.json 17 | 18 | ############################ 19 | # Linux 20 | ############################ 21 | 22 | *~ 23 | 24 | 25 | ############################ 26 | # Windows 27 | ############################ 28 | 29 | Thumbs.db 30 | ehthumbs.db 31 | Desktop.ini 32 | $RECYCLE.BIN/ 33 | *.cab 34 | *.msi 35 | *.msm 36 | *.msp 37 | 38 | 39 | ############################ 40 | # Packages 41 | ############################ 42 | 43 | *.7z 44 | *.csv 45 | *.dat 46 | *.dmg 47 | *.gz 48 | *.iso 49 | *.jar 50 | *.rar 51 | *.tar 52 | *.zip 53 | *.com 54 | *.class 55 | *.dll 56 | *.exe 57 | *.o 58 | *.seed 59 | *.so 60 | *.swo 61 | *.swp 62 | *.swn 63 | *.swm 64 | *.out 65 | *.pid 66 | 67 | 68 | ############################ 69 | # Logs and databases 70 | ############################ 71 | 72 | .tmp 73 | *.log 74 | *.sql 75 | *.sqlite 76 | *.sqlite3 77 | 78 | 79 | ############################ 80 | # Misc. 81 | ############################ 82 | 83 | *# 84 | ssl 85 | .idea 86 | nbproject 87 | public/uploads/* 88 | !public/uploads/.gitkeep 89 | 90 | ############################ 91 | # Node.js 92 | ############################ 93 | 94 | lib-cov 95 | lcov.info 96 | pids 97 | logs 98 | results 99 | node_modules 100 | .node_history 101 | 102 | ############################ 103 | # Tests 104 | ############################ 105 | 106 | testApp 107 | coverage 108 | 109 | ############################ 110 | # Strapi 111 | ############################ 112 | 113 | .env 114 | license.txt 115 | exports 116 | *.cache 117 | build 118 | .strapi-updater.json 119 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "workbench.colorCustomizations": { 3 | "activityBar.activeBackground": "#1f6fd0", 4 | "activityBar.activeBorder": "#ee90bb", 5 | "activityBar.background": "#1f6fd0", 6 | "activityBar.foreground": "#e7e7e7", 7 | "activityBar.inactiveForeground": "#e7e7e799", 8 | "activityBarBadge.background": "#ee90bb", 9 | "activityBarBadge.foreground": "#15202b", 10 | "statusBar.background": "#1857a4", 11 | "statusBar.foreground": "#e7e7e7", 12 | "statusBarItem.hoverBackground": "#1f6fd0", 13 | "titleBar.activeBackground": "#1857a4", 14 | "titleBar.activeForeground": "#e7e7e7", 15 | "titleBar.inactiveBackground": "#1857a499", 16 | "titleBar.inactiveForeground": "#e7e7e799" 17 | }, 18 | "peacock.color": "#1857a4" 19 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | DJ Events (Strapi Backend) 2 | 3 | > Strapi backend for the DJ Events website. This is part of my Next.js course on Udemy 4 | 5 | The Next.js frontend can be found [here](https://github.com/bradtraversy/dj-events-frontend) 6 | 7 | # Usage 8 | 9 | ### Using Cloudinary 10 | 11 | Create a .env file and add your Cloudinary info for images 12 | 13 | ``` 14 | CLOUDINARY_NAME = "xxxx" 15 | CLOUDINARY_KEY = "xxxx" 16 | CLOUDINARY_SECRET = "xxxx" 17 | ``` 18 | 19 | ### Run the Server 20 | 21 | ```bash 22 | npm run develop 23 | # or 24 | yarn develop 25 | ``` 26 | 27 | Open [http://localhost:1337/admin](http://localhost:1337/admin) to access the CMS 28 | -------------------------------------------------------------------------------- /api/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradtraversy/dj-events-backend/9b8d25822af721901222a1c7186aae1b9b35d7ac/api/.gitkeep -------------------------------------------------------------------------------- /api/events/config/routes.json: -------------------------------------------------------------------------------- 1 | { 2 | "routes": [ 3 | { 4 | "method": "GET", 5 | "path": "/events", 6 | "handler": "events.find", 7 | "config": { 8 | "policies": [] 9 | } 10 | }, 11 | { 12 | "method": "GET", 13 | "path": "/events/me", 14 | "handler": "events.me", 15 | "config": { 16 | "policies": [] 17 | } 18 | }, 19 | { 20 | "method": "GET", 21 | "path": "/events/count", 22 | "handler": "events.count", 23 | "config": { 24 | "policies": [] 25 | } 26 | }, 27 | { 28 | "method": "GET", 29 | "path": "/events/:id", 30 | "handler": "events.findOne", 31 | "config": { 32 | "policies": [] 33 | } 34 | }, 35 | { 36 | "method": "POST", 37 | "path": "/events", 38 | "handler": "events.create", 39 | "config": { 40 | "policies": [] 41 | } 42 | }, 43 | { 44 | "method": "PUT", 45 | "path": "/events/:id", 46 | "handler": "events.update", 47 | "config": { 48 | "policies": [] 49 | } 50 | }, 51 | { 52 | "method": "DELETE", 53 | "path": "/events/:id", 54 | "handler": "events.delete", 55 | "config": { 56 | "policies": [] 57 | } 58 | } 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /api/events/controllers/events.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const { sanitizeEntity } = require("strapi-utils"); 3 | 4 | /** 5 | * Read the documentation (https://strapi.io/documentation/developer-docs/latest/development/backend-customization.html#core-controllers) 6 | * to customize this controller 7 | */ 8 | 9 | module.exports = { 10 | // Create event with linked user 11 | async create(ctx) { 12 | let entity; 13 | if (ctx.is("multipart")) { 14 | const { data, files } = parseMultipartData(ctx); 15 | data.user = ctx.state.user.id; 16 | entity = await strapi.services.events.create(data, { files }); 17 | } else { 18 | ctx.request.body.user = ctx.state.user.id; 19 | entity = await strapi.services.events.create(ctx.request.body); 20 | } 21 | return sanitizeEntity(entity, { model: strapi.models.events }); 22 | }, 23 | // Update user event 24 | async update(ctx) { 25 | const { id } = ctx.params; 26 | 27 | let entity; 28 | 29 | const [events] = await strapi.services.events.find({ 30 | id: ctx.params.id, 31 | "user.id": ctx.state.user.id, 32 | }); 33 | 34 | if (!events) { 35 | return ctx.unauthorized(`You can't update this entry`); 36 | } 37 | 38 | if (ctx.is("multipart")) { 39 | const { data, files } = parseMultipartData(ctx); 40 | entity = await strapi.services.events.update({ id }, data, { 41 | files, 42 | }); 43 | } else { 44 | entity = await strapi.services.events.update({ id }, ctx.request.body); 45 | } 46 | 47 | return sanitizeEntity(entity, { model: strapi.models.events }); 48 | }, 49 | // Delete a user event 50 | async delete(ctx) { 51 | const { id } = ctx.params; 52 | 53 | const [events] = await strapi.services.events.find({ 54 | id: ctx.params.id, 55 | "user.id": ctx.state.user.id, 56 | }); 57 | 58 | if (!events) { 59 | return ctx.unauthorized(`You can't update this entry`); 60 | } 61 | 62 | const entity = await strapi.services.events.delete({ id }); 63 | return sanitizeEntity(entity, { model: strapi.models.events }); 64 | }, 65 | // Get logged in users 66 | async me(ctx) { 67 | const user = ctx.state.user; 68 | 69 | if (!user) { 70 | return ctx.badRequest(null, [ 71 | { messages: [{ id: "No authorization header was found" }] }, 72 | ]); 73 | } 74 | 75 | const data = await strapi.services.events.find({ user: user.id }); 76 | 77 | if (!data) { 78 | return ctx.notFound(); 79 | } 80 | 81 | return sanitizeEntity(data, { model: strapi.models.events }); 82 | }, 83 | }; 84 | -------------------------------------------------------------------------------- /api/events/models/events.js: -------------------------------------------------------------------------------- 1 | const slugify = require("slugify"); 2 | 3 | module.exports = { 4 | lifecycles: { 5 | beforeCreate: async (data) => { 6 | if (data.name) { 7 | data.slug = slugify(data.name, { lower: true }); 8 | } 9 | }, 10 | beforeUpdate: async (params, data) => { 11 | if (data.name) { 12 | data.slug = slugify(data.name, { lower: true }); 13 | } 14 | }, 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /api/events/models/events.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "collectionType", 3 | "collectionName": "events", 4 | "info": { 5 | "name": "events" 6 | }, 7 | "options": { 8 | "increments": true, 9 | "timestamps": true, 10 | "draftAndPublish": true 11 | }, 12 | "attributes": { 13 | "name": { 14 | "type": "string" 15 | }, 16 | "slug": { 17 | "type": "uid", 18 | "targetField": "name" 19 | }, 20 | "venue": { 21 | "type": "string" 22 | }, 23 | "address": { 24 | "type": "string" 25 | }, 26 | "date": { 27 | "type": "datetime" 28 | }, 29 | "time": { 30 | "type": "string" 31 | }, 32 | "performers": { 33 | "type": "string" 34 | }, 35 | "description": { 36 | "type": "richtext" 37 | }, 38 | "image": { 39 | "model": "file", 40 | "via": "related", 41 | "allowedTypes": [ 42 | "images", 43 | "files", 44 | "videos" 45 | ], 46 | "plugin": "upload", 47 | "required": false 48 | }, 49 | "user": { 50 | "plugin": "users-permissions", 51 | "model": "user", 52 | "via": "events" 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /api/events/services/events.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Read the documentation (https://strapi.io/documentation/developer-docs/latest/development/backend-customization.html#core-services) 5 | * to customize this service 6 | */ 7 | 8 | module.exports = {}; 9 | -------------------------------------------------------------------------------- /config/database.js: -------------------------------------------------------------------------------- 1 | module.exports = ({ env }) => ({ 2 | defaultConnection: 'default', 3 | connections: { 4 | default: { 5 | connector: 'bookshelf', 6 | settings: { 7 | client: 'sqlite', 8 | filename: env('DATABASE_FILENAME', '.tmp/data.db'), 9 | }, 10 | options: { 11 | useNullAsDefault: true, 12 | }, 13 | }, 14 | }, 15 | }); 16 | -------------------------------------------------------------------------------- /config/env/production/database.js: -------------------------------------------------------------------------------- 1 | const parse = require("pg-connection-string").parse; 2 | const config = parse(process.env.DATABASE_URL); 3 | 4 | module.exports = ({ env }) => ({ 5 | defaultConnection: "default", 6 | connections: { 7 | default: { 8 | connector: "bookshelf", 9 | settings: { 10 | client: "postgres", 11 | host: config.host, 12 | port: config.port, 13 | database: config.database, 14 | username: config.user, 15 | password: config.password, 16 | ssl: { 17 | rejectUnauthorized: false, 18 | }, 19 | }, 20 | options: { 21 | ssl: true, 22 | }, 23 | }, 24 | }, 25 | }); 26 | -------------------------------------------------------------------------------- /config/env/production/server.js: -------------------------------------------------------------------------------- 1 | module.exports = ({ env }) => ({ 2 | url: env("HEROKU_URL"), 3 | }); 4 | -------------------------------------------------------------------------------- /config/functions/bootstrap.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * An asynchronous bootstrap function that runs before 5 | * your application gets started. 6 | * 7 | * This gives you an opportunity to set up your data model, 8 | * run jobs, or perform some special logic. 9 | * 10 | * See more details here: https://strapi.io/documentation/developer-docs/latest/setup-deployment-guides/configurations.html#bootstrap 11 | */ 12 | 13 | module.exports = () => {}; 14 | -------------------------------------------------------------------------------- /config/functions/cron.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Cron config that gives you an opportunity 5 | * to run scheduled jobs. 6 | * 7 | * The cron format consists of: 8 | * [SECOND (optional)] [MINUTE] [HOUR] [DAY OF MONTH] [MONTH OF YEAR] [DAY OF WEEK] 9 | * 10 | * See more details here: https://strapi.io/documentation/developer-docs/latest/setup-deployment-guides/configurations.html#cron-tasks 11 | */ 12 | 13 | module.exports = { 14 | /** 15 | * Simple example. 16 | * Every monday at 1am. 17 | */ 18 | // '0 1 * * 1': () => { 19 | // 20 | // } 21 | }; 22 | -------------------------------------------------------------------------------- /config/functions/responses/404.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = async (/* ctx */) => { 4 | // return ctx.notFound('My custom message 404'); 5 | }; 6 | -------------------------------------------------------------------------------- /config/plugins.js: -------------------------------------------------------------------------------- 1 | module.exports = ({ env }) => ({ 2 | // ... 3 | upload: { 4 | provider: "cloudinary", 5 | providerOptions: { 6 | cloud_name: env("CLOUDINARY_NAME"), 7 | api_key: env("CLOUDINARY_KEY"), 8 | api_secret: env("CLOUDINARY_SECRET"), 9 | }, 10 | actionOptions: { 11 | upload: {}, 12 | delete: {}, 13 | }, 14 | }, 15 | // ... 16 | }); 17 | -------------------------------------------------------------------------------- /config/server.js: -------------------------------------------------------------------------------- 1 | module.exports = ({ env }) => ({ 2 | host: env('HOST', '0.0.0.0'), 3 | port: env.int('PORT', 1337), 4 | admin: { 5 | auth: { 6 | secret: env('ADMIN_JWT_SECRET', 'e52ed7fe658aee15ca1fbebe40efab04'), 7 | }, 8 | }, 9 | }); 10 | -------------------------------------------------------------------------------- /extensions/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradtraversy/dj-events-backend/9b8d25822af721901222a1c7186aae1b9b35d7ac/extensions/.gitkeep -------------------------------------------------------------------------------- /extensions/users-permissions/config/jwt.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | jwtSecret: process.env.JWT_SECRET || '73eae42c-034e-41e5-8c49-533d378d177b' 3 | }; -------------------------------------------------------------------------------- /extensions/users-permissions/models/User.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "collectionType", 3 | "collectionName": "users-permissions_user", 4 | "info": { 5 | "name": "user", 6 | "description": "" 7 | }, 8 | "options": { 9 | "draftAndPublish": false, 10 | "timestamps": true 11 | }, 12 | "attributes": { 13 | "username": { 14 | "type": "string", 15 | "minLength": 3, 16 | "unique": true, 17 | "configurable": false, 18 | "required": true 19 | }, 20 | "email": { 21 | "type": "email", 22 | "minLength": 6, 23 | "configurable": false, 24 | "required": true 25 | }, 26 | "provider": { 27 | "type": "string", 28 | "configurable": false 29 | }, 30 | "password": { 31 | "type": "password", 32 | "minLength": 6, 33 | "configurable": false, 34 | "private": true 35 | }, 36 | "resetPasswordToken": { 37 | "type": "string", 38 | "configurable": false, 39 | "private": true 40 | }, 41 | "confirmationToken": { 42 | "type": "string", 43 | "configurable": false, 44 | "private": true 45 | }, 46 | "confirmed": { 47 | "type": "boolean", 48 | "default": false, 49 | "configurable": false 50 | }, 51 | "blocked": { 52 | "type": "boolean", 53 | "default": false, 54 | "configurable": false 55 | }, 56 | "role": { 57 | "model": "role", 58 | "via": "users", 59 | "plugin": "users-permissions", 60 | "configurable": false 61 | }, 62 | "events": { 63 | "via": "user", 64 | "collection": "events" 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradtraversy/dj-events-backend/9b8d25822af721901222a1c7186aae1b9b35d7ac/favicon.ico -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dj-events-backend", 3 | "private": true, 4 | "version": "0.1.0", 5 | "description": "A Strapi application", 6 | "scripts": { 7 | "develop": "strapi develop", 8 | "start": "strapi start", 9 | "build": "strapi build", 10 | "strapi": "strapi" 11 | }, 12 | "dependencies": { 13 | "knex": "0.21.18", 14 | "pg": "^8.6.0", 15 | "pg-connection-string": "^2.4.0", 16 | "slugify": "^1.5.0", 17 | "sqlite3": "5.0.0", 18 | "strapi": "3.5.4", 19 | "strapi-admin": "3.5.4", 20 | "strapi-connector-bookshelf": "3.5.4", 21 | "strapi-plugin-content-manager": "3.5.4", 22 | "strapi-plugin-content-type-builder": "3.5.4", 23 | "strapi-plugin-email": "3.5.4", 24 | "strapi-plugin-upload": "3.5.4", 25 | "strapi-plugin-users-permissions": "3.5.4", 26 | "strapi-provider-upload-cloudinary": "^3.5.4", 27 | "strapi-utils": "3.5.4" 28 | }, 29 | "author": { 30 | "name": "A Strapi developer" 31 | }, 32 | "strapi": { 33 | "uuid": "7fc1bb2f-9215-4357-b5c8-556770186f21" 34 | }, 35 | "engines": { 36 | "node": ">=10.16.0 <=14.x.x", 37 | "npm": "^6.0.0" 38 | }, 39 | "license": "MIT" 40 | } 41 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # To prevent search engines from seeing the site altogether, uncomment the next two lines: 2 | # User-Agent: * 3 | # Disallow: / 4 | -------------------------------------------------------------------------------- /public/uploads/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradtraversy/dj-events-backend/9b8d25822af721901222a1c7186aae1b9b35d7ac/public/uploads/.gitkeep --------------------------------------------------------------------------------