├── README.md ├── cms ├── .editorconfig ├── .env.example ├── .eslintignore ├── .eslintrc ├── .gitignore ├── api │ ├── .gitkeep │ ├── auth │ │ ├── config │ │ │ └── routes.json │ │ └── controllers │ │ │ └── auth.js │ └── post │ │ ├── config │ │ └── routes.json │ │ ├── controllers │ │ └── post.js │ │ ├── models │ │ ├── post.js │ │ └── post.settings.json │ │ └── services │ │ └── post.js ├── config │ ├── database.js │ ├── functions │ │ ├── bootstrap.js │ │ ├── cron.js │ │ └── responses │ │ │ └── 404.js │ └── server.js ├── extensions │ ├── .gitkeep │ └── users-permissions │ │ ├── config │ │ └── jwt.js │ │ └── models │ │ └── User.settings.json ├── favicon.ico ├── package.json └── public │ ├── robots.txt │ └── uploads │ └── .gitkeep └── frontend ├── .eslintrc.cjs ├── .gitignore ├── .npmrc ├── .prettierignore ├── .prettierrc ├── package.json ├── postcss.config.cjs ├── src ├── app.html ├── app.postcss ├── global.d.ts ├── hooks.ts ├── lib │ ├── Navbar.svelte │ ├── types.ts │ └── user.ts └── routes │ ├── __error.svelte │ ├── __layout.svelte │ ├── blog │ └── [slug].svelte │ ├── index.svelte │ ├── login.svelte │ ├── new.svelte │ └── posts.ts ├── static ├── favicon.png ├── robots.txt ├── svelte-welcome.png └── svelte-welcome.webp ├── svelte.config.js ├── tailwind.config.cjs └── tsconfig.json /README.md: -------------------------------------------------------------------------------- 1 | This code is a result of the tutorial How to Create a Blog with SvelteKit and Strapi 2 | 3 | -------------------------------------------------------------------------------- /cms/.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 | -------------------------------------------------------------------------------- /cms/.env.example: -------------------------------------------------------------------------------- 1 | HOST=0.0.0.0 2 | PORT=1337 3 | -------------------------------------------------------------------------------- /cms/.eslintignore: -------------------------------------------------------------------------------- 1 | .cache 2 | build 3 | **/node_modules/** 4 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /cms/api/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arnu515/sveltekit-strapi-blog/ed2e966f80aad457c6c2164aa7b482c4434cb398/cms/api/.gitkeep -------------------------------------------------------------------------------- /cms/api/auth/config/routes.json: -------------------------------------------------------------------------------- 1 | { 2 | "routes": [ 3 | { 4 | "method": "GET", 5 | "path": "/auth/me", 6 | "handler": "Auth.me", 7 | "config": { 8 | "policies": [] 9 | } 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /cms/api/auth/controllers/auth.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /** 4 | * A set of functions called "actions" for `auth` 5 | */ 6 | 7 | module.exports = { 8 | async me(ctx) { 9 | if (ctx.state.user) { 10 | return ctx.state.user; 11 | } 12 | 13 | ctx.unauthorized("You're not logged in"); 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /cms/api/post/models/post.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Read the documentation (https://strapi.io/documentation/developer-docs/latest/development/backend-customization.html#lifecycle-hooks) 5 | * to customize this model 6 | */ 7 | 8 | module.exports = {}; 9 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /cms/api/post/services/post.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 | -------------------------------------------------------------------------------- /cms/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 | -------------------------------------------------------------------------------- /cms/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /cms/config/functions/responses/404.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = async (/* ctx */) => { 4 | // return ctx.notFound('My custom message 404'); 5 | }; 6 | -------------------------------------------------------------------------------- /cms/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', '2688ca5aa2bd0e138f7a8c63532b693e'), 7 | }, 8 | }, 9 | }); 10 | -------------------------------------------------------------------------------- /cms/extensions/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arnu515/sveltekit-strapi-blog/ed2e966f80aad457c6c2164aa7b482c4434cb398/cms/extensions/.gitkeep -------------------------------------------------------------------------------- /cms/extensions/users-permissions/config/jwt.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | jwtSecret: process.env.JWT_SECRET || '59c3a97a-5bd5-448c-8ec0-58eebd2a08d2' 3 | }; -------------------------------------------------------------------------------- /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/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arnu515/sveltekit-strapi-blog/ed2e966f80aad457c6c2164aa7b482c4434cb398/cms/favicon.ico -------------------------------------------------------------------------------- /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/public/robots.txt: -------------------------------------------------------------------------------- 1 | # To prevent search engines from seeing the site altogether, uncomment the next two lines: 2 | # User-Agent: * 3 | # Disallow: / 4 | -------------------------------------------------------------------------------- /cms/public/uploads/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arnu515/sveltekit-strapi-blog/ed2e966f80aad457c6c2164aa7b482c4434cb398/cms/public/uploads/.gitkeep -------------------------------------------------------------------------------- /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/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /.svelte-kit 4 | /package 5 | -------------------------------------------------------------------------------- /frontend/.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /frontend/.prettierignore: -------------------------------------------------------------------------------- 1 | .svelte-kit/** 2 | static/** 3 | build/** 4 | node_modules/** 5 | -------------------------------------------------------------------------------- /frontend/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 100 6 | } 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | %svelte.head% 9 | 10 | 11 |
%svelte.body%
12 | 13 | 14 | -------------------------------------------------------------------------------- /frontend/src/app.postcss: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | 3 | @tailwind components; 4 | 5 | @tailwind utilities; 6 | -------------------------------------------------------------------------------- /frontend/src/global.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /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/lib/Navbar.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 18 | -------------------------------------------------------------------------------- /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/lib/user.ts: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | import type { User } from './types'; 3 | 4 | const user = writable(null); 5 | 6 | export default user; 7 | -------------------------------------------------------------------------------- /frontend/src/routes/__error.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 15 | 16 |
17 |
18 |

{status}

19 |

{error.message}

20 |
21 |
22 | -------------------------------------------------------------------------------- /frontend/src/routes/__layout.svelte: -------------------------------------------------------------------------------- 1 | 28 | 29 | {#if !loading} 30 | 31 | 32 | {/if} 33 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /frontend/src/routes/login.svelte: -------------------------------------------------------------------------------- 1 | 30 | 31 |
32 |

Login

33 | 34 |
35 | 36 | 37 |
38 |
39 | 40 | 41 |
42 |
43 | 44 |
45 |
46 | 47 | 60 | -------------------------------------------------------------------------------- /frontend/src/routes/new.svelte: -------------------------------------------------------------------------------- 1 | 32 | 33 | 106 | 107 |
108 |
109 | 110 | 111 |
112 |
113 | 114 | 115 |
116 |
117 | 118 |