Merge pull request 'webapp' (#1) from webapp into master
Reviewed-on: http://gitea.athena.server/Hestia/hestia-app/pulls/1
This commit is contained in:
commit
2f6f46bccb
54 changed files with 622 additions and 217 deletions
2
.env
2
.env
|
|
@ -1,2 +1,2 @@
|
||||||
APP_VERSION=1.0.0-alpha
|
VITE_APP_VERSION=1.0.0-alpha
|
||||||
DATABASE_URL="file:./dev.db"
|
DATABASE_URL="file:./dev.db"
|
||||||
14
.github/workflows/pr.yml
vendored
14
.github/workflows/pr.yml
vendored
|
|
@ -1,14 +0,0 @@
|
||||||
name: PR Checks
|
|
||||||
on: [pull_request]
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: oven-sh/setup-bun@v2
|
|
||||||
with:
|
|
||||||
bun-version: latest
|
|
||||||
- name: install
|
|
||||||
run: bun install
|
|
||||||
- name: build
|
|
||||||
run: bun run clean && bun run build
|
|
||||||
20
.gitignore
vendored
20
.gitignore
vendored
|
|
@ -1,2 +1,20 @@
|
||||||
|
test-results
|
||||||
node_modules
|
node_modules
|
||||||
build
|
|
||||||
|
# Output
|
||||||
|
.output
|
||||||
|
.vercel
|
||||||
|
.netlify
|
||||||
|
.wrangler
|
||||||
|
/.svelte-kit
|
||||||
|
/build
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Vite
|
||||||
|
vite.config.js.timestamp-*
|
||||||
|
vite.config.ts.timestamp-*
|
||||||
|
|
||||||
|
*storybook.log
|
||||||
1
.npmrc
Normal file
1
.npmrc
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
engine-strict=true
|
||||||
4
.prettierignore
Normal file
4
.prettierignore
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
# Package Managers
|
||||||
|
package-lock.json
|
||||||
|
pnpm-lock.yaml
|
||||||
|
yarn.lock
|
||||||
15
.prettierrc
Normal file
15
.prettierrc
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"useTabs": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"printWidth": 100,
|
||||||
|
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": "*.svelte",
|
||||||
|
"options": {
|
||||||
|
"parser": "svelte"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
16
.storybook/main.js
Normal file
16
.storybook/main.js
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
/** @type { import('@storybook/sveltekit').StorybookConfig } */
|
||||||
|
const config = {
|
||||||
|
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|ts|svelte)'],
|
||||||
|
addons: [
|
||||||
|
'@storybook/addon-svelte-csf',
|
||||||
|
'@storybook/addon-essentials',
|
||||||
|
'@chromatic-com/storybook',
|
||||||
|
'@storybook/addon-interactions',
|
||||||
|
'storybook-dark-mode'
|
||||||
|
],
|
||||||
|
framework: {
|
||||||
|
name: '@storybook/sveltekit',
|
||||||
|
options: {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
export default config;
|
||||||
13
.storybook/preview.js
Normal file
13
.storybook/preview.js
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
/** @type { import('@storybook/svelte').Preview } */
|
||||||
|
const preview = {
|
||||||
|
parameters: {
|
||||||
|
controls: {
|
||||||
|
matchers: {
|
||||||
|
color: /(background|color)$/i,
|
||||||
|
date: /Date$/i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default preview;
|
||||||
40
README.md
40
README.md
|
|
@ -1,22 +1,38 @@
|
||||||
# Hestia
|
# sv
|
||||||
|
|
||||||
To install dependencies:
|
Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli).
|
||||||
|
|
||||||
|
## Creating a project
|
||||||
|
|
||||||
|
If you're seeing this, you've probably already done this step. Congrats!
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bun install
|
# create a new project in the current directory
|
||||||
|
npx sv create
|
||||||
|
|
||||||
|
# create a new project in my-app
|
||||||
|
npx sv create my-app
|
||||||
```
|
```
|
||||||
|
|
||||||
To run:
|
## Developing
|
||||||
|
|
||||||
|
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bun run src/index.ts
|
npm run dev
|
||||||
|
|
||||||
|
# or start the server and open the app in a new browser tab
|
||||||
|
npm run dev -- --open
|
||||||
```
|
```
|
||||||
|
|
||||||
## Stack
|
## Building
|
||||||
|
|
||||||
- **Bun** Package manager
|
To create a production version of your app:
|
||||||
- **Yoga** GraphQL Server
|
|
||||||
- **Pothos** GraphQL Schema Builder
|
```bash
|
||||||
- **Prisma** Database ORM
|
npm run build
|
||||||
- **Pino** Logger
|
```
|
||||||
- **Zod** Schema validation
|
|
||||||
|
You can preview the production build with `npm run preview`.
|
||||||
|
|
||||||
|
> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.
|
||||||
|
|
|
||||||
BIN
bun.lockb
BIN
bun.lockb
Binary file not shown.
6
e2e/demo.test.ts
Normal file
6
e2e/demo.test.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { expect, test } from '@playwright/test';
|
||||||
|
|
||||||
|
test('home page has expected h1', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
await expect(page.locator('h1')).toBeVisible();
|
||||||
|
});
|
||||||
34
eslint.config.js
Normal file
34
eslint.config.js
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
import prettier from 'eslint-config-prettier';
|
||||||
|
import js from '@eslint/js';
|
||||||
|
import { includeIgnoreFile } from '@eslint/compat';
|
||||||
|
import svelte from 'eslint-plugin-svelte';
|
||||||
|
import globals from 'globals';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import ts from 'typescript-eslint';
|
||||||
|
const gitignorePath = fileURLToPath(new URL('./.gitignore', import.meta.url));
|
||||||
|
|
||||||
|
export default ts.config(
|
||||||
|
includeIgnoreFile(gitignorePath),
|
||||||
|
js.configs.recommended,
|
||||||
|
...ts.configs.recommended,
|
||||||
|
...svelte.configs['flat/recommended'],
|
||||||
|
prettier,
|
||||||
|
...svelte.configs['flat/prettier'],
|
||||||
|
{
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.browser,
|
||||||
|
...globals.node
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['**/*.svelte'],
|
||||||
|
|
||||||
|
languageOptions: {
|
||||||
|
parserOptions: {
|
||||||
|
parser: ts.parser
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
// @ts-check
|
|
||||||
|
|
||||||
import eslint from '@eslint/js';
|
|
||||||
import tseslint from 'typescript-eslint';
|
|
||||||
// @ts-expect-error No type-def
|
|
||||||
import eslintConfigPrettier from 'eslint-config-prettier';
|
|
||||||
|
|
||||||
export default tseslint.config(
|
|
||||||
{ files: ['{app,src}/**/*.{js,mjs,ts}'] },
|
|
||||||
{ ignores: ['build/*'] },
|
|
||||||
eslint.configs.recommended,
|
|
||||||
tseslint.configs.recommended,
|
|
||||||
eslintConfigPrettier
|
|
||||||
);
|
|
||||||
60
package.json
60
package.json
|
|
@ -1,35 +1,65 @@
|
||||||
{
|
{
|
||||||
"name": "hestia",
|
"name": "hestia",
|
||||||
"module": "src/index.ts",
|
"version": "0.0.1",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "bun build ./src/index.ts --outdir ./build",
|
"dev": "vite dev",
|
||||||
"clean": "rm -rf ./build",
|
"build": "vite build",
|
||||||
"dev": "bun --watch src/index.ts | pino-pretty",
|
"preview": "vite preview",
|
||||||
"format": "prettier . --write",
|
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||||
"lint": "",
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||||
|
"format": "prettier --write .",
|
||||||
|
"lint": "prettier --check . && eslint .",
|
||||||
|
"test:unit": "vitest",
|
||||||
|
"test": "npm run test:unit -- --run && npm run test:e2e",
|
||||||
|
"storybook": "storybook dev -p 6006",
|
||||||
|
"build-storybook": "storybook build",
|
||||||
|
"test:e2e": "playwright test",
|
||||||
"prisma:generate": "prisma generate"
|
"prisma:generate": "prisma generate"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.16.0",
|
"@chromatic-com/storybook": "^3.2.2",
|
||||||
"@types/bun": "latest",
|
"@eslint/compat": "^1.2.3",
|
||||||
"eslint": "^9.16.0",
|
"@playwright/test": "^1.45.3",
|
||||||
|
"@storybook/addon-essentials": "^8.4.7",
|
||||||
|
"@storybook/addon-interactions": "^8.4.7",
|
||||||
|
"@storybook/addon-svelte-csf": "^5.0.0-next.13",
|
||||||
|
"@storybook/blocks": "^8.4.7",
|
||||||
|
"@storybook/svelte": "^8.4.7",
|
||||||
|
"@storybook/sveltekit": "^8.4.7",
|
||||||
|
"@storybook/test": "^8.4.7",
|
||||||
|
"@sveltejs/adapter-auto": "^3.0.0",
|
||||||
|
"@sveltejs/kit": "^2.9.0",
|
||||||
|
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
||||||
|
"autoprefixer": "^10.4.20",
|
||||||
|
"eslint": "^9.7.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"prettier": "3.4.1",
|
"eslint-plugin-svelte": "^2.36.0",
|
||||||
|
"globals": "^15.0.0",
|
||||||
|
"prettier": "^3.3.2",
|
||||||
|
"prettier-plugin-svelte": "^3.2.6",
|
||||||
|
"prettier-plugin-tailwindcss": "^0.6.5",
|
||||||
"prisma": "^6.0.1",
|
"prisma": "^6.0.1",
|
||||||
"typescript-eslint": "^8.17.0"
|
"storybook": "^8.4.7",
|
||||||
},
|
"svelte": "^5.0.0",
|
||||||
"peerDependencies": {
|
"svelte-check": "^4.0.0",
|
||||||
"typescript": "^5.0.0"
|
"tailwindcss": "^3.4.9",
|
||||||
|
"typescript": "^5.0.0",
|
||||||
|
"typescript-eslint": "^8.0.0",
|
||||||
|
"vite": "^6.0.0",
|
||||||
|
"vitest": "^2.0.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@pothos/core": "^4.3.0",
|
"@pothos/core": "^4.3.0",
|
||||||
"@pothos/plugin-prisma": "^4.4.0",
|
"@pothos/plugin-prisma": "^4.4.0",
|
||||||
"@prisma/client": "6.0.1",
|
"@prisma/client": "6.0.1",
|
||||||
|
"@tailwindcss/typography": "^0.5.15",
|
||||||
|
"@types/bun": "^1.1.14",
|
||||||
"graphql": "^16.9.0",
|
"graphql": "^16.9.0",
|
||||||
"graphql-yoga": "^5.10.4",
|
"graphql-yoga": "^5.10.4",
|
||||||
"pino": "^9.5.0",
|
"pino": "^9.5.0",
|
||||||
"pino-pretty": "^13.0.0",
|
"pino-pretty": "^13.0.0",
|
||||||
"zod": "^3.23.8"
|
"storybook-dark-mode": "^4.0.2",
|
||||||
|
"zod": "^3.24.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
10
playwright.config.ts
Normal file
10
playwright.config.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { defineConfig } from '@playwright/test';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
webServer: {
|
||||||
|
command: 'npm run build && npm run preview',
|
||||||
|
port: 4173
|
||||||
|
},
|
||||||
|
|
||||||
|
testDir: 'e2e'
|
||||||
|
});
|
||||||
6
postcss.config.js
Normal file
6
postcss.config.js
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
export default {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
/**
|
|
||||||
* @see https://prettier.io/docs/en/configuration.html
|
|
||||||
* @type {import("prettier").Config}
|
|
||||||
*/
|
|
||||||
const config = {
|
|
||||||
trailingComma: 'es5',
|
|
||||||
tabWidth: 4,
|
|
||||||
useTabs: true,
|
|
||||||
semi: true,
|
|
||||||
singleQuote: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default config;
|
|
||||||
BIN
prisma/dev.db
BIN
prisma/dev.db
Binary file not shown.
Binary file not shown.
3
src/app.css
Normal file
3
src/app.css
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
@import 'tailwindcss/base';
|
||||||
|
@import 'tailwindcss/components';
|
||||||
|
@import 'tailwindcss/utilities';
|
||||||
13
src/app.d.ts
vendored
Normal file
13
src/app.d.ts
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
// See https://svelte.dev/docs/kit/types#app.d.ts
|
||||||
|
// for information about these interfaces
|
||||||
|
declare global {
|
||||||
|
namespace App {
|
||||||
|
// interface Error {}
|
||||||
|
// interface Locals {}
|
||||||
|
// interface PageData {}
|
||||||
|
// interface PageState {}
|
||||||
|
// interface Platform {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {};
|
||||||
12
src/app.html
Normal file
12
src/app.html
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
%sveltekit.head%
|
||||||
|
</head>
|
||||||
|
<body data-sveltekit-preload-data="hover">
|
||||||
|
<div style="display: contents">%sveltekit.body%</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
7
src/demo.spec.ts
Normal file
7
src/demo.spec.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
|
||||||
|
describe('sum test', () => {
|
||||||
|
it('adds 1 + 2 to equal 3', () => {
|
||||||
|
expect(1 + 2).toBe(3);
|
||||||
|
});
|
||||||
|
});
|
||||||
15
src/index.ts
15
src/index.ts
|
|
@ -1,15 +0,0 @@
|
||||||
import { logger } from '@lib/logger';
|
|
||||||
import { yoga } from './yoga';
|
|
||||||
|
|
||||||
const server = Bun.serve({
|
|
||||||
fetch: yoga.fetch,
|
|
||||||
error: (error) => {
|
|
||||||
logger.error(error.message);
|
|
||||||
return new Response('', {
|
|
||||||
status: 500,
|
|
||||||
statusText: 'You fucked the goose',
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
logger.info(`Server is running on: ${server.url}${yoga.graphqlEndpoint}`);
|
|
||||||
30
src/lib/components/Button.stories.svelte
Normal file
30
src/lib/components/Button.stories.svelte
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
<script module lang="ts">
|
||||||
|
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||||
|
import Button from './Button.svelte';
|
||||||
|
import { fn } from '@storybook/test';
|
||||||
|
|
||||||
|
const { Story } = defineMeta({
|
||||||
|
title: 'Button',
|
||||||
|
component: Button,
|
||||||
|
tags: ['autodocs'],
|
||||||
|
args: {
|
||||||
|
onClick: fn()
|
||||||
|
},
|
||||||
|
argTypes: {
|
||||||
|
size: {
|
||||||
|
control: 'select',
|
||||||
|
options: ['small', 'normal', 'large'],
|
||||||
|
defaultValue: 'normal'
|
||||||
|
},
|
||||||
|
backgroundColor: {
|
||||||
|
control: 'color'
|
||||||
|
},
|
||||||
|
primary: {
|
||||||
|
control: 'boolean',
|
||||||
|
defaultValue: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Story name="Default" args={{ label: 'Button', size: 'normal', primary: true }} />
|
||||||
51
src/lib/components/Button.svelte
Normal file
51
src/lib/components/Button.svelte
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLButtonAttributes } from 'svelte/elements';
|
||||||
|
|
||||||
|
let {
|
||||||
|
type = 'button',
|
||||||
|
onClick,
|
||||||
|
label,
|
||||||
|
size = 'normal',
|
||||||
|
backgroundColor,
|
||||||
|
primary = false
|
||||||
|
}: {
|
||||||
|
type: HTMLButtonAttributes['type'];
|
||||||
|
onClick: () => void;
|
||||||
|
label: string;
|
||||||
|
size: 'small' | 'normal' | 'large';
|
||||||
|
backgroundColor: string;
|
||||||
|
primary: boolean;
|
||||||
|
} = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button
|
||||||
|
{type}
|
||||||
|
onclick={onClick}
|
||||||
|
class={`button button--${size}`}
|
||||||
|
style:background-color={backgroundColor}
|
||||||
|
class:button--primary={primary}
|
||||||
|
class:button--secondary={!primary}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.button {
|
||||||
|
@apply inline-block cursor-pointer rounded-full border-0 font-semibold leading-none transition-colors hover:opacity-80 hover:shadow-lg;
|
||||||
|
}
|
||||||
|
.button--small {
|
||||||
|
@apply px-2 py-0.5 text-sm;
|
||||||
|
}
|
||||||
|
.button--normal {
|
||||||
|
@apply px-2 py-1 text-base;
|
||||||
|
}
|
||||||
|
.button--large {
|
||||||
|
@apply px-2.5 py-1 text-xl;
|
||||||
|
}
|
||||||
|
.button--primary {
|
||||||
|
@apply bg-red-600 text-white;
|
||||||
|
}
|
||||||
|
.button--secondary {
|
||||||
|
@apply bg-blue-600 text-white;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
12
src/lib/components/Loader.stories.svelte
Normal file
12
src/lib/components/Loader.stories.svelte
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
<script module lang="ts">
|
||||||
|
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||||
|
import Loader from './Loader.svelte';
|
||||||
|
|
||||||
|
const { Story } = defineMeta({
|
||||||
|
title: 'Loader',
|
||||||
|
component: Loader,
|
||||||
|
tags: ['autodocs']
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Story name="Default" args={{}} />
|
||||||
51
src/lib/components/Loader.svelte
Normal file
51
src/lib/components/Loader.svelte
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
<script lang="ts"></script>
|
||||||
|
|
||||||
|
<span class="loader"></span>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.loader {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
border: 3px dotted #fff;
|
||||||
|
border-style: solid solid dotted dotted;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
box-sizing: border-box;
|
||||||
|
animation: rotation 2s linear infinite;
|
||||||
|
}
|
||||||
|
.loader::after {
|
||||||
|
content: '';
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
margin: auto;
|
||||||
|
border: 3px dotted #ff3d00;
|
||||||
|
border-style: solid solid dotted;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: rotationBack 1s linear infinite;
|
||||||
|
transform-origin: center center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rotation {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes rotationBack {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(-360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
12
src/lib/components/Navbar.stories.svelte
Normal file
12
src/lib/components/Navbar.stories.svelte
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
<script module lang="ts">
|
||||||
|
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||||
|
import Navbar from './Navbar.svelte';
|
||||||
|
|
||||||
|
const { Story } = defineMeta({
|
||||||
|
title: 'Navbar',
|
||||||
|
component: Navbar,
|
||||||
|
tags: ['autodocs']
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Story name="Default" args={{ title: 'Storybook' }} />
|
||||||
31
src/lib/components/Navbar.svelte
Normal file
31
src/lib/components/Navbar.svelte
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
<script lang="ts">
|
||||||
|
let { title }: { title: string } = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<header>
|
||||||
|
<div class="navbar">
|
||||||
|
<div>
|
||||||
|
<h2>Hestia</h2>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h1>{title}</h1>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p>Welcome!</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.navbar {
|
||||||
|
@apply flex items-center justify-between border-slate-200 bg-slate-100 px-6 py-2 font-display drop-shadow;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar h1 {
|
||||||
|
@apply text-2xl;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar h2 {
|
||||||
|
@apply text-xl;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
1
src/lib/components/index.ts
Normal file
1
src/lib/components/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './Navbar.svelte';
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { logger } from '@lib/logger';
|
import { logger } from '$lib/logger';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
export interface Configuration {
|
export interface Configuration {
|
||||||
|
|
@ -8,15 +8,17 @@ export interface Configuration {
|
||||||
export const LoadConfig = (): Configuration => {
|
export const LoadConfig = (): Configuration => {
|
||||||
const { success, data, error } = z
|
const { success, data, error } = z
|
||||||
.object({
|
.object({
|
||||||
APP_VERSION: z.string().default('development'),
|
VITE_APP_VERSION: z.string().default('development')
|
||||||
})
|
})
|
||||||
.safeParse(process.env);
|
.safeParse(import.meta.env);
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
logger.error(error.message);
|
logger.error(error.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
app_version: data!.APP_VERSION,
|
app_version: data!.VITE_APP_VERSION
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const Config = LoadConfig();
|
||||||
2
src/lib/index.ts
Normal file
2
src/lib/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
// place files you want to import through the `$lib` alias in this folder.
|
||||||
|
export * from './components';
|
||||||
|
|
@ -19,5 +19,5 @@ export const yogaLogger: YogaLogger = {
|
||||||
error(...args) {
|
error(...args) {
|
||||||
// @ts-expect-error types dont match
|
// @ts-expect-error types dont match
|
||||||
logger.error(...args);
|
logger.error(...args);
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
65
src/lib/pothos/index.ts
Normal file
65
src/lib/pothos/index.ts
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
import { prisma } from '$lib/prisma';
|
||||||
|
import { Context } from '$lib/yoga/context';
|
||||||
|
import SchemaBuilder from '@pothos/core';
|
||||||
|
import PrismaPlugin, { type PrismaTypesFromClient } from '@pothos/plugin-prisma';
|
||||||
|
|
||||||
|
type ContextType = ReturnType<typeof Context>;
|
||||||
|
|
||||||
|
export const builder = new SchemaBuilder<{
|
||||||
|
Context: ContextType;
|
||||||
|
PrismaTypes: PrismaTypesFromClient<typeof prisma>;
|
||||||
|
}>({
|
||||||
|
plugins: [PrismaPlugin],
|
||||||
|
prisma: {
|
||||||
|
client: prisma,
|
||||||
|
// defaults to false, uses /// comments from prisma schema as descriptions
|
||||||
|
// for object types, relations and exposed fields.
|
||||||
|
// descriptions can be omitted by setting description to false
|
||||||
|
exposeDescriptions: false,
|
||||||
|
// use where clause from prismaRelatedConnection for totalCount (defaults to true)
|
||||||
|
filterConnectionTotalCount: true,
|
||||||
|
// warn when not using a query parameter correctly
|
||||||
|
onUnusedQuery: process.env.NODE_ENV === 'production' ? null : 'warn'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const User = builder.prismaObject('User', {
|
||||||
|
fields: (t) => ({
|
||||||
|
id: t.exposeID('id'),
|
||||||
|
email: t.exposeString('email'),
|
||||||
|
name: t.exposeString('name'),
|
||||||
|
posts: t.relation('posts')
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const Post = builder.prismaObject('Post', {
|
||||||
|
fields: (t) => ({
|
||||||
|
id: t.exposeID('id'),
|
||||||
|
title: t.exposeString('title'),
|
||||||
|
content: t.exposeString('content'),
|
||||||
|
published: t.exposeBoolean('published'),
|
||||||
|
author: t.relation('author')
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.queryType({
|
||||||
|
fields: (t) => ({
|
||||||
|
version: t.string({
|
||||||
|
resolve: (parent, args, context) => context.config.app_version
|
||||||
|
}),
|
||||||
|
users: t.prismaField({
|
||||||
|
type: [User],
|
||||||
|
resolve: async () => {
|
||||||
|
return await prisma.user.findMany();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
posts: t.prismaField({
|
||||||
|
type: [Post],
|
||||||
|
resolve: async () => {
|
||||||
|
return await prisma.post.findMany();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
export const Schema = builder.toSchema();
|
||||||
7
src/lib/yoga/context.ts
Normal file
7
src/lib/yoga/context.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { Config } from '$lib/config';
|
||||||
|
import type { YogaInitialContext } from 'graphql-yoga';
|
||||||
|
|
||||||
|
export const Context = (initialContext: YogaInitialContext) => ({
|
||||||
|
...initialContext,
|
||||||
|
config: Config
|
||||||
|
});
|
||||||
14
src/lib/yoga/index.ts
Normal file
14
src/lib/yoga/index.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { yogaLogger } from '$lib/logger';
|
||||||
|
import { Schema } from '$lib/pothos';
|
||||||
|
import type { RequestEvent } from '@sveltejs/kit';
|
||||||
|
import { createYoga } from 'graphql-yoga';
|
||||||
|
import { Context } from './context';
|
||||||
|
|
||||||
|
export const Yoga = createYoga<RequestEvent>({
|
||||||
|
context: Context,
|
||||||
|
schema: Schema,
|
||||||
|
graphqlEndpoint: '/api/graphql',
|
||||||
|
// Let Yoga use sveltekit's Response object
|
||||||
|
fetchAPI: { Response },
|
||||||
|
logging: yogaLogger
|
||||||
|
});
|
||||||
6
src/routes/+layout.svelte
Normal file
6
src/routes/+layout.svelte
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import '../app.css';
|
||||||
|
let { children } = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{@render children()}
|
||||||
27
src/routes/+page.svelte
Normal file
27
src/routes/+page.svelte
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { goto } from '$app/navigation';
|
||||||
|
import Loader from '$lib/components/Loader.svelte';
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
const id = setTimeout(() => {
|
||||||
|
goto('/app');
|
||||||
|
}, 1500);
|
||||||
|
return () => {
|
||||||
|
clearTimeout(id);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="site-loader">
|
||||||
|
<h1>Hestia</h1>
|
||||||
|
<Loader />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.site-loader {
|
||||||
|
@apply flex h-screen w-screen flex-col items-center justify-center gap-6 bg-slate-100;
|
||||||
|
}
|
||||||
|
.site-loader h1 {
|
||||||
|
@apply font-display text-4xl;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
3
src/routes/api/graphql/+server.ts
Normal file
3
src/routes/api/graphql/+server.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
import { Yoga } from '$lib/yoga';
|
||||||
|
|
||||||
|
export { Yoga as GET, Yoga as POST };
|
||||||
8
src/routes/app/+layout.svelte
Normal file
8
src/routes/app/+layout.svelte
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import Navbar from '$lib/components/Navbar.svelte';
|
||||||
|
|
||||||
|
let { children } = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Navbar title="Svelte" />
|
||||||
|
{@render children()}
|
||||||
0
src/routes/app/+page.svelte
Normal file
0
src/routes/app/+page.svelte
Normal file
|
|
@ -1,29 +0,0 @@
|
||||||
import type { Configuration } from '@app/config';
|
|
||||||
import { prisma } from '@app/prisma';
|
|
||||||
import SchemaBuilder from '@pothos/core';
|
|
||||||
import PrismaPlugin, {
|
|
||||||
type PrismaTypesFromClient,
|
|
||||||
} from '@pothos/plugin-prisma';
|
|
||||||
import type { YogaInitialContext } from 'graphql-yoga';
|
|
||||||
|
|
||||||
type Context = YogaInitialContext & {
|
|
||||||
config: Configuration;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const builder = new SchemaBuilder<{
|
|
||||||
Context: Context;
|
|
||||||
PrismaTypes: PrismaTypesFromClient<typeof prisma>;
|
|
||||||
}>({
|
|
||||||
plugins: [PrismaPlugin],
|
|
||||||
prisma: {
|
|
||||||
client: prisma,
|
|
||||||
// defaults to false, uses /// comments from prisma schema as descriptions
|
|
||||||
// for object types, relations and exposed fields.
|
|
||||||
// descriptions can be omitted by setting description to false
|
|
||||||
exposeDescriptions: false,
|
|
||||||
// use where clause from prismaRelatedConnection for totalCount (defaults to true)
|
|
||||||
filterConnectionTotalCount: true,
|
|
||||||
// warn when not using a query parameter correctly
|
|
||||||
onUnusedQuery: process.env.NODE_ENV === 'production' ? null : 'warn',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
import { LoadConfig } from '@app/config';
|
|
||||||
import type { YogaInitialContext } from 'graphql-yoga';
|
|
||||||
|
|
||||||
export const context = (initialContext: YogaInitialContext) => {
|
|
||||||
const config = LoadConfig();
|
|
||||||
return {
|
|
||||||
...initialContext,
|
|
||||||
config,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
import { yogaLogger } from '@lib/logger';
|
|
||||||
import { createYoga } from 'graphql-yoga';
|
|
||||||
import { context } from './context';
|
|
||||||
import { schema } from './schema';
|
|
||||||
|
|
||||||
export const yoga = createYoga({
|
|
||||||
schema,
|
|
||||||
context: context,
|
|
||||||
logging: yogaLogger,
|
|
||||||
});
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
import { prisma } from '@app/prisma';
|
|
||||||
import { builder } from './builder';
|
|
||||||
|
|
||||||
const User = builder.prismaObject('User', {
|
|
||||||
fields: (t) => ({
|
|
||||||
id: t.exposeID('id'),
|
|
||||||
email: t.exposeString('email'),
|
|
||||||
name: t.exposeString('name'),
|
|
||||||
posts: t.relation('posts'),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
const Post = builder.prismaObject('Post', {
|
|
||||||
fields: (t) => ({
|
|
||||||
id: t.exposeID('id'),
|
|
||||||
title: t.exposeString('title'),
|
|
||||||
content: t.exposeString('content'),
|
|
||||||
published: t.exposeBoolean('published'),
|
|
||||||
author: t.relation('author'),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
builder.queryType({
|
|
||||||
fields: (t) => ({
|
|
||||||
version: t.string({
|
|
||||||
resolve: (parent, args, context) => context.config.app_version,
|
|
||||||
}),
|
|
||||||
users: t.prismaField({
|
|
||||||
type: [User],
|
|
||||||
resolve: async () => {
|
|
||||||
return await prisma.user.findMany();
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
posts: t.prismaField({
|
|
||||||
type: [Post],
|
|
||||||
resolve: async () => {
|
|
||||||
return await prisma.post.findMany();
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const schema = builder.toSchema();
|
|
||||||
BIN
static/favicon.png
Normal file
BIN
static/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
18
svelte.config.js
Normal file
18
svelte.config.js
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import adapter from '@sveltejs/adapter-auto';
|
||||||
|
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
||||||
|
|
||||||
|
/** @type {import('@sveltejs/kit').Config} */
|
||||||
|
const config = {
|
||||||
|
// Consult https://svelte.dev/docs/kit/integrations
|
||||||
|
// for more information about preprocessors
|
||||||
|
preprocess: vitePreprocess(),
|
||||||
|
|
||||||
|
kit: {
|
||||||
|
// adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
|
||||||
|
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
|
||||||
|
// See https://svelte.dev/docs/kit/adapters for more information about adapters.
|
||||||
|
adapter: adapter()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
16
tailwind.config.ts
Normal file
16
tailwind.config.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
import typography from '@tailwindcss/typography';
|
||||||
|
import type { Config } from 'tailwindcss';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
content: ['./src/**/*.{html,js,svelte,ts}'],
|
||||||
|
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
fontFamily: {
|
||||||
|
display: ['Baskervville SC']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
plugins: [typography]
|
||||||
|
} satisfies Config;
|
||||||
|
|
@ -1,36 +1,19 @@
|
||||||
{
|
{
|
||||||
|
"extends": "./.svelte-kit/tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
// Enable latest features
|
|
||||||
"lib": ["ESNext", "DOM"],
|
|
||||||
"target": "ESNext",
|
|
||||||
"module": "ESNext",
|
|
||||||
"moduleDetection": "force",
|
|
||||||
"jsx": "react-jsx",
|
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
|
"checkJs": true,
|
||||||
// Bundler mode
|
"esModuleInterop": true,
|
||||||
"moduleResolution": "bundler",
|
"forceConsistentCasingInFileNames": true,
|
||||||
"allowImportingTsExtensions": true,
|
"resolveJsonModule": true,
|
||||||
"verbatimModuleSyntax": true,
|
|
||||||
"noEmit": true,
|
|
||||||
|
|
||||||
// Best practices
|
|
||||||
"strict": true,
|
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"noFallthroughCasesInSwitch": true,
|
"sourceMap": true,
|
||||||
|
"strict": true,
|
||||||
// Some stricter flags (disabled by default)
|
"moduleResolution": "bundler"
|
||||||
"noUnusedLocals": false,
|
|
||||||
"noUnusedParameters": false,
|
|
||||||
"noPropertyAccessFromIndexSignature": false,
|
|
||||||
|
|
||||||
// Path mapping
|
|
||||||
"baseUrl": ".",
|
|
||||||
"paths": {
|
|
||||||
"@app": ["./src"],
|
|
||||||
"@app/*": ["./src/*"],
|
|
||||||
"@lib": ["./lib"],
|
|
||||||
"@lib/*": ["./lib/*"]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
|
||||||
|
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
|
||||||
|
//
|
||||||
|
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
||||||
|
// from the referenced tsconfig.json - TypeScript does not merge them in
|
||||||
}
|
}
|
||||||
|
|
|
||||||
10
vite.config.ts
Normal file
10
vite.config.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { defineConfig } from 'vitest/config';
|
||||||
|
import { sveltekit } from '@sveltejs/kit/vite';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [sveltekit()],
|
||||||
|
|
||||||
|
test: {
|
||||||
|
include: ['src/**/*.{test,spec}.{js,ts}']
|
||||||
|
}
|
||||||
|
});
|
||||||
Loading…
Add table
Reference in a new issue