This commit is contained in:
2025-04-08 00:13:48 +02:00
parent 0ad70b3c7d
commit cdd9f01214
11 changed files with 345 additions and 34 deletions

4
.gitignore vendored
View File

@@ -43,4 +43,6 @@ yarn-error.log*
*.tsbuildinfo
# idea files
.idea
.idea
# clerk configuration (can include secrets)
/.clerk/

View File

@@ -18,6 +18,7 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@clerk/nextjs": "^6.14.0",
"@neondatabase/serverless": "^1.0.0",
"@t3-oss/env-nextjs": "^0.12.0",
"drizzle-orm": "^0.41.0",
@@ -25,6 +26,7 @@
"postgres": "^3.4.4",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"server-only": "^0.0.1",
"zod": "^3.24.2"
},
"devDependencies": {

201
pnpm-lock.yaml generated
View File

@@ -8,6 +8,9 @@ importers:
.:
dependencies:
'@clerk/nextjs':
specifier: ^6.14.0
version: 6.14.0(next@15.2.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@neondatabase/serverless':
specifier: ^1.0.0
version: 1.0.0
@@ -29,6 +32,9 @@ importers:
react-dom:
specifier: ^19.0.0
version: 19.1.0(react@19.1.0)
server-only:
specifier: ^0.0.1
version: 0.0.1
zod:
specifier: ^3.24.2
version: 3.24.2
@@ -120,6 +126,46 @@ packages:
cpu: [x64]
os: [win32]
'@clerk/backend@1.27.0':
resolution: {integrity: sha512-WqdigkqGyVcU0Y08xCJSor6I8j7WxtlZfMlIbHyw+JM8U06kNVSX9umRmTePFRFN02EwIQmQmqnRXVn0kwMyBQ==}
engines: {node: '>=18.17.0'}
peerDependencies:
svix: ^1.62.0
peerDependenciesMeta:
svix:
optional: true
'@clerk/clerk-react@5.26.0':
resolution: {integrity: sha512-9PQfCSFvpQDtL5fcp/gnQHBKvkMSW/FTuIEHdzhqhcO8xJRcOmcE0z5YjrnhytB3j3P1fx2gdiJgO5/OaU33hg==}
engines: {node: '>=18.17.0'}
peerDependencies:
react: ^18.0.0 || ^19.0.0 || ^19.0.0-0
react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-0
'@clerk/nextjs@6.14.0':
resolution: {integrity: sha512-kSb0yPLbaACyRcvGDB2T0/IyypNYBQMIUP95YhFrZZWJDKR96rUdzw5ptG/OaP+vw7fAyhfz3Q8OeMrn97hxBw==}
engines: {node: '>=18.17.0'}
peerDependencies:
next: ^13.5.7 || ^14.2.25 || ^15.2.3
react: ^18.0.0 || ^19.0.0 || ^19.0.0-0
react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-0
'@clerk/shared@3.4.0':
resolution: {integrity: sha512-3ms1JZVNXe/qJXdsFY+QlOYKcocdMKDHXDr/EvpuQV+WsLKkpMcswH2fbWNhsWG2otekL2sIYBVkCy+/0hBlPA==}
engines: {node: '>=18.17.0'}
peerDependencies:
react: ^18.0.0 || ^19.0.0 || ^19.0.0-0
react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-0
peerDependenciesMeta:
react:
optional: true
react-dom:
optional: true
'@clerk/types@4.51.0':
resolution: {integrity: sha512-igUZ5BOe3rOgQSM8v9f6zHCOowX3yV36zWYImMUWD61cpLqUokKtN6qH2x8sHUHrq3B9Dj3AsXItGaQsLNs3mw==}
engines: {node: '>=18.17.0'}
'@drizzle-team/brocli@0.10.2':
resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==}
@@ -721,6 +767,10 @@ packages:
resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==}
engines: {node: '>=12.5.0'}
cookie@1.0.2:
resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==}
engines: {node: '>=18'}
csstype@3.1.3:
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
@@ -733,10 +783,17 @@ packages:
supports-color:
optional: true
dequal@2.0.3:
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
engines: {node: '>=6'}
detect-libc@2.0.3:
resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==}
engines: {node: '>=8'}
dot-case@3.0.4:
resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==}
drizzle-kit@0.30.6:
resolution: {integrity: sha512-U4wWit0fyZuGuP7iNmRleQyK2V8wCuv57vf5l3MnG4z4fzNTjY/U13M8owyQ5RavqvqxBifWORaR3wIUzlN64g==}
hasBin: true
@@ -861,6 +918,9 @@ packages:
get-tsconfig@4.10.0:
resolution: {integrity: sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==}
glob-to-regexp@0.4.1:
resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==}
graceful-fs@4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
@@ -875,6 +935,10 @@ packages:
resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==}
hasBin: true
js-cookie@3.0.5:
resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==}
engines: {node: '>=14'}
lightningcss-darwin-arm64@1.29.2:
resolution: {integrity: sha512-cK/eMabSViKn/PG8U/a7aCorpeKLMlK0bQeNHmdb7qUnBkNPnL+oV5DjJUo0kqWsJUapZsM4jCfYItbqBDvlcA==}
engines: {node: '>= 12.0.0'}
@@ -939,6 +1003,13 @@ packages:
resolution: {integrity: sha512-6b6gd/RUXKaw5keVdSEtqFVdzWnU5jMxTUjA2bVcMNPLwSQ08Sv/UodBVtETLCn7k4S1Ibxwh7k68IwLZPgKaA==}
engines: {node: '>= 12.0.0'}
lower-case@2.0.2:
resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==}
map-obj@4.3.0:
resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==}
engines: {node: '>=8'}
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
@@ -968,6 +1039,9 @@ packages:
sass:
optional: true
no-case@3.0.4:
resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==}
obuf@1.1.2:
resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==}
@@ -1040,6 +1114,9 @@ packages:
engines: {node: '>=10'}
hasBin: true
server-only@0.0.1:
resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==}
sharp@0.33.5:
resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
@@ -1051,6 +1128,13 @@ packages:
simple-swizzle@0.2.2:
resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==}
snake-case@3.0.4:
resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==}
snakecase-keys@8.0.1:
resolution: {integrity: sha512-Sj51kE1zC7zh6TDlNNz0/Jn1n5HiHdoQErxO8jLtnyrkJW/M5PrI7x05uDgY3BO7OUQYKCvmeMurW6BPUdwEOw==}
engines: {node: '>=18'}
source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
@@ -1062,6 +1146,9 @@ packages:
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
engines: {node: '>=0.10.0'}
std-env@3.9.0:
resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==}
streamsearch@1.1.0:
resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==}
engines: {node: '>=10.0.0'}
@@ -1079,6 +1166,11 @@ packages:
babel-plugin-macros:
optional: true
swr@2.3.3:
resolution: {integrity: sha512-dshNvs3ExOqtZ6kJBaAsabhPdHyeY4P2cKwRCniDVifBMoG/SVI7tfLWqPXriVspf2Rg4tPzXJTnwaihIeFw2A==}
peerDependencies:
react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
tailwindcss@4.1.3:
resolution: {integrity: sha512-2Q+rw9vy1WFXu5cIxlvsabCwhU2qUwodGq03ODhLJ0jW4ek5BUtoCsnLB0qG+m8AHgEsSJcJGDSDe06FXlP74g==}
@@ -1089,6 +1181,10 @@ packages:
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
type-fest@4.39.1:
resolution: {integrity: sha512-uW9qzd66uyHYxwyVBYiwS4Oi0qZyUqwjU+Oevr6ZogYiXt99EOYtwvzMSLw1c3lYo2HzJsep/NB23iEVEgjG/w==}
engines: {node: '>=16'}
typescript@5.8.3:
resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==}
engines: {node: '>=14.17'}
@@ -1100,6 +1196,11 @@ packages:
undici-types@6.21.0:
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
use-sync-external-store@1.5.0:
resolution: {integrity: sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
which@4.0.0:
resolution: {integrity: sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==}
engines: {node: ^16.13.0 || >=18.0.0}
@@ -1147,6 +1248,55 @@ snapshots:
'@biomejs/cli-win32-x64@1.9.4':
optional: true
'@clerk/backend@1.27.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
dependencies:
'@clerk/shared': 3.4.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@clerk/types': 4.51.0
cookie: 1.0.2
snakecase-keys: 8.0.1
tslib: 2.8.1
transitivePeerDependencies:
- react
- react-dom
'@clerk/clerk-react@5.26.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
dependencies:
'@clerk/shared': 3.4.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@clerk/types': 4.51.0
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
tslib: 2.8.1
'@clerk/nextjs@6.14.0(next@15.2.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
dependencies:
'@clerk/backend': 1.27.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@clerk/clerk-react': 5.26.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@clerk/shared': 3.4.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@clerk/types': 4.51.0
next: 15.2.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
server-only: 0.0.1
tslib: 2.8.1
transitivePeerDependencies:
- svix
'@clerk/shared@3.4.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
dependencies:
'@clerk/types': 4.51.0
dequal: 2.0.3
glob-to-regexp: 0.4.1
js-cookie: 3.0.5
std-env: 3.9.0
swr: 2.3.3(react@19.1.0)
optionalDependencies:
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
'@clerk/types@4.51.0':
dependencies:
csstype: 3.1.3
'@drizzle-team/brocli@0.10.2': {}
'@emnapi/runtime@1.4.0':
@@ -1539,14 +1689,23 @@ snapshots:
color-string: 1.9.1
optional: true
cookie@1.0.2: {}
csstype@3.1.3: {}
debug@4.4.0:
dependencies:
ms: 2.1.3
dequal@2.0.3: {}
detect-libc@2.0.3: {}
dot-case@3.0.4:
dependencies:
no-case: 3.0.4
tslib: 2.8.1
drizzle-kit@0.30.6:
dependencies:
'@drizzle-team/brocli': 0.10.2
@@ -1644,6 +1803,8 @@ snapshots:
dependencies:
resolve-pkg-maps: 1.0.0
glob-to-regexp@0.4.1: {}
graceful-fs@4.2.11: {}
is-arrayish@0.3.2:
@@ -1653,6 +1814,8 @@ snapshots:
jiti@2.4.2: {}
js-cookie@3.0.5: {}
lightningcss-darwin-arm64@1.29.2:
optional: true
@@ -1698,6 +1861,12 @@ snapshots:
lightningcss-win32-arm64-msvc: 1.29.2
lightningcss-win32-x64-msvc: 1.29.2
lower-case@2.0.2:
dependencies:
tslib: 2.8.1
map-obj@4.3.0: {}
ms@2.1.3: {}
nanoid@3.3.11: {}
@@ -1727,6 +1896,11 @@ snapshots:
- '@babel/core'
- babel-plugin-macros
no-case@3.0.4:
dependencies:
lower-case: 2.0.2
tslib: 2.8.1
obuf@1.1.2: {}
pg-int8@1.0.1: {}
@@ -1786,6 +1960,8 @@ snapshots:
semver@7.7.1: {}
server-only@0.0.1: {}
sharp@0.33.5:
dependencies:
color: 4.2.3
@@ -1820,6 +1996,17 @@ snapshots:
is-arrayish: 0.3.2
optional: true
snake-case@3.0.4:
dependencies:
dot-case: 3.0.4
tslib: 2.8.1
snakecase-keys@8.0.1:
dependencies:
map-obj: 4.3.0
snake-case: 3.0.4
type-fest: 4.39.1
source-map-js@1.2.1: {}
source-map-support@0.5.21:
@@ -1829,6 +2016,8 @@ snapshots:
source-map@0.6.1: {}
std-env@3.9.0: {}
streamsearch@1.1.0: {}
styled-jsx@5.1.6(react@19.1.0):
@@ -1836,18 +2025,30 @@ snapshots:
client-only: 0.0.1
react: 19.1.0
swr@2.3.3(react@19.1.0):
dependencies:
dequal: 2.0.3
react: 19.1.0
use-sync-external-store: 1.5.0(react@19.1.0)
tailwindcss@4.1.3: {}
tapable@2.2.1: {}
tslib@2.8.1: {}
type-fest@4.39.1: {}
typescript@5.8.3: {}
undici-types@6.19.8: {}
undici-types@6.21.0: {}
use-sync-external-store@1.5.0(react@19.1.0):
dependencies:
react: 19.1.0
which@4.0.0:
dependencies:
isexe: 3.1.1

30
public/GLIcon.svg Normal file
View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="74.193405mm"
height="74.232162mm"
viewBox="0 0 74.193405 74.232162"
version="1.1"
id="svg1"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<g
id="layer1"
transform="translate(-24.550957,-64.437925)">
<path
d="m 61.66652,64.437927 c -20.498425,1.81e-4 -37.115669,16.617653 -37.115564,37.116083 -1.05e-4,20.49842 16.617139,37.1159 37.115564,37.11608 16.081184,-0.0265 30.316081,-10.4061 35.258313,-25.70903 1.144195,-3.51294 1.757471,-7.1771 1.819527,-10.87117 H 87.864404 67.217603 v 10.87117 h 17.977714 c -4.361366,9.03731 -13.494221,14.79672 -23.528797,14.83786 -14.494622,0 -26.244916,-11.75029 -26.244909,-26.24491 -7e-6,-14.494627 11.750287,-26.244918 26.244909,-26.244912 z"
style="font-variation-settings:'wght' 600;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.07219;stroke-linejoin:round"
id="path10" />
<rect
style="font-variation-settings:'wght' 600;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.03354;stroke-linejoin:round"
id="rect9"
width="31.802109"
height="11.397169"
x="-96.2453"
y="67.460899"
transform="rotate(-90)" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

15
src/app/admin/page.tsx Normal file
View File

@@ -0,0 +1,15 @@
import { SignedIn } from "@clerk/nextjs";
const AdminPage = async () => {
return (
<SignedIn>
<main className="flex min-h-screen flex-col items-center justify-center bg-black text-white">
<div>
hello admin
</div>
</main>
</SignedIn>
)
}
export default AdminPage;

View File

@@ -1,12 +1,15 @@
import "~/styles/globals.css";
import Link from "next/link";
import type { Metadata } from "next";
import { Geist } from "next/font/google";
import { ClerkProvider, SignedIn, SignedOut, SignUpButton, UserButton } from "@clerk/nextjs";
import { auth } from "@clerk/nextjs/server";
import { env } from "~/env";
export const metadata: Metadata = {
title: "Create T3 App",
description: "Generated by create-t3-app",
icons: [{ rel: "icon", url: "/favicon.ico" }],
title: "Gregor Lohaus",
description: "My Personal Website",
icons: [{ rel: "icon", url: "/GLIcon.svg" }],
};
const geist = Geist({
@@ -14,13 +17,32 @@ const geist = Geist({
variable: "--font-geist-sans",
});
const TopNav = () => {
const AdminWrap = async ({children,}: Readonly<{ children: React.ReactNode }>) => {
const userid = (await auth()).userId
const isAdmin = (userid == env.ADMIN_USER_CLERK_ID)
if (isAdmin) {
return <>{children}</>
}
return (<></>)
}
const TopNav = async () => {
return (
<nav className="flex w-full border-b p-4 gap-5 bg-black text-white border-white">
<div> Blog </div>
<div> CV </div>
<div> Projects </div>
<div> Fun </div>
<nav className="flex flex-wrap items-center w-full border-b px-5 py-5 gap-5 bg-black text-white border-white">
<Link className="h-fit" href={"/blog"}> Blog </Link>
<Link className="h-fit" href={"/cv"}> CV </Link>
<Link className="h-fit" href={"/projects"}> Projects </Link>
<Link className="h-fit" href={"/fun"}> Fun </Link>
<div className="ml-auto"/>
<AdminWrap><Link className="h-fit" href={"/admin"}> Admin </Link></AdminWrap>
<div className="h-fit flex">
<SignedIn>
<UserButton/>
</SignedIn>
<SignedOut>
<SignUpButton/>
</SignedOut>
</div>
</nav>
)
}
@@ -29,11 +51,13 @@ export default function RootLayout({
children,
}: Readonly<{ children: React.ReactNode }>) {
return (
<html lang="en" className={`${geist.variable}`}>
<body className="flex flex-col gap-2 bg-black text-white">
<TopNav/>
{children}
</body>
</html>
<ClerkProvider>
<html lang="en" className={`${geist.variable}`}>
<body className="flex flex-col gap-2 bg-black text-white">
<TopNav/>
{children}
</body>
</html>
</ClerkProvider>
);
}

View File

@@ -1,18 +1,10 @@
import Link from "next/link";
import { db } from "~/server/db";
export const dynamic = "force-dynamic"
export default async function HomePage() {
const posts = await db.query.posts.findMany()
return (
<main className="flex min-h-screen flex-col items-center justify-center bg-black text-white">
<div>
{posts.map((post) => {
return (
<div key={post.id}>
{post.name}
</div>
)
})}
hello world
</div>
</main>
);

View File

@@ -8,6 +8,7 @@ export const env = createEnv({
*/
server: {
DATABASE_URL: z.string().url(),
ADMIN_USER_CLERK_ID: z.string(),
NODE_ENV: z
.enum(["development", "test", "production"])
.default("development"),
@@ -28,6 +29,7 @@ export const env = createEnv({
*/
runtimeEnv: {
DATABASE_URL: process.env.DATABASE_URL,
ADMIN_USER_CLERK_ID: process.env.ADMIN_USER_CLERK_ID,
NODE_ENV: process.env.NODE_ENV,
// NEXT_PUBLIC_CLIENTVAR: process.env.NEXT_PUBLIC_CLIENTVAR,
},

21
src/middleware.ts Normal file
View File

@@ -0,0 +1,21 @@
import { clerkMiddleware, createRouteMatcher, currentUser } from "@clerk/nextjs/server";
import { env } from "~/env";
const isTenantAdminRoute = createRouteMatcher(['/admin(.*)'])
export default clerkMiddleware(async (auth,req) => {
if (isTenantAdminRoute(req)) {
let userid = (await auth()).userId
if (userid != env.ADMIN_USER_CLERK_ID) {
await auth.protect()
}
}
});
export const config = {
matcher: [
// Skip Next.js internals and all static files, unless found in search params
'/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
// Always run for API routes
'/(api|trpc)(.*)',
],
};

1
src/server/db/query.ts Normal file
View File

@@ -0,0 +1 @@
import "server-only"

View File

@@ -1,7 +1,7 @@
// Example model schema from the Drizzle docs
// https://orm.drizzle.team/docs/sql-schema-declaration
import { sql } from "drizzle-orm";
import { relations, sql } from "drizzle-orm";
import { index, pgTableCreator } from "drizzle-orm/pg-core";
/**
@@ -12,16 +12,37 @@ import { index, pgTableCreator } from "drizzle-orm/pg-core";
*/
export const createTable = pgTableCreator((name) => `gregorlohaus.com_${name}`);
export const posts = createTable(
"post",
export const cvCategory = createTable(
"cv_category",
(d) => ({
id: d.integer().primaryKey().generatedByDefaultAsIdentity(),
name: d.varchar({ length: 256 }),
id: d.uuid().primaryKey(),
name: d.varchar({length: 50})
}),
(t) => [index("name_idx").on(t.name)],
)
export const cvCategoryRelations = relations(cvCategory,({many}) => ({
cvEntry: many(cvEntry)
}))
export const cvEntry = createTable(
"cv_entry",
(d) => ({
id: d.uuid().primaryKey().notNull(),
categoryId: d.uuid('category_id'),
fromTime: d.date().notNull(),
toTime: d.date().notNull(),
createdAt: d
.timestamp({ withTimezone: true })
.default(sql`CURRENT_TIMESTAMP`)
.notNull(),
updatedAt: d.timestamp({ withTimezone: true }).$onUpdate(() => new Date()),
})
)
export const cvEntryRelations = relations(cvEntry, ({one}) => ({
category: one(cvCategory, {
fields: [cvEntry.categoryId],
references: [cvCategory.id]
}),
(t) => [index("name_idx").on(t.name)],
);
}));