element in src/app.html
13 | target: '#svelte'
14 | }
15 | };
16 |
17 | export default config;
18 |
--------------------------------------------------------------------------------
/cms/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 |
--------------------------------------------------------------------------------
/frontend/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | parser: '@typescript-eslint/parser',
4 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'],
5 | plugins: ['svelte3', '@typescript-eslint'],
6 | ignorePatterns: ['*.cjs'],
7 | overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }],
8 | settings: {
9 | 'svelte3/typescript': () => require('typescript')
10 | },
11 | parserOptions: {
12 | sourceType: 'module',
13 | ecmaVersion: 2019
14 | },
15 | env: {
16 | browser: true,
17 | es2017: true,
18 | node: true
19 | }
20 | };
21 |
--------------------------------------------------------------------------------
/frontend/postcss.config.cjs:
--------------------------------------------------------------------------------
1 | const tailwindcss = require("tailwindcss");
2 | const autoprefixer = require("autoprefixer");
3 | const cssnano = require("cssnano");
4 |
5 | const mode = process.env.NODE_ENV;
6 | const dev = mode === "development";
7 |
8 | const config = {
9 | plugins: [
10 | //Some plugins, like postcss-nested, need to run before Tailwind,
11 | tailwindcss(),
12 | //But others, like autoprefixer, need to run after,
13 | autoprefixer(),
14 | !dev && cssnano({
15 | preset: "default",
16 | })
17 | ],
18 | };
19 |
20 | module.exports = config;
--------------------------------------------------------------------------------
/frontend/src/lib/Navbar.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 |
18 |
--------------------------------------------------------------------------------
/cms/.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 |
--------------------------------------------------------------------------------
/frontend/src/routes/__error.svelte:
--------------------------------------------------------------------------------
1 |
10 |
11 |
15 |
16 |
17 |
18 | {status}
19 | {error.message}
20 |
21 |
22 |
--------------------------------------------------------------------------------
/cms/api/post/models/post.settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "kind": "collectionType",
3 | "collectionName": "posts",
4 | "info": {
5 | "name": "Post",
6 | "description": ""
7 | },
8 | "options": {
9 | "increments": true,
10 | "timestamps": true,
11 | "draftAndPublish": false
12 | },
13 | "pluginOptions": {},
14 | "attributes": {
15 | "title": {
16 | "type": "string"
17 | },
18 | "description": {
19 | "type": "text"
20 | },
21 | "content": {
22 | "type": "richtext"
23 | },
24 | "author": {
25 | "targetColumnName": "",
26 | "plugin": "users-permissions",
27 | "model": "user",
28 | "via": "posts"
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/frontend/src/lib/types.ts:
--------------------------------------------------------------------------------
1 | export interface Post {
2 | id: number;
3 | title: string;
4 | description: string;
5 | content: string;
6 | author: Author;
7 | created_at: string;
8 | updated_at: string;
9 | }
10 |
11 | export interface User {
12 | id: number;
13 | username: string;
14 | email: string;
15 | provider: string;
16 | confirmed: boolean;
17 | blocked: boolean;
18 | role: Role;
19 | created_at: string;
20 | updated_at: string;
21 | posts: Post[];
22 | }
23 |
24 | export interface Role {
25 | id: number;
26 | name: string;
27 | description: string;
28 | type: string;
29 | }
30 |
31 | export interface Author extends Omit
, 'role'> {
32 | role: Role['id'];
33 | }
34 |
--------------------------------------------------------------------------------
/frontend/src/hooks.ts:
--------------------------------------------------------------------------------
1 | import cookie from 'cookie';
2 | import { v4 as uuid } from '@lukeed/uuid';
3 | import type { Handle } from '@sveltejs/kit';
4 |
5 | export const handle: Handle = async ({ request, resolve }) => {
6 | const cookies = cookie.parse(request.headers.cookie || '');
7 | request.locals.userid = cookies.userid || uuid();
8 |
9 | // TODO https://github.com/sveltejs/kit/issues/1046
10 | if (request.query.has('_method')) {
11 | request.method = request.query.get('_method').toUpperCase();
12 | }
13 |
14 | const response = await resolve(request);
15 |
16 | if (!cookies.userid) {
17 | // if this is the first time the user has visited this app,
18 | // set a cookie so that we recognise them when they return
19 | response.headers['set-cookie'] = `userid=${request.locals.userid}; Path=/; HttpOnly`;
20 | }
21 |
22 | return response;
23 | };
24 |
--------------------------------------------------------------------------------
/frontend/src/routes/__layout.svelte:
--------------------------------------------------------------------------------
1 |
28 |
29 | {#if !loading}
30 |
31 |
32 | {/if}
33 |
--------------------------------------------------------------------------------
/frontend/tailwind.config.cjs:
--------------------------------------------------------------------------------
1 | const { tailwindExtractor } = require("tailwindcss/lib/lib/purgeUnusedStyles");
2 |
3 | const config = {
4 | mode: "aot",
5 | purge: {
6 | content: [
7 | "./src/**/*.{html,js,svelte,ts}",
8 | ],
9 | options: {
10 | defaultExtractor: (content) => [
11 | // If this stops working, please open an issue at https://github.com/svelte-add/tailwindcss/issues rather than bothering Tailwind Labs about it
12 | ...tailwindExtractor(content),
13 | // Match Svelte class: directives (https://github.com/tailwindlabs/tailwindcss/discussions/1731)
14 | ...[...content.matchAll(/(?:class:)*([\w\d-/:%.]+)/gm)].map(([_match, group, ..._rest]) => group),
15 | ],
16 | },
17 | safelist: [/^svelte-[\d\w]+$/],
18 | },
19 | theme: {
20 | extend: {},
21 | },
22 | variants: {
23 | extend: {},
24 | },
25 | plugins: [],
26 | };
27 |
28 | module.exports = config;
29 |
--------------------------------------------------------------------------------
/frontend/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "moduleResolution": "node",
4 | "module": "es2020",
5 | "lib": ["es2020"],
6 | "target": "es2019",
7 | /**
8 | svelte-preprocess cannot figure out whether you have a value or a type, so tell TypeScript
9 | to enforce using \`import type\` instead of \`import\` for Types.
10 | */
11 | "importsNotUsedAsValues": "error",
12 | "isolatedModules": true,
13 | "resolveJsonModule": true,
14 | /**
15 | To have warnings/errors of the Svelte compiler at the correct position,
16 | enable source maps by default.
17 | */
18 | "sourceMap": true,
19 | "esModuleInterop": true,
20 | "skipLibCheck": true,
21 | "forceConsistentCasingInFileNames": true,
22 | "baseUrl": ".",
23 | "allowJs": true,
24 | "checkJs": true,
25 | "paths": {
26 | "$lib/*": ["src/lib/*"]
27 | }
28 | },
29 | "include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.ts", "src/**/*.svelte"]
30 | }
31 |
--------------------------------------------------------------------------------
/frontend/src/routes/index.svelte:
--------------------------------------------------------------------------------
1 |
11 |
12 |
18 |
19 |
20 |
My wonderful blog
21 |
22 |
23 |
24 | {#each posts as post}
25 |
goto('/blog/' + post.id)}
28 | >
29 |
{post.title}
30 |
{post.description}
31 |
By: {post.author.username}
32 |
33 | {/each}
34 |
35 |
--------------------------------------------------------------------------------
/cms/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cms",
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 | "devDependencies": {},
13 | "dependencies": {
14 | "strapi": "3.6.3",
15 | "strapi-admin": "3.6.3",
16 | "strapi-utils": "3.6.3",
17 | "strapi-plugin-content-type-builder": "3.6.3",
18 | "strapi-plugin-content-manager": "3.6.3",
19 | "strapi-plugin-users-permissions": "3.6.3",
20 | "strapi-plugin-email": "3.6.3",
21 | "strapi-plugin-upload": "3.6.3",
22 | "strapi-plugin-i18n": "3.6.3",
23 | "strapi-connector-bookshelf": "3.6.3",
24 | "knex": "0.21.18",
25 | "sqlite3": "5.0.0"
26 | },
27 | "author": {
28 | "name": "A Strapi developer"
29 | },
30 | "strapi": {
31 | "uuid": "597444d1-5b9c-4096-b1f8-77e4008b68ed"
32 | },
33 | "engines": {
34 | "node": ">=10.16.0 <=14.x.x",
35 | "npm": "^6.0.0"
36 | },
37 | "license": "MIT"
38 | }
39 |
--------------------------------------------------------------------------------
/cms/api/post/config/routes.json:
--------------------------------------------------------------------------------
1 | {
2 | "routes": [
3 | {
4 | "method": "GET",
5 | "path": "/posts",
6 | "handler": "post.find",
7 | "config": {
8 | "policies": []
9 | }
10 | },
11 | {
12 | "method": "GET",
13 | "path": "/posts/count",
14 | "handler": "post.count",
15 | "config": {
16 | "policies": []
17 | }
18 | },
19 | {
20 | "method": "GET",
21 | "path": "/posts/:id",
22 | "handler": "post.findOne",
23 | "config": {
24 | "policies": []
25 | }
26 | },
27 | {
28 | "method": "POST",
29 | "path": "/posts",
30 | "handler": "post.create",
31 | "config": {
32 | "policies": []
33 | }
34 | },
35 | {
36 | "method": "PUT",
37 | "path": "/posts/:id",
38 | "handler": "post.update",
39 | "config": {
40 | "policies": []
41 | }
42 | },
43 | {
44 | "method": "DELETE",
45 | "path": "/posts/:id",
46 | "handler": "post.delete",
47 | "config": {
48 | "policies": []
49 | }
50 | }
51 | ]
52 | }
53 |
--------------------------------------------------------------------------------
/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "~TODO~",
3 | "version": "0.0.1",
4 | "scripts": {
5 | "dev": "svelte-kit dev",
6 | "build": "svelte-kit build",
7 | "preview": "svelte-kit preview",
8 | "check": "svelte-check --tsconfig ./tsconfig.json",
9 | "check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
10 | "lint": "prettier --check --plugin-search-dir=. . && eslint --ignore-path .gitignore .",
11 | "format": "prettier --write --plugin-search-dir=. ."
12 | },
13 | "devDependencies": {
14 | "@sveltejs/kit": "next",
15 | "@types/cookie": "^0.4.0",
16 | "@typescript-eslint/eslint-plugin": "^4.19.0",
17 | "@typescript-eslint/parser": "^4.19.0",
18 | "autoprefixer": "^10.2.5",
19 | "cssnano": "^5.0.2",
20 | "eslint": "^7.22.0",
21 | "eslint-config-prettier": "^8.1.0",
22 | "eslint-plugin-svelte3": "^3.2.0",
23 | "postcss": "^8.2.13",
24 | "postcss-load-config": "^3.0.1",
25 | "prettier": "~2.2.1",
26 | "prettier-plugin-svelte": "^2.2.0",
27 | "svelte": "^3.34.0",
28 | "svelte-check": "^2.0.0",
29 | "svelte-preprocess": "^4.7.3",
30 | "tailwindcss": "^2.1.2",
31 | "tslib": "^2.0.0",
32 | "typescript": "^4.0.0"
33 | },
34 | "type": "module",
35 | "dependencies": {
36 | "@fontsource/fira-mono": "^4.2.2",
37 | "@lukeed/uuid": "^2.0.0",
38 | "cookie": "^0.4.1",
39 | "marked": "^2.1.1"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/cms/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 | "posts": {
63 | "via": "author",
64 | "collection": "post"
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/cms/.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 | ############################
15 | # Linux
16 | ############################
17 |
18 | *~
19 |
20 |
21 | ############################
22 | # Windows
23 | ############################
24 |
25 | Thumbs.db
26 | ehthumbs.db
27 | Desktop.ini
28 | $RECYCLE.BIN/
29 | *.cab
30 | *.msi
31 | *.msm
32 | *.msp
33 |
34 |
35 | ############################
36 | # Packages
37 | ############################
38 |
39 | *.7z
40 | *.csv
41 | *.dat
42 | *.dmg
43 | *.gz
44 | *.iso
45 | *.jar
46 | *.rar
47 | *.tar
48 | *.zip
49 | *.com
50 | *.class
51 | *.dll
52 | *.exe
53 | *.o
54 | *.seed
55 | *.so
56 | *.swo
57 | *.swp
58 | *.swn
59 | *.swm
60 | *.out
61 | *.pid
62 |
63 |
64 | ############################
65 | # Logs and databases
66 | ############################
67 |
68 | .tmp
69 | *.log
70 | *.sql
71 | *.sqlite
72 | *.sqlite3
73 |
74 |
75 | ############################
76 | # Misc.
77 | ############################
78 |
79 | *#
80 | ssl
81 | .idea
82 | nbproject
83 | public/uploads/*
84 | !public/uploads/.gitkeep
85 |
86 | ############################
87 | # Node.js
88 | ############################
89 |
90 | lib-cov
91 | lcov.info
92 | pids
93 | logs
94 | results
95 | node_modules
96 | .node_history
97 |
98 | ############################
99 | # Tests
100 | ############################
101 |
102 | testApp
103 | coverage
104 |
105 | ############################
106 | # Strapi
107 | ############################
108 |
109 | .env
110 | license.txt
111 | exports
112 | *.cache
113 | build
114 | .strapi-updater.json
115 |
--------------------------------------------------------------------------------
/frontend/src/routes/login.svelte:
--------------------------------------------------------------------------------
1 |
30 |
31 |
46 |
47 |
60 |
--------------------------------------------------------------------------------
/cms/api/post/controllers/post.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const { parseMultipartData, sanitizeEntity } = require("strapi-utils");
4 |
5 | /**
6 | * Read the documentation (https://strapi.io/documentation/developer-docs/latest/development/backend-customization.html#core-controllers)
7 | * to customize this controller
8 | */
9 |
10 | module.exports = {
11 | async create(ctx) {
12 | let entity;
13 |
14 | if (ctx.is("multipart")) {
15 | const { data, files } = parseMultipartData(ctx);
16 | data.author = ctx.state.user.id;
17 | entity = await strapi.services.post.create(data, { files });
18 | } else {
19 | ctx.request.body.author = ctx.state.user.id;
20 | entity = await strapi.services.post.create(ctx.request.body);
21 | }
22 |
23 | return sanitizeEntity(entity, { model: strapi.models.post });
24 | },
25 |
26 | async update(ctx) {
27 | const { id } = ctx.params;
28 |
29 | let entity;
30 |
31 | const [article] = await strapi.services.post.find({
32 | id: ctx.params.id,
33 | "author.id": ctx.state.user.id,
34 | });
35 |
36 | if (!article) {
37 | return ctx.unauthorized(`You can't update this entry`);
38 | }
39 |
40 | if (ctx.is("multipart")) {
41 | const { data, files } = parseMultipartData(ctx);
42 | entity = await strapi.services.post.update({ id }, data, {
43 | files,
44 | });
45 | } else {
46 | entity = await strapi.services.post.update({ id }, ctx.request.body);
47 | }
48 |
49 | return sanitizeEntity(entity, { model: strapi.models.post });
50 | },
51 |
52 | async delete(ctx) {
53 | const { id } = ctx.params;
54 |
55 | let entity;
56 |
57 | const [article] = await strapi.services.post.find({
58 | id: ctx.params.id,
59 | "author.id": ctx.state.user.id,
60 | });
61 |
62 | if (!article) {
63 | return ctx.unauthorized(`You can't delete this entry`);
64 | }
65 |
66 | await strapi.services.post.delete({ id });
67 |
68 | return { ok: true };
69 | },
70 | };
71 |
--------------------------------------------------------------------------------
/frontend/src/routes/blog/[slug].svelte:
--------------------------------------------------------------------------------
1 |
23 |
24 |
63 |
64 | {post.title}
65 | By: {post.author.username}
66 |
67 | {#if $user && post.author.id === $user.id}
68 |
69 |
73 |
77 |
78 | {/if}
79 |
80 |
81 | {@html content}
82 |
83 |
--------------------------------------------------------------------------------
/frontend/src/routes/new.svelte:
--------------------------------------------------------------------------------
1 |
32 |
33 |
106 |
107 |
124 |
125 |
142 |
--------------------------------------------------------------------------------