├── src ├── lib │ ├── InputField.svelte │ ├── HoverDropdown.svelte │ ├── Button.svelte │ ├── Tag.svelte │ ├── MediumDropdown.svelte │ ├── Comment.svelte │ ├── pocketbase.ts │ ├── UserFollow.svelte │ ├── CircleIcon.svelte │ ├── DropdownOption.svelte │ ├── SmallDropdown.svelte │ ├── Icon.svelte │ ├── Dropdown.svelte │ ├── BigToggle.svelte │ ├── SmallPost.svelte │ ├── EditModal.svelte │ └── Post.svelte ├── app.css ├── app.html └── routes │ ├── +layout.svelte │ ├── user │ └── [userid] │ │ └── +page.svelte │ └── +page.svelte ├── .env.example ├── static ├── favicon.png ├── heart-full.svg ├── heart.svg ├── close.svg ├── chevron-right.svg ├── chevron.svg ├── edit.svg ├── comment.svg ├── spinner.svg ├── bell.svg ├── dots.svg ├── trash-can.svg ├── show.svg ├── trash-can-red.svg ├── followed.svg ├── send.svg ├── hide.svg ├── follow.svg ├── gear.svg ├── message.svg ├── friends.svg ├── friends-color.svg ├── sign-out.svg ├── report.svg ├── globe.svg ├── globe-color.svg ├── profile.svg ├── verified.svg ├── help.svg ├── confetti.svg └── placeholder-logo.svg ├── postcss.config.cjs ├── Dockerfile ├── vite.config.ts ├── .gitignore ├── tailwind.config.cjs ├── .prettierignore ├── .prettierrc ├── svelte.config.js ├── tsconfig.json ├── package.json ├── compose.yml ├── README.md └── pb_schema.json /src/lib/InputField.svelte: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | DOMAIN="localhost" 2 | PUBLIC_API="http://localhost:8090" -------------------------------------------------------------------------------- /static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vtempest/spock-dev-stack/HEAD/static/favicon.png -------------------------------------------------------------------------------- /postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /src/app.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | body{ 6 | background-color: #222222; 7 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM oven/bun:alpine AS runtime 2 | WORKDIR /app 3 | # COPY package.json ./ 4 | COPY . . 5 | RUN bun i 6 | EXPOSE 5173 7 | CMD bunx --bun vite dev --host 0.0.0.0 -------------------------------------------------------------------------------- /src/lib/HoverDropdown.svelte: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { sveltekit } from '@sveltejs/kit/vite'; 2 | import type { UserConfig } from 'vite'; 3 | 4 | const config: UserConfig = { 5 | plugins: [sveltekit()] 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | /dist 7 | .env 8 | !.env.example 9 | vite.config.*.timestamp-* 10 | pb_data 11 | /pb_data 12 | .vscode 13 | *.lockb 14 | pnpm-lock.yaml -------------------------------------------------------------------------------- /tailwind.config.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ['./src/**/*.{html,js,svelte,ts}'], 4 | theme: { 5 | extend: {}, 6 | }, 7 | plugins: [require("daisyui")], 8 | } 9 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Ignore files for PNPM, NPM and YARN 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 100, 6 | "plugins": ["prettier-plugin-svelte"], 7 | "pluginSearchDirs": ["."], 8 | "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] 9 | } 10 | -------------------------------------------------------------------------------- /src/lib/Button.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | -------------------------------------------------------------------------------- /src/lib/Tag.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | -------------------------------------------------------------------------------- /src/lib/MediumDropdown.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 |
7 |

{label}

8 | dot dot dot 9 |
10 | 11 |
12 | -------------------------------------------------------------------------------- /src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | News Feed 8 | %sveltekit.head% 9 | 10 | 11 |
%sveltekit.body%
12 | 13 | 14 | -------------------------------------------------------------------------------- /static/heart-full.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static/heart.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-node'; 2 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; 3 | 4 | /** @type {import('@sveltejs/kit').Config} */ 5 | const config = { 6 | // Consult https://kit.svelte.dev/docs/integrations#preprocessors 7 | // for more information about preprocessors 8 | preprocess: vitePreprocess(), 9 | 10 | kit: { 11 | adapter: adapter({ out: 'build' }) 12 | } 13 | }; 14 | 15 | export default config; 16 | -------------------------------------------------------------------------------- /static/close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/lib/Comment.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 |
8 | Profile Icon 9 |
10 |
{author}
11 |
{text}
12 |
13 |
14 | -------------------------------------------------------------------------------- /static/chevron-right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static/chevron.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static/edit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "resolveJsonModule": true, 9 | "skipLibCheck": true, 10 | "sourceMap": true, 11 | "strict": true 12 | } 13 | // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias 14 | // 15 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes 16 | // from the referenced tsconfig.json - TypeScript does not merge them in 17 | } 18 | -------------------------------------------------------------------------------- /static/comment.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/lib/pocketbase.ts: -------------------------------------------------------------------------------- 1 | import PocketBase from 'pocketbase'; 2 | import { PUBLIC_API } from '$env/static/public' 3 | 4 | import { writable } from 'svelte/store'; 5 | 6 | 7 | // export const pb = new PocketBase(prodDatabase); 8 | export const pb = new PocketBase(PUBLIC_API); 9 | 10 | export const currentUser = writable(pb.authStore.model); 11 | 12 | //TODO: implement this 13 | export const getImageURL = (collectionId: string, recordId: string, fileName:string) => { 14 | return false; 15 | } 16 | 17 | pb.authStore.onChange((auth) => { 18 | // console.log('Google auth token', auth) 19 | currentUser.set(pb.authStore.model); 20 | }) 21 | -------------------------------------------------------------------------------- /src/lib/UserFollow.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 |
11 | 15 | 18 |
-------------------------------------------------------------------------------- /src/lib/CircleIcon.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 | 18 | -------------------------------------------------------------------------------- /static/spinner.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/lib/DropdownOption.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 | 21 | -------------------------------------------------------------------------------- /static/bell.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /static/dots.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /static/trash-can.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /static/show.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /static/trash-can-red.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /static/followed.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/lib/SmallDropdown.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 |
7 | {#if showDelete} 8 | 14 | {/if} 15 | 22 | 23 |
24 | -------------------------------------------------------------------------------- /static/send.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static/hide.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/lib/Icon.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 | {@html displayIcon.svg} -------------------------------------------------------------------------------- /src/lib/Dropdown.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 |
12 | 20 | onClick(0)} /> 21 | onClick(1)} /> 22 | onClick(2)} /> 23 |
24 | -------------------------------------------------------------------------------- /static/follow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spock-dev-stack", 3 | "version": "1.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vite dev --host 0.0.0.0 ", 7 | "build": "bunx --bun vite build", 8 | "preview": "bunx --bun vite preview --host 0.0.0.0", 9 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 10 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", 11 | "lint": "prettier --plugin-search-dir . --check . && eslint .", 12 | "format": "prettier --plugin-search-dir . --write ." 13 | }, 14 | "dependencies": { 15 | "@sveltejs/adapter-auto": "^3.2.0", 16 | "@sveltejs/kit": "^2.5.7", 17 | "@sveltejs/vite-plugin-svelte": "^3.0.0", 18 | "@typescript-eslint/eslint-plugin": "^7.7.1", 19 | "@typescript-eslint/parser": "^7.7.1", 20 | "autoprefixer": "^10.4.19", 21 | "daisyui": "^4.10.2", 22 | "eslint": "^9.1.1", 23 | "eslint-config-prettier": "^9.1.0", 24 | "eslint-plugin-svelte3": "^4.0.0", 25 | "postcss": "^8.4.38", 26 | "prettier": "^3.2.5", 27 | "prettier-plugin-svelte": "^3.2.3", 28 | "svelte": "^4.2.15", 29 | "svelte-check": "^3.6.9", 30 | "tailwindcss": "^3.4.3", 31 | "tslib": "^2.6.2", 32 | "typescript": "^5.4.5", 33 | "@sveltejs/adapter-node": "^5.0.1", 34 | "pocketbase": "^0.21.2", 35 | "vite": "^5.2.10" 36 | }, 37 | "type": "module" 38 | } -------------------------------------------------------------------------------- /compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | app-dev: 3 | build: ./ 4 | container_name: app-dev 5 | restart: unless-stopped 6 | ports: 7 | - "5173:5173" 8 | expose: 9 | - "5173" 10 | networks: 11 | - caddy 12 | working_dir: /app 13 | volumes: 14 | - /app/node_modules 15 | - .:/app 16 | environment: 17 | - PUBLIC_API=http://localhost:8090 18 | labels: 19 | caddy: app.${DOMAIN} 20 | caddy.reverse_proxy: "{{upstreams 5173}}" 21 | 22 | 23 | 24 | # Pocketbase - sqlite, auth, api, admin ui 25 | db-admin: 26 | image: ghcr.io/muchobien/pocketbase:latest 27 | container_name: pocketbase 28 | restart: unless-stopped 29 | ports: 30 | - "8090:8090" 31 | expose: 32 | - "8090" 33 | networks: 34 | - caddy 35 | volumes: 36 | - ./pb_data:/pb_data 37 | labels: 38 | caddy: api.${DOMAIN} 39 | caddy.reverse_proxy: "{{upstreams 8090}}" 40 | 41 | # Caddy - proxy server router for containers 42 | caddy: 43 | image: lucaslorentz/caddy-docker-proxy:ci-alpine 44 | container_name: caddy 45 | ports: 46 | - 80:80 47 | - 443:443 48 | volumes: 49 | - /var/run/docker.sock:/var/run/docker.sock 50 | - /var/lib/caddy/data:/data 51 | restart: unless-stopped 52 | environment: 53 | - CADDY_INGRESS_NETWORKS=caddy 54 | networks: 55 | - caddy 56 | 57 | 58 | networks: 59 | caddy: 60 | external: true -------------------------------------------------------------------------------- /static/gear.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static/message.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/lib/BigToggle.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 |
15 | 28 | 41 |
42 | -------------------------------------------------------------------------------- /static/friends.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static/friends-color.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static/sign-out.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /static/report.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static/globe.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /static/globe-color.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /static/profile.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /static/verified.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static/help.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Full-stack all-in-one in 3 containers 5 | * SvelteKit real-world demo of OAuth, posts, comments, profiles, likes, followers 6 | * Pocketbase SQL, admin dashboard 7 | * Caddy https router 8 | 9 | ## Spock Dev Stack Docs 10 | 11 | - [Svelte](https://svelte.dev/examples/hello-world) structure for reactive interface components 12 | - [Pocketbase](https://pocketbase.io/docs/js-overview/) sqlite db toolkit, admin panel, auth, api docs, ORM rules, migrations, files, js extensions 13 | - [OAuth2](https://developers.google.com/identity/protocols/oauth2) Google Signin user authentication 14 | - [Caddy](https://caddyserver.com/docs/) https server routing to containers with [caddy-docker-proxy](https://github.com/lucaslorentz/caddy-docker-proxy) in one file 15 | - [Svelte Kit](https://kit.svelte.dev) api server with server-side render 16 | - [docker compose](https://docs.docker.com/compose/gettingstarted/) manage containers in one file 17 | - [ESLint](https://github.com/dustinspecker/awesome-eslint)/[Prettier](https://prettier.io) code style 18 | - [Vite](https://vitejs.dev) bundle compiler 19 | 20 | 21 | #### Backend Alternatives 22 | 23 | - [PostgreSQL Supabase](https://supabase.com/docs/guides/getting-started/quickstarts/sveltekit) 24 | - [Drizzle ORM](https://orm.drizzle.team/docs/overview) 25 | - [Lucia OAuth](https://github.com/lucia-auth/examples/tree/main/sveltekit/github-oauth) 26 | - [Ava Unit Testing](https://github.com/avajs/ava) 27 | - [Redoc OpenAPI Internal Docs](https://github.com/Redocly/redoc?tab=readme-ov-file) 28 | - [Docusaurus Public Docs](https://docusaurus.io/docs) 29 | 30 | #### Interface Alternatives 31 | 32 | - [List of Svelte Libraries](https://github.com/TheComputerM/awesome-svelte#ui-libraries) 33 | - [Svelte Material UI](https://sveltematerialui.com/INSTALL.md) 34 | - [SkeletonUI](https://www.skeleton.dev/components/app-rail) 35 | - [Flowbite](https://flowbite-svelte.com/docs/pages/introduction) 36 | - [shadcdn-svelte](https://shadcn-svelte.com/docs) 37 | - [Icon sets](https://www.svgrepo.com/collections) 38 | 39 | 40 | ## Install 41 | 42 | 1. Install docker `curl -fsSL https://get.docker.com -o get-docker.sh; sh get-docker.sh` 43 | 2. Clone to localhost or server `git clone https://github.com/vtempest/spock-dev-stack` 44 | 3. `mv .env.example .env` and set the domain in `.env` 45 | 4. Setup and run `docker network create caddy`; `docker-compose up -d` 46 | 5. Go to `localhost:8090/_` or with caddy `api.localhost/_` or on server `api.domain.com/_` and setup admin login 47 | 6. Import Collections, load `pb_schema.json` for seed migration 48 | 7. Auth providers, get id/secret from [Google](https://console.cloud.google.com/apis/credentials) 49 | 8. Set OAuth origin `http://localhost` and `http://localhost:5173` on local or `https://domain.com` on server 50 | 9. Set redirect `http://localhost:8090/api/oauth2-redirect` or `https://api.domain.com/api/oauth2-redirect` 51 | 10. Develop app running on `app.domain.com` or `localhost:5173` 52 | 53 | 54 | 55 | > \\\\// Build fast and scale \\\\// 56 | -------------------------------------------------------------------------------- /src/lib/SmallPost.svelte: -------------------------------------------------------------------------------- 1 | 42 | 43 |
44 | {#if showPostDropdown} 45 | onDelete(id)} showDelete={$currentUser?.id === authorId} /> 46 | {/if} 47 |
48 |
49 |
50 | Profile Icon 55 |
56 | 62 |

{date}

63 |
64 |
65 |
66 | 77 | 83 |
84 |
85 | 86 |

87 | {content} 88 |

89 | 90 |
91 |

(showLikesDropdown = true)} 94 | on:pointerleave={() => (showLikesDropdown = false)} 95 | > 96 | {likes?.length} likes 97 |

98 | {#if showLikesDropdown} 99 | 100 | (call back to page and show load state while gathering most recent likes) 101 | 102 | {/if} 103 |
104 | 105 | {#if tagArr.length > 0} 106 |
107 | {#each tagArr as tag} 108 | 109 | {/each} 110 |
111 | {/if} 112 | 113 | 114 |
115 |
116 | -------------------------------------------------------------------------------- /src/lib/EditModal.svelte: -------------------------------------------------------------------------------- 1 | 23 | 24 |
27 |
28 |

Edit Profile

29 | 36 |
37 |

38 | Profile Picture (Max {maxFileSize / 1000000}Mb) 39 |

40 |
41 | Profile Icon 46 |
47 |
48 |
49 |

Preview:

50 |

maxFileSize / 1000000 52 | ? 'text-red-400 font-semibold text-sm' 53 | : 'text-neutral-400 text-sm'} 54 | > 55 | {fileSize}Mb 56 |

57 |
58 | Profile Icon 64 |
65 | 76 |
77 |
78 |

Name

79 |
80 |
81 | 82 | 83 |
84 |
85 | 86 | 87 |
88 |
89 |

Bio

90 |