chore: wipe
This commit is contained in:
parent
a7b06f1d6e
commit
4bc8d2d79f
58 changed files with 0 additions and 8571 deletions
|
|
@ -1,11 +0,0 @@
|
||||||
.env
|
|
||||||
Dockerfile
|
|
||||||
.dockerignore
|
|
||||||
node_modules
|
|
||||||
npm-debug.log
|
|
||||||
README.md
|
|
||||||
.next
|
|
||||||
.git
|
|
||||||
README.create-t3-gg.md
|
|
||||||
charts
|
|
||||||
seed.cjs
|
|
||||||
26
.env.example
26
.env.example
|
|
@ -1,26 +0,0 @@
|
||||||
# Since the ".env" file is gitignored, you can use the ".env.example" file to
|
|
||||||
# build a new ".env" file when you clone the repo. Keep this file up-to-date
|
|
||||||
# when you add new variables to `.env`.
|
|
||||||
|
|
||||||
# This file will be committed to version control, so make sure not to have any
|
|
||||||
# secrets in it. If you are cloning this repo, create a copy of this file named
|
|
||||||
# ".env" and populate it with your secrets.
|
|
||||||
|
|
||||||
# When adding additional environment variables, the schema in "/src/env.mjs"
|
|
||||||
# should be updated accordingly.
|
|
||||||
|
|
||||||
# Prisma
|
|
||||||
# https://www.prisma.io/docs/reference/database-reference/connection-urls#env
|
|
||||||
DATABASE_URL="postgresql://username:password@localhost:5432/database"
|
|
||||||
|
|
||||||
# Next Auth
|
|
||||||
# You can generate a new secret on the command line with:
|
|
||||||
# openssl rand -base64 32
|
|
||||||
# https://next-auth.js.org/configuration/options#secret
|
|
||||||
# NEXTAUTH_SECRET=""
|
|
||||||
NEXTAUTH_URL="http://localhost:3000"
|
|
||||||
|
|
||||||
# Next Auth Keycloak Provider
|
|
||||||
KEYCLOAK_CLIENT_ID=""
|
|
||||||
KEYCLOAK_CLIENT_SECRET=""
|
|
||||||
KEYCLOAK_ISSUER=""
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
||||||
const path = require("path");
|
|
||||||
|
|
||||||
/** @type {import("eslint").Linter.Config} */
|
|
||||||
const config = {
|
|
||||||
overrides: [
|
|
||||||
{
|
|
||||||
extends: [
|
|
||||||
"plugin:@typescript-eslint/recommended-requiring-type-checking",
|
|
||||||
],
|
|
||||||
files: ["*.ts", "*.tsx"],
|
|
||||||
parserOptions: {
|
|
||||||
project: path.join(__dirname, "tsconfig.json"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
parser: "@typescript-eslint/parser",
|
|
||||||
parserOptions: {
|
|
||||||
project: path.join(__dirname, "tsconfig.json"),
|
|
||||||
},
|
|
||||||
plugins: ["@typescript-eslint"],
|
|
||||||
extends: ["next/core-web-vitals", "plugin:@typescript-eslint/recommended"],
|
|
||||||
rules: {
|
|
||||||
"@typescript-eslint/consistent-type-imports": [
|
|
||||||
"warn",
|
|
||||||
{
|
|
||||||
prefer: "type-imports",
|
|
||||||
fixStyle: "inline-type-imports",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_" }],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = config;
|
|
||||||
44
.gitignore
vendored
44
.gitignore
vendored
|
|
@ -1,44 +0,0 @@
|
||||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
|
||||||
|
|
||||||
# dependencies
|
|
||||||
/node_modules
|
|
||||||
/.pnp
|
|
||||||
.pnp.js
|
|
||||||
|
|
||||||
# testing
|
|
||||||
/coverage
|
|
||||||
|
|
||||||
# database
|
|
||||||
/prisma/db.sqlite
|
|
||||||
/prisma/db.sqlite-journal
|
|
||||||
|
|
||||||
# next.js
|
|
||||||
/.next/
|
|
||||||
/out/
|
|
||||||
next-env.d.ts
|
|
||||||
|
|
||||||
# production
|
|
||||||
/build
|
|
||||||
|
|
||||||
# misc
|
|
||||||
.DS_Store
|
|
||||||
*.pem
|
|
||||||
|
|
||||||
# debug
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
.pnpm-debug.log*
|
|
||||||
|
|
||||||
# local env files
|
|
||||||
# do not commit any .env files to git, except for the .env.example file. https://create.t3.gg/en/usage/env-variables#using-environment-variables
|
|
||||||
.env
|
|
||||||
.env*.local
|
|
||||||
|
|
||||||
# vercel
|
|
||||||
.vercel
|
|
||||||
|
|
||||||
# typescript
|
|
||||||
*.tsbuildinfo
|
|
||||||
|
|
||||||
seed.cjs
|
|
||||||
137
.gitlab-ci.yml
137
.gitlab-ci.yml
|
|
@ -1,137 +0,0 @@
|
||||||
default:
|
|
||||||
image: ghcr.io/vojtechmares/toolkit:latest
|
|
||||||
|
|
||||||
stages:
|
|
||||||
- lint
|
|
||||||
- build
|
|
||||||
- deploy:dry-run
|
|
||||||
- deploy
|
|
||||||
|
|
||||||
lint next.js:
|
|
||||||
stage: lint
|
|
||||||
image: node:18-alpine3.17
|
|
||||||
script:
|
|
||||||
- npm ci --frozen-lockfile
|
|
||||||
- SKIP_ENV_VALIDATION=1 npm run lint
|
|
||||||
|
|
||||||
lint helm:
|
|
||||||
stage: lint
|
|
||||||
script:
|
|
||||||
- helm lint ./charts/backoffice -f ./charts/backoffice/values.dummy.yaml --quiet
|
|
||||||
|
|
||||||
docker build:
|
|
||||||
stage: build
|
|
||||||
before_script:
|
|
||||||
- echo $CI_REGISTRY_PASSWORD | docker login -u $CI_REGISTRY_USER --password-stdin $CI_REGISTRY
|
|
||||||
- docker info
|
|
||||||
script:
|
|
||||||
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_BRANCH-$CI_COMMIT_SHORT_SHA .
|
|
||||||
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_BRANCH-$CI_COMMIT_SHORT_SHA
|
|
||||||
|
|
||||||
deploy to staging (dry-run):
|
|
||||||
stage: deploy:dry-run
|
|
||||||
script:
|
|
||||||
- >
|
|
||||||
helm \
|
|
||||||
upgrade \
|
|
||||||
--install \
|
|
||||||
--atomic \
|
|
||||||
--wait=true \
|
|
||||||
--wait-for-jobs=true \
|
|
||||||
--timeout=900s \
|
|
||||||
--dry-run=true \
|
|
||||||
--namespace backoffice-staging \
|
|
||||||
--values ./charts/backoffice/values.staging.yaml \
|
|
||||||
--set image.tag=$CI_COMMIT_BRANCH-$CI_COMMIT_SHORT_SHA \
|
|
||||||
--set dockerconfigjsonBase64=dummy \
|
|
||||||
--set backoffice.secrets.databaseURL=dummy \
|
|
||||||
--set backoffice.secrets.nextauthSecret=dummy \
|
|
||||||
--set backoffice.secrets.keycloakClientID=dummy \
|
|
||||||
--set backoffice.secrets.keycloakClientSecret=dummy \
|
|
||||||
--set backoffice.secrets.keycloakIssuer=dummy \
|
|
||||||
backoffice \
|
|
||||||
./charts/backoffice
|
|
||||||
rules:
|
|
||||||
- if: $CI_COMMIT_BRANCH == "main"
|
|
||||||
|
|
||||||
deploy to production (dry-run):
|
|
||||||
stage: deploy:dry-run
|
|
||||||
script:
|
|
||||||
- >
|
|
||||||
helm \
|
|
||||||
upgrade \
|
|
||||||
--install \
|
|
||||||
--atomic \
|
|
||||||
--wait=true \
|
|
||||||
--wait-for-jobs=true \
|
|
||||||
--timeout=900s \
|
|
||||||
--dry-run=true \
|
|
||||||
--namespace backoffice-production \
|
|
||||||
--values ./charts/backoffice/values.production.yaml \
|
|
||||||
--set image.tag=$CI_COMMIT_BRANCH-$CI_COMMIT_SHORT_SHA \
|
|
||||||
--set dockerconfigjsonBase64=dummy \
|
|
||||||
--set backoffice.secrets.databaseURL=dummy \
|
|
||||||
--set backoffice.secrets.nextauthSecret=dummy \
|
|
||||||
--set backoffice.secrets.keycloakClientID=dummy \
|
|
||||||
--set backoffice.secrets.keycloakClientSecret=dummy \
|
|
||||||
--set backoffice.secrets.keycloakIssuer=dummy \
|
|
||||||
backoffice \
|
|
||||||
./charts/backoffice
|
|
||||||
rules:
|
|
||||||
- if: $CI_COMMIT_BRANCH == "production"
|
|
||||||
|
|
||||||
deploy to staging:
|
|
||||||
stage: deploy
|
|
||||||
script:
|
|
||||||
- >
|
|
||||||
helm \
|
|
||||||
upgrade \
|
|
||||||
--install \
|
|
||||||
--atomic \
|
|
||||||
--wait=true \
|
|
||||||
--wait-for-jobs=true \
|
|
||||||
--timeout=900s \
|
|
||||||
--namespace backoffice-staging \
|
|
||||||
--values ./charts/backoffice/values.staging.yaml \
|
|
||||||
--set image.tag=$CI_COMMIT_BRANCH-$CI_COMMIT_SHORT_SHA \
|
|
||||||
--set dockerconfigjsonBase64=$DOCKERCONFIG_BASE64 \
|
|
||||||
--set backoffice.secrets.databaseURL=$DATABASE_URL \
|
|
||||||
--set backoffice.secrets.nextauthSecret=$NEXTAUTH_SECRET \
|
|
||||||
--set backoffice.secrets.keycloakClientID=$KEYCLOAK_CLIENT_ID \
|
|
||||||
--set backoffice.secrets.keycloakClientSecret=$KEYCLOAK_CLIENT_SECRET \
|
|
||||||
--set backoffice.secrets.keycloakIssuer=$KEYCLOAK_ISSUER \
|
|
||||||
backoffice \
|
|
||||||
./charts/backoffice
|
|
||||||
environment:
|
|
||||||
name: staging
|
|
||||||
url: https://staging.backoffice.mareshq.com
|
|
||||||
rules:
|
|
||||||
- if: $CI_COMMIT_BRANCH == "main"
|
|
||||||
|
|
||||||
deploy to production:
|
|
||||||
stage: deploy
|
|
||||||
script:
|
|
||||||
- >
|
|
||||||
helm \
|
|
||||||
upgrade \
|
|
||||||
--install \
|
|
||||||
--atomic \
|
|
||||||
--wait=true \
|
|
||||||
--wait-for-jobs=true \
|
|
||||||
--timeout=900s \
|
|
||||||
--namespace backoffice-production \
|
|
||||||
--values ./charts/backoffice/values.production.yaml \
|
|
||||||
--set image.tag=$CI_COMMIT_BRANCH-$CI_COMMIT_SHORT_SHA \
|
|
||||||
--set dockerconfigjsonBase64=$DOCKERCONFIG_BASE64 \
|
|
||||||
--set backoffice.secrets.databaseURL=$DATABASE_URL \
|
|
||||||
--set backoffice.secrets.nextauthSecret=$NEXTAUTH_SECRET \
|
|
||||||
--set backoffice.secrets.keycloakClientID=$KEYCLOAK_CLIENT_ID \
|
|
||||||
--set backoffice.secrets.keycloakClientSecret=$KEYCLOAK_CLIENT_SECRET \
|
|
||||||
--set backoffice.secrets.keycloakIssuer=$KEYCLOAK_ISSUER \
|
|
||||||
backoffice \
|
|
||||||
./charts/backoffice
|
|
||||||
environment:
|
|
||||||
name: production
|
|
||||||
url: https://backoffice.mareshq.com
|
|
||||||
rules:
|
|
||||||
- if: $CI_COMMIT_BRANCH == "production"
|
|
||||||
65
Dockerfile
65
Dockerfile
|
|
@ -1,65 +0,0 @@
|
||||||
FROM --platform=linux/amd64 node:18-alpine3.17 as base
|
|
||||||
|
|
||||||
### DEPENDENCIES
|
|
||||||
|
|
||||||
FROM base AS deps
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
RUN apk add --no-cache libc6-compat openssl1.1-compat
|
|
||||||
|
|
||||||
COPY prisma ./prisma
|
|
||||||
|
|
||||||
COPY package.json package-lock.json ./
|
|
||||||
|
|
||||||
RUN npm ci --frozen-lockfile
|
|
||||||
|
|
||||||
### BUILDER
|
|
||||||
|
|
||||||
FROM base AS builder
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
COPY --from=deps /app/prisma ./prisma
|
|
||||||
COPY --from=deps /app/node_modules ./node_modules
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
ENV NEXT_TELEMETRY_DISABLED 1
|
|
||||||
|
|
||||||
RUN SKIP_ENV_VALIDATION=1 npm run build
|
|
||||||
|
|
||||||
### RUNNER
|
|
||||||
|
|
||||||
FROM base AS runner
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
ENV NODE_ENV=production \
|
|
||||||
NEXT_TELEMETRY_DISABLED=1
|
|
||||||
|
|
||||||
RUN addgroup --system --gid 1001 nodejs
|
|
||||||
RUN adduser --system --uid 1001 nextjs
|
|
||||||
|
|
||||||
# Required for `npx prisma migrate deploy`
|
|
||||||
COPY --from=builder /app/prisma ./prisma
|
|
||||||
|
|
||||||
# Required for `npx prisma db seed`
|
|
||||||
COPY --from=builder /app/content ./content
|
|
||||||
COPY --from=builder /app/src/content/training.ts ./src/content/training.ts
|
|
||||||
COPY --from=builder /app/src/server/db.ts ./src/server/db.ts
|
|
||||||
COPY --from=builder /app/src/env.mjs ./src/env.mjs
|
|
||||||
|
|
||||||
# Required for Next.js
|
|
||||||
COPY --from=builder /app/next.config.mjs ./
|
|
||||||
COPY --from=builder /app/tsconfig.json ./
|
|
||||||
COPY --from=builder /app/public ./public
|
|
||||||
COPY --from=builder /app/package.json ./package.json
|
|
||||||
|
|
||||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
|
||||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
|
||||||
|
|
||||||
USER nextjs
|
|
||||||
EXPOSE 3000
|
|
||||||
ENV PORT 3000
|
|
||||||
|
|
||||||
CMD ["node", "server.js"]
|
|
||||||
19
Makefile
19
Makefile
|
|
@ -1,19 +0,0 @@
|
||||||
.PHONY: dev
|
|
||||||
dev:
|
|
||||||
npm run dev
|
|
||||||
|
|
||||||
.PHONY: db-push
|
|
||||||
db-push:
|
|
||||||
npx prisma db push
|
|
||||||
|
|
||||||
.PHONY: db-seed
|
|
||||||
db-seed:
|
|
||||||
npm run seed
|
|
||||||
|
|
||||||
.PHONY: db-migrate-dev
|
|
||||||
db-migrate-dev:
|
|
||||||
npx prisma migrate dev
|
|
||||||
|
|
||||||
.PHONY: lint
|
|
||||||
lint:
|
|
||||||
npm run lint
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
# Create T3 App
|
|
||||||
|
|
||||||
This is a [T3 Stack](https://create.t3.gg/) project bootstrapped with `create-t3-app`.
|
|
||||||
|
|
||||||
## What's next? How do I make an app with this?
|
|
||||||
|
|
||||||
We try to keep this project as simple as possible, so you can start with just the scaffolding we set up for you, and add additional things later when they become necessary.
|
|
||||||
|
|
||||||
If you are not familiar with the different technologies used in this project, please refer to the respective docs. If you still are in the wind, please join our [Discord](https://t3.gg/discord) and ask for help.
|
|
||||||
|
|
||||||
- [Next.js](https://nextjs.org)
|
|
||||||
- [NextAuth.js](https://next-auth.js.org)
|
|
||||||
- [Prisma](https://prisma.io)
|
|
||||||
- [Tailwind CSS](https://tailwindcss.com)
|
|
||||||
- [tRPC](https://trpc.io)
|
|
||||||
|
|
||||||
## Learn More
|
|
||||||
|
|
||||||
To learn more about the [T3 Stack](https://create.t3.gg/), take a look at the following resources:
|
|
||||||
|
|
||||||
- [Documentation](https://create.t3.gg/)
|
|
||||||
- [Learn the T3 Stack](https://create.t3.gg/en/faq#what-learning-resources-are-currently-available) — Check out these awesome tutorials
|
|
||||||
|
|
||||||
You can check out the [create-t3-app GitHub repository](https://github.com/t3-oss/create-t3-app) — your feedback and contributions are welcome!
|
|
||||||
|
|
||||||
## How do I deploy this?
|
|
||||||
|
|
||||||
Follow our deployment guides for [Vercel](https://create.t3.gg/en/deployment/vercel), [Netlify](https://create.t3.gg/en/deployment/netlify) and [Docker](https://create.t3.gg/en/deployment/docker) for more information.
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
# backoffice
|
|
||||||
|
|
||||||
A small application serving as a freelancer's backoffice.
|
|
||||||
|
|
||||||
A single source of truth. Currently an experiment.
|
|
||||||
|
|
||||||
The idea to have all training data source here and not as a part of a monorepo, is mostly laziness since I do not think monorepo tooling such as [Turborepo](https://turbo.build/repo) is quite ready and also it brings certain challenges with building etc. My target platform for this project is a small Kubernetes cluster.
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
apiVersion: v2
|
|
||||||
name: backoffice
|
|
||||||
description: A Kubernetes Helm chart for backoffice
|
|
||||||
type: application
|
|
||||||
version: 0.1.0
|
|
||||||
appVersion: 0.1.0
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
{{- if .Values.ingress.enabled }}
|
|
||||||
URL: https://{{ .Values.ingress.host }}
|
|
||||||
{{- end }}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
kind: ConfigMap
|
|
||||||
apiVersion: v1
|
|
||||||
metadata:
|
|
||||||
name: {{ .Release.Name }}-config
|
|
||||||
annotations:
|
|
||||||
"helm.sh/hook": pre-install,pre-upgrade
|
|
||||||
"helm.sh/hook-weight": "-15"
|
|
||||||
data:
|
|
||||||
NODE_ENV: {{ .Values.backoffice.env | quote }}
|
|
||||||
NEXTAUTH_URL: "https://{{ .Values.ingress.host }}/"
|
|
||||||
PORT: {{ .Values.service.portNumber | quote }}
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: {{ .Release.Name }}
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/name: {{ .Chart.Name }}
|
|
||||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
|
||||||
app.kubernetes.io/component: backend
|
|
||||||
spec:
|
|
||||||
replicas: {{ .Values.replicaCount }}
|
|
||||||
strategy:
|
|
||||||
type: RollingUpdate
|
|
||||||
rollingUpdate:
|
|
||||||
maxSurge: 1
|
|
||||||
maxUnavailable: 1
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app.kubernetes.io/name: {{ .Chart.Name }}
|
|
||||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
|
||||||
app.kubernetes.io/component: backend
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/name: {{ .Chart.Name }}
|
|
||||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
|
||||||
app.kubernetes.io/component: backend
|
|
||||||
spec:
|
|
||||||
{{- if .Values.dockerconfigjsonBase64 }}
|
|
||||||
imagePullSecrets:
|
|
||||||
- name: {{ .Release.Name }}-container-registry
|
|
||||||
{{- end }}
|
|
||||||
containers:
|
|
||||||
- name: {{ .Chart.Name }}
|
|
||||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
|
||||||
imagePullPolicy: Always
|
|
||||||
ports:
|
|
||||||
- name: {{ .Values.service.port.name }}
|
|
||||||
containerPort: {{ .Values.service.port.number }}
|
|
||||||
protocol: TCP
|
|
||||||
livenessProbe:
|
|
||||||
httpGet:
|
|
||||||
path: /api/livez
|
|
||||||
port: {{ .Values.service.port.name }}
|
|
||||||
initialDelaySeconds: 3
|
|
||||||
periodSeconds: 3
|
|
||||||
timeoutSeconds: 2
|
|
||||||
failureThreshold: 3
|
|
||||||
sucessThreshold: 1
|
|
||||||
envFrom:
|
|
||||||
- configMapRef:
|
|
||||||
name: {{ .Release.Name }}-config
|
|
||||||
- secretRef:
|
|
||||||
name: {{ .Release.Name }}-database
|
|
||||||
- secretRef:
|
|
||||||
name: {{ .Release.Name }}-nextauth
|
|
||||||
resources:
|
|
||||||
{{- toYaml .Values.backend.resources | nindent 12 }}
|
|
||||||
terminationGracePeriodSeconds: 10
|
|
||||||
affinity:
|
|
||||||
podAntiAffinity:
|
|
||||||
preferredDuringSchedulingIgnoredDuringExecution:
|
|
||||||
- weight: 100
|
|
||||||
podAffinityTerm:
|
|
||||||
labelSelector:
|
|
||||||
matchExpressions:
|
|
||||||
- key: app.kubernetes.io/instance
|
|
||||||
operator: In
|
|
||||||
values:
|
|
||||||
- {{ .Release.Name }}
|
|
||||||
topologyKey: kubernetes.io/hostname
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
{{- if .Values.ingress.enabled -}}
|
|
||||||
apiVersion: networking.k8s.io/v1
|
|
||||||
kind: Ingress
|
|
||||||
metadata:
|
|
||||||
name: {{ .Release.Name }}
|
|
||||||
{{- with .Values.ingress.annotations }}
|
|
||||||
annotations:
|
|
||||||
{{- toYaml . | nindent 4 }}
|
|
||||||
{{- end }}
|
|
||||||
spec:
|
|
||||||
ingressClassName: {{ .Values.ingress.ingressClassName }}
|
|
||||||
tls:
|
|
||||||
- hosts:
|
|
||||||
- {{ .Values.ingress.host | quote }}
|
|
||||||
secretName: {{ .Release.Name }}-tls
|
|
||||||
|
|
||||||
rules:
|
|
||||||
- host: {{ .Values.ingress.host | quote }}
|
|
||||||
http:
|
|
||||||
paths:
|
|
||||||
- path: {{ .Values.ingress.path }}
|
|
||||||
pathType: {{ .Values.ingress.pathType }}
|
|
||||||
backend:
|
|
||||||
service:
|
|
||||||
name: {{ .Release.Name }}
|
|
||||||
port:
|
|
||||||
name: {{ .Values.service.port.name }}
|
|
||||||
{{- end -}}
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
apiVersion: batch/v1
|
|
||||||
kind: Job
|
|
||||||
metadata:
|
|
||||||
name: {{ .Release.Name }}-db-migration
|
|
||||||
annotations:
|
|
||||||
"helm.sh/hook": pre-install,pre-upgrade
|
|
||||||
"helm.sh/hook-weight": "-10"
|
|
||||||
"helm.sh/hook-delete-policy": before-hook-creation #,hook-succeeded
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/name: {{ .Chart.Name }}
|
|
||||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
|
||||||
app.kubernetes.io/component: database-migration
|
|
||||||
spec:
|
|
||||||
backoffLimit: 0
|
|
||||||
ttlSecondsAfterFinished: 86400 # 1 day
|
|
||||||
activeDeadlineSeconds: 300 # 5 minutes
|
|
||||||
template:
|
|
||||||
spec:
|
|
||||||
{{- if .Values.dockerconfigjsonBase64 }}
|
|
||||||
imagePullSecrets:
|
|
||||||
- name: {{ .Release.Name }}-container-registry
|
|
||||||
{{- end }}
|
|
||||||
containers:
|
|
||||||
- name: {{ .Chart.Name }}-migration
|
|
||||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
|
||||||
command: ["npx", "prisma", "migrate", "deploy"]
|
|
||||||
envFrom:
|
|
||||||
- configMapRef:
|
|
||||||
name: {{ .Release.Name }}-config
|
|
||||||
- secretRef:
|
|
||||||
name: {{ .Release.Name }}-database
|
|
||||||
- secretRef:
|
|
||||||
name: {{ .Release.Name }}-nextauth
|
|
||||||
resources:
|
|
||||||
{{- toYaml .Values.migration.resources | nindent 10 }}
|
|
||||||
restartPolicy: Never
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
apiVersion: batch/v1
|
|
||||||
kind: Job
|
|
||||||
metadata:
|
|
||||||
name: {{ .Release.Name }}-db-seed
|
|
||||||
annotations:
|
|
||||||
"helm.sh/hook": pre-install,pre-upgrade
|
|
||||||
"helm.sh/hook-weight": "-5"
|
|
||||||
"helm.sh/hook-delete-policy": before-hook-creation #,hook-succeeded
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/name: {{ .Chart.Name }}
|
|
||||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
|
||||||
app.kubernetes.io/component: database-seed
|
|
||||||
spec:
|
|
||||||
backoffLimit: 0
|
|
||||||
ttlSecondsAfterFinished: 86400 # 1 day
|
|
||||||
activeDeadlineSeconds: 300 # 5 minutes
|
|
||||||
template:
|
|
||||||
spec:
|
|
||||||
{{- if .Values.dockerconfigjsonBase64 }}
|
|
||||||
imagePullSecrets:
|
|
||||||
- name: {{ .Release.Name }}-container-registry
|
|
||||||
{{- end }}
|
|
||||||
containers:
|
|
||||||
- name: {{ .Chart.Name }}-seed
|
|
||||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
|
||||||
command: ["/bin/sh", "-c"]
|
|
||||||
args:
|
|
||||||
- |
|
|
||||||
npm install esbuild --no-save
|
|
||||||
npx esbuild prisma/seed.ts --outfile=/app/cache/seed.cjs --bundle --format=cjs --external:prisma --external:@prisma/client --platform=node
|
|
||||||
node /app/cache/seed.cjs
|
|
||||||
envFrom:
|
|
||||||
- configMapRef:
|
|
||||||
name: {{ .Release.Name }}-config
|
|
||||||
- secretRef:
|
|
||||||
name: {{ .Release.Name }}-database
|
|
||||||
- secretRef:
|
|
||||||
name: {{ .Release.Name }}-nextauth
|
|
||||||
volumeMounts:
|
|
||||||
- mountPath: /app/cache
|
|
||||||
name: cache-volume
|
|
||||||
resources:
|
|
||||||
{{- toYaml .Values.seed.resources | nindent 10 }}
|
|
||||||
restartPolicy: Never
|
|
||||||
volumes:
|
|
||||||
- name: cache-volume
|
|
||||||
emptyDir:
|
|
||||||
sizeLimit: 2Mi
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
{{- if gt (.Values.replicaCount | int) 1 }}
|
|
||||||
apiVersion: policy/v1
|
|
||||||
kind: PodDisruptionBudget
|
|
||||||
metadata:
|
|
||||||
name: {{ .Release.Name }}
|
|
||||||
spec:
|
|
||||||
minAvailable: {{ .Values.pdb.minAvailable }}
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app.kubernetes.io/name: {{ .Chart.Name }}
|
|
||||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
|
||||||
app.kubernetes.io/component: backend
|
|
||||||
{{- end }}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
{{ if .Values.dockerconfigjsonBase64 }}
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Secret
|
|
||||||
metadata:
|
|
||||||
name: {{ .Release.Name }}-container-registry
|
|
||||||
annotations:
|
|
||||||
"helm.sh/hook": pre-install,pre-upgrade
|
|
||||||
"helm.sh/hook-weight": "-15"
|
|
||||||
type: kubernetes.io/dockerconfigjson
|
|
||||||
data:
|
|
||||||
.dockerconfigjson: {{ .Values.dockerconfigjsonBase64 }}
|
|
||||||
{{ end }}
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
apiVersion: v1
|
|
||||||
kind: Secret
|
|
||||||
type: Opaque
|
|
||||||
metadata:
|
|
||||||
name: {{ .Release.Name }}-database
|
|
||||||
annotations:
|
|
||||||
"helm.sh/hook": pre-install,pre-upgrade
|
|
||||||
"helm.sh/hook-weight": "-15"
|
|
||||||
stringData:
|
|
||||||
DATABASE_URL: {{ .Values.backoffice.secrets.databaseURL | quote }}
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
apiVersion: v1
|
|
||||||
kind: Secret
|
|
||||||
type: Opaque
|
|
||||||
metadata:
|
|
||||||
name: {{ .Release.Name }}-nextauth
|
|
||||||
annotations:
|
|
||||||
"helm.sh/hook": pre-install,pre-upgrade
|
|
||||||
"helm.sh/hook-weight": "-15"
|
|
||||||
stringData:
|
|
||||||
NEXTAUTH_SECRET: {{ .Values.backoffice.secrets.nextauthSecret | quote }}
|
|
||||||
KEYCLOAK_CLIENT_ID: {{ .Values.backoffice.secrets.keycloakClientID | quote }}
|
|
||||||
KEYCLOAK_CLIENT_SECRET: {{ .Values.backoffice.secrets.keycloakClientSecret | quote }}
|
|
||||||
KEYCLOAK_ISSUER: {{ .Values.backoffice.secrets.keycloakIssuer | quote }}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: {{ .Release.Name }}
|
|
||||||
|
|
||||||
spec:
|
|
||||||
type: {{ .Values.service.type }}
|
|
||||||
ports:
|
|
||||||
- port: {{ .Values.service.port.number }}
|
|
||||||
targetPort: {{ .Values.service.port.number }}
|
|
||||||
protocol: TCP
|
|
||||||
name: {{ .Values.service.port.name }}
|
|
||||||
|
|
||||||
selector:
|
|
||||||
app.kubernetes.io/name: {{ .Chart.Name }}
|
|
||||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
backoffice:
|
|
||||||
secretes:
|
|
||||||
databaseURL: "postgres://postgres:postgres@localhost:5432/backoffice"
|
|
||||||
nextauthSecret: "secret"
|
|
||||||
keycloakClientID: "secret"
|
|
||||||
keycloakClientSecret: "secret"
|
|
||||||
keycloakIssuer: "secret"
|
|
||||||
|
|
||||||
image:
|
|
||||||
tag: dummy
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
replicaCount: 2
|
|
||||||
|
|
||||||
ingress:
|
|
||||||
enabled: true
|
|
||||||
host: backoffice.mareshq.com
|
|
||||||
annotations:
|
|
||||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
|
||||||
|
|
||||||
backoffice:
|
|
||||||
env: production
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
replicaCount: 2
|
|
||||||
|
|
||||||
ingress:
|
|
||||||
enabled: true
|
|
||||||
host: staging.backoffice.mareshq.com
|
|
||||||
annotations:
|
|
||||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
|
||||||
|
|
||||||
backoffice:
|
|
||||||
env: production
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
replicaCount: 1
|
|
||||||
|
|
||||||
image:
|
|
||||||
repository: registry.mareshq.com/mareshq/backoffice
|
|
||||||
tag:
|
|
||||||
|
|
||||||
ingress:
|
|
||||||
enabled: false
|
|
||||||
host: example.com
|
|
||||||
ingressClassName: nginx
|
|
||||||
path: /
|
|
||||||
pathType: Prefix
|
|
||||||
annotations:
|
|
||||||
{}
|
|
||||||
# cert-manager.io/cluster-issuer: letsencrypt-prod
|
|
||||||
# cert-manager.io/issuer: letsencrypt-prod
|
|
||||||
|
|
||||||
service:
|
|
||||||
port:
|
|
||||||
name: http
|
|
||||||
number: 3000
|
|
||||||
|
|
||||||
pdb:
|
|
||||||
minAvailable: 1
|
|
||||||
|
|
||||||
backend:
|
|
||||||
resources:
|
|
||||||
limits:
|
|
||||||
cpu: 200m
|
|
||||||
memory: 512Mi
|
|
||||||
requests:
|
|
||||||
cpu: 200m
|
|
||||||
memory: 512Mi
|
|
||||||
|
|
||||||
migration:
|
|
||||||
resources:
|
|
||||||
limits:
|
|
||||||
cpu: 200m
|
|
||||||
memory: 512Mi
|
|
||||||
requests:
|
|
||||||
cpu: 200m
|
|
||||||
memory: 512Mi
|
|
||||||
|
|
||||||
seed:
|
|
||||||
resources:
|
|
||||||
limits:
|
|
||||||
cpu: 200m
|
|
||||||
memory: 512Mi
|
|
||||||
requests:
|
|
||||||
cpu: 200m
|
|
||||||
memory: 512Mi
|
|
||||||
|
|
||||||
backoffice:
|
|
||||||
env: null # allowed values: development | test | production
|
|
||||||
|
|
||||||
secrets:
|
|
||||||
databaseURL: null
|
|
||||||
nextauthSecret: null
|
|
||||||
keycloakClientID: null
|
|
||||||
keycloakClientSecret: null
|
|
||||||
keycloakIssuer: null
|
|
||||||
|
|
||||||
dockerconfigjsonBase64: null
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
---
|
|
||||||
id: s448cmvx05lra5mjyu35fdtd # generated via /scripts/cuid.mjs
|
|
||||||
name: Terraform
|
|
||||||
slug: terraform
|
|
||||||
days: 1
|
|
||||||
weight: 1
|
|
||||||
draft: false
|
|
||||||
logoURL: https://example.com/logo.png
|
|
||||||
svgIconURL: https://example.com/icon.svg
|
|
||||||
repositoryURL: https://github.com/vojtechmares/terraform-training
|
|
||||||
priceOpen: 5900
|
|
||||||
priceCorporate: 24000
|
|
||||||
---
|
|
||||||
|
|
||||||
# TODO
|
|
||||||
|
|
||||||
TEST ME
|
|
||||||
|
|
||||||
# DOES IT WORK?
|
|
||||||
|
|
||||||
LOOKS LIKE IT DOES!
|
|
||||||
|
|
||||||
# LETS CHECK OUT LISTS
|
|
||||||
|
|
||||||
- A
|
|
||||||
- B
|
|
||||||
|
|
||||||
# AND WHAT ABOUT OTHER STUFF?
|
|
||||||
|
|
||||||
- **STRONG**
|
|
||||||
- *ITALIC*
|
|
||||||
- [Link](https://google.com/)
|
|
||||||
|
|
||||||
## NESTED
|
|
||||||
|
|
||||||
### HEADINGS
|
|
||||||
|
|
||||||
OK WORK
|
|
||||||
|
|
||||||
> blockquote
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
code:
|
|
||||||
block: true
|
|
||||||
```
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
/**
|
|
||||||
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially useful
|
|
||||||
* for Docker builds.
|
|
||||||
*/
|
|
||||||
await import("./src/env.mjs");
|
|
||||||
|
|
||||||
/** @type {import("next").NextConfig} */
|
|
||||||
const config = {
|
|
||||||
reactStrictMode: true,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If you have `experimental: { appDir: true }` set, then you must comment the below `i18n` config
|
|
||||||
* out.
|
|
||||||
*
|
|
||||||
* @see https://github.com/vercel/next.js/issues/41980
|
|
||||||
*/
|
|
||||||
i18n: {
|
|
||||||
locales: ["en"],
|
|
||||||
defaultLocale: "en",
|
|
||||||
},
|
|
||||||
swcMinify: true,
|
|
||||||
output: "standalone",
|
|
||||||
};
|
|
||||||
|
|
||||||
export default config;
|
|
||||||
6536
package-lock.json
generated
6536
package-lock.json
generated
File diff suppressed because it is too large
Load diff
55
package.json
55
package.json
|
|
@ -1,55 +0,0 @@
|
||||||
{
|
|
||||||
"name": "backoffice",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"private": true,
|
|
||||||
"scripts": {
|
|
||||||
"build": "next build",
|
|
||||||
"dev": "next dev",
|
|
||||||
"postinstall": "prisma generate",
|
|
||||||
"lint": "next lint",
|
|
||||||
"start": "next start",
|
|
||||||
"seed": "esbuild prisma/seed.ts --outfile=seed.cjs --bundle --format=cjs --external:prisma --external:@prisma/client --platform=node && node seed.cjs"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@headlessui/react": "^1.7.15",
|
|
||||||
"@heroicons/react": "^2.0.18",
|
|
||||||
"@next-auth/prisma-adapter": "^1.0.5",
|
|
||||||
"@paralleldrive/cuid2": "^2.2.1",
|
|
||||||
"@prisma/client": "^5.2.0",
|
|
||||||
"@t3-oss/env-nextjs": "^0.6.1",
|
|
||||||
"clsx": "^2.0.0",
|
|
||||||
"gray-matter": "^4.0.3",
|
|
||||||
"next": "^13.4.2",
|
|
||||||
"next-auth": "^4.23.1",
|
|
||||||
"react": "18.2.0",
|
|
||||||
"react-dom": "18.2.0",
|
|
||||||
"react-markdown": "^8.0.7",
|
|
||||||
"zod": "^3.22.2"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@tailwindcss/typography": "^0.5.9",
|
|
||||||
"@types/eslint": "^8.37.0",
|
|
||||||
"@types/node": "^18.17.15",
|
|
||||||
"@types/prettier": "^3.0.0",
|
|
||||||
"@types/react": "^18.2.6",
|
|
||||||
"@types/react-dom": "^18.2.4",
|
|
||||||
"@typescript-eslint/eslint-plugin": "^6.6.0",
|
|
||||||
"@typescript-eslint/parser": "^6.6.0",
|
|
||||||
"autoprefixer": "^10.4.14",
|
|
||||||
"esbuild": "^0.19.2",
|
|
||||||
"eslint": "^8.40.0",
|
|
||||||
"eslint-config-next": "^13.4.2",
|
|
||||||
"postcss": "^8.4.21",
|
|
||||||
"prettier": "^3.0.3",
|
|
||||||
"prettier-plugin-tailwindcss": "^0.5.4",
|
|
||||||
"prisma": "^5.2.0",
|
|
||||||
"tailwindcss": "^3.3.0",
|
|
||||||
"typescript": "^5.2.2"
|
|
||||||
},
|
|
||||||
"ct3aMetadata": {
|
|
||||||
"initVersion": "7.14.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18.0.0 <19.0.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
const config = {
|
|
||||||
plugins: {
|
|
||||||
tailwindcss: {},
|
|
||||||
autoprefixer: {},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = config;
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
/** @type {import("prettier").Config} */
|
|
||||||
const config = {
|
|
||||||
plugins: [require.resolve("prettier-plugin-tailwindcss")],
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = config;
|
|
||||||
|
|
@ -1,66 +0,0 @@
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE "Account" (
|
|
||||||
"id" TEXT NOT NULL,
|
|
||||||
"userId" TEXT NOT NULL,
|
|
||||||
"type" TEXT NOT NULL,
|
|
||||||
"provider" TEXT NOT NULL,
|
|
||||||
"providerAccountId" TEXT NOT NULL,
|
|
||||||
"refresh_token" TEXT,
|
|
||||||
"access_token" TEXT,
|
|
||||||
"expires_at" INTEGER,
|
|
||||||
"token_type" TEXT,
|
|
||||||
"scope" TEXT,
|
|
||||||
"id_token" TEXT,
|
|
||||||
"session_state" TEXT,
|
|
||||||
|
|
||||||
CONSTRAINT "Account_pkey" PRIMARY KEY ("id")
|
|
||||||
);
|
|
||||||
|
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE "Session" (
|
|
||||||
"id" TEXT NOT NULL,
|
|
||||||
"sessionToken" TEXT NOT NULL,
|
|
||||||
"userId" TEXT NOT NULL,
|
|
||||||
"expires" TIMESTAMP(3) NOT NULL,
|
|
||||||
|
|
||||||
CONSTRAINT "Session_pkey" PRIMARY KEY ("id")
|
|
||||||
);
|
|
||||||
|
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE "User" (
|
|
||||||
"id" TEXT NOT NULL,
|
|
||||||
"name" TEXT,
|
|
||||||
"email" TEXT,
|
|
||||||
"emailVerified" TIMESTAMP(3),
|
|
||||||
"image" TEXT,
|
|
||||||
|
|
||||||
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
|
|
||||||
);
|
|
||||||
|
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE "VerificationToken" (
|
|
||||||
"identifier" TEXT NOT NULL,
|
|
||||||
"token" TEXT NOT NULL,
|
|
||||||
"expires" TIMESTAMP(3) NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE UNIQUE INDEX "Account_provider_providerAccountId_key" ON "Account"("provider", "providerAccountId");
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE UNIQUE INDEX "Session_sessionToken_key" ON "Session"("sessionToken");
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE UNIQUE INDEX "VerificationToken_token_key" ON "VerificationToken"("token");
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE UNIQUE INDEX "VerificationToken_identifier_token_key" ON "VerificationToken"("identifier", "token");
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE "Account" ADD CONSTRAINT "Account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE "Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE "Training" (
|
|
||||||
"id" TEXT NOT NULL,
|
|
||||||
"name" TEXT NOT NULL,
|
|
||||||
"slug" TEXT NOT NULL,
|
|
||||||
"days" INTEGER NOT NULL,
|
|
||||||
"weight" INTEGER NOT NULL,
|
|
||||||
"draft" BOOLEAN NOT NULL DEFAULT true,
|
|
||||||
"logoURL" TEXT,
|
|
||||||
"svgIconURL" TEXT,
|
|
||||||
"repositoryURL" TEXT,
|
|
||||||
"priceOpen" INTEGER NOT NULL,
|
|
||||||
"priceCorporate" INTEGER NOT NULL,
|
|
||||||
"content" TEXT,
|
|
||||||
|
|
||||||
CONSTRAINT "Training_pkey" PRIMARY KEY ("id")
|
|
||||||
);
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE UNIQUE INDEX "Training_slug_key" ON "Training"("slug");
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE INDEX "Training_slug_idx" ON "Training"("slug");
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
# Please do not edit this file manually
|
|
||||||
# It should be added in your version-control system (i.e. Git)
|
|
||||||
provider = "postgresql"
|
|
||||||
|
|
@ -1,79 +0,0 @@
|
||||||
// This is your Prisma schema file,
|
|
||||||
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
|
||||||
|
|
||||||
generator client {
|
|
||||||
provider = "prisma-client-js"
|
|
||||||
previewFeatures = ["jsonProtocol"]
|
|
||||||
binaryTargets = ["native", "linux-musl-openssl-3.0.x"]
|
|
||||||
}
|
|
||||||
|
|
||||||
datasource db {
|
|
||||||
provider = "postgresql"
|
|
||||||
// NOTE: When using mysql or sqlserver, uncomment the @db.Text annotations in model Account below
|
|
||||||
// Further reading:
|
|
||||||
// https://next-auth.js.org/adapters/prisma#create-the-prisma-schema
|
|
||||||
// https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#string
|
|
||||||
url = env("DATABASE_URL")
|
|
||||||
}
|
|
||||||
|
|
||||||
model Training {
|
|
||||||
id String @id @default(cuid())
|
|
||||||
name String
|
|
||||||
slug String @unique
|
|
||||||
days Int
|
|
||||||
weight Int
|
|
||||||
draft Boolean @default(true)
|
|
||||||
logoURL String?
|
|
||||||
svgIconURL String?
|
|
||||||
repositoryURL String?
|
|
||||||
priceOpen Int
|
|
||||||
priceCorporate Int
|
|
||||||
content String?
|
|
||||||
|
|
||||||
@@index([slug])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Necessary for Next auth
|
|
||||||
model Account {
|
|
||||||
id String @id @default(cuid())
|
|
||||||
userId String
|
|
||||||
type String
|
|
||||||
provider String
|
|
||||||
providerAccountId String
|
|
||||||
refresh_token String? // @db.Text
|
|
||||||
access_token String? // @db.Text
|
|
||||||
expires_at Int?
|
|
||||||
token_type String?
|
|
||||||
scope String?
|
|
||||||
id_token String? // @db.Text
|
|
||||||
session_state String?
|
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
||||||
|
|
||||||
@@unique([provider, providerAccountId])
|
|
||||||
}
|
|
||||||
|
|
||||||
model Session {
|
|
||||||
id String @id @default(cuid())
|
|
||||||
sessionToken String @unique
|
|
||||||
userId String
|
|
||||||
expires DateTime
|
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
||||||
}
|
|
||||||
|
|
||||||
model User {
|
|
||||||
id String @id @default(cuid())
|
|
||||||
name String?
|
|
||||||
email String? @unique
|
|
||||||
emailVerified DateTime?
|
|
||||||
image String?
|
|
||||||
accounts Account[]
|
|
||||||
sessions Session[]
|
|
||||||
}
|
|
||||||
|
|
||||||
model VerificationToken {
|
|
||||||
identifier String
|
|
||||||
token String @unique
|
|
||||||
expires DateTime
|
|
||||||
|
|
||||||
@@unique([identifier, token])
|
|
||||||
}
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
import { prisma } from "~/server/db";
|
|
||||||
import { getAllTrainingsWithMetadata } from "~/content/training";
|
|
||||||
|
|
||||||
async function seed() {
|
|
||||||
const trainings = await getAllTrainingsWithMetadata();
|
|
||||||
let instertedTrainings = [];
|
|
||||||
|
|
||||||
for (const training of trainings) {
|
|
||||||
instertedTrainings.push(
|
|
||||||
await prisma.training.upsert({
|
|
||||||
where: { id: training.metadata.id },
|
|
||||||
update: {
|
|
||||||
id: training.metadata.id ,
|
|
||||||
name: training.metadata.name,
|
|
||||||
slug: training.metadata.slug,
|
|
||||||
days: training.metadata.days,
|
|
||||||
weight: training.metadata.weight,
|
|
||||||
draft: training.metadata.draft,
|
|
||||||
logoURL: training.metadata.logoURL,
|
|
||||||
svgIconURL: training.metadata.svgIconURL,
|
|
||||||
repositoryURL: training.metadata.repositoryURL,
|
|
||||||
priceOpen: training.metadata.priceOpen,
|
|
||||||
priceCorporate: training.metadata.priceCorporate,
|
|
||||||
content: training.content,
|
|
||||||
},
|
|
||||||
create: {
|
|
||||||
id: training.metadata.id ,
|
|
||||||
name: training.metadata.name,
|
|
||||||
slug: training.metadata.slug,
|
|
||||||
days: training.metadata.days,
|
|
||||||
weight: training.metadata.weight,
|
|
||||||
draft: training.metadata.draft,
|
|
||||||
logoURL: training.metadata.logoURL,
|
|
||||||
svgIconURL: training.metadata.svgIconURL,
|
|
||||||
repositoryURL: training.metadata.repositoryURL,
|
|
||||||
priceOpen: training.metadata.priceOpen,
|
|
||||||
priceCorporate: training.metadata.priceCorporate,
|
|
||||||
content: training.content,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
seed()
|
|
||||||
.then(async () => {
|
|
||||||
await prisma.$disconnect()
|
|
||||||
})
|
|
||||||
.catch(async (e) => {
|
|
||||||
console.error(e)
|
|
||||||
await prisma.$disconnect()
|
|
||||||
process.exit(1)
|
|
||||||
});
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB |
|
|
@ -1,5 +0,0 @@
|
||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
import { createId } from '@paralleldrive/cuid2';
|
|
||||||
|
|
||||||
console.log(createId());
|
|
||||||
|
|
@ -1,75 +0,0 @@
|
||||||
import clsx from "clsx";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { type ReactNode } from "react";
|
|
||||||
|
|
||||||
const baseStyles = {
|
|
||||||
solid:
|
|
||||||
"group inline-flex items-center justify-center rounded-md font-semibold focus:outline-none focus-visible:outline-2 focus-visible:outline-offset-2",
|
|
||||||
outline:
|
|
||||||
"group inline-flex ring-1 items-center justify-center rounded-md focus:outline-none",
|
|
||||||
};
|
|
||||||
|
|
||||||
const variantStyles = {
|
|
||||||
solid: {
|
|
||||||
black:
|
|
||||||
"bg-black text-white hover:bg-gray-700 active:bg-gray-800 focus-visible:outline-gray-900",
|
|
||||||
amber:
|
|
||||||
"bg-amber-500 text-white hover:bg-amber-600 active:bg-amber-800 focus-visible:outline-amber-500",
|
|
||||||
white:
|
|
||||||
"bg-white text-black hover:bg-amber-50 active:bg-amber-200 focus-visible:outline-white",
|
|
||||||
},
|
|
||||||
outline: {
|
|
||||||
black:
|
|
||||||
"ring-gray-200 text-black hover:ring-gray-300 active:bg-gray-100 focus-visible:outline-amber-500 focus-visible:ring-gray-300",
|
|
||||||
white:
|
|
||||||
"ring-gray-700 text-white hover:ring-gray-500 active:ring-gray-700 focus-visible:outline-white",
|
|
||||||
amber: "", // Outline buttons cannot be amber
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const transitionStyle = "transition duration-150 ease-in-out";
|
|
||||||
|
|
||||||
const sizeStyles = {
|
|
||||||
medium: "px-4 py-2 text-sm",
|
|
||||||
large: "px-8 py-4 text-base",
|
|
||||||
};
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
variant?: "solid" | "outline";
|
|
||||||
color?: "black" | "white" | "amber";
|
|
||||||
size?: "medium" | "large";
|
|
||||||
className?: string;
|
|
||||||
href?: string;
|
|
||||||
children?: ReactNode;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function Button({
|
|
||||||
variant = "solid",
|
|
||||||
color = "black",
|
|
||||||
size = "medium",
|
|
||||||
className,
|
|
||||||
href,
|
|
||||||
children,
|
|
||||||
}: Props) {
|
|
||||||
if (variant === "outline" && color === "amber") {
|
|
||||||
throw new Error("Outline buttons cannot be amber");
|
|
||||||
}
|
|
||||||
|
|
||||||
className = clsx(
|
|
||||||
baseStyles[variant],
|
|
||||||
variantStyles[variant][color],
|
|
||||||
sizeStyles[size],
|
|
||||||
transitionStyle,
|
|
||||||
className
|
|
||||||
);
|
|
||||||
|
|
||||||
if (href !== undefined) {
|
|
||||||
return (
|
|
||||||
<Link href={href} className={className}>
|
|
||||||
{children}
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return <button className={className}>{children}</button>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
import { Sidebar } from "~/components/Sidebar";
|
|
||||||
|
|
||||||
export type LayoutProps = {
|
|
||||||
children: React.ReactNode;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function Layout({ children }: LayoutProps) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Sidebar />
|
|
||||||
<main className="py-10 lg:pl-72">
|
|
||||||
<div className="px-4 sm:px-6 lg:px-8">
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,158 +0,0 @@
|
||||||
import clsx from "clsx";
|
|
||||||
import Link from 'next/link';
|
|
||||||
import { useRouter } from 'next/router';
|
|
||||||
import { Fragment, useState } from 'react';
|
|
||||||
import { Dialog, Transition } from '@headlessui/react';
|
|
||||||
import {
|
|
||||||
Bars3Icon,
|
|
||||||
HomeIcon,
|
|
||||||
XMarkIcon,
|
|
||||||
AcademicCapIcon,
|
|
||||||
ArrowRightOnRectangleIcon,
|
|
||||||
} from '@heroicons/react/24/outline';
|
|
||||||
|
|
||||||
const navigation = [
|
|
||||||
{ name: 'Dashboard', href: '/', icon: HomeIcon },
|
|
||||||
{ name: 'Trainings', href: '/trainings', icon: AcademicCapIcon },
|
|
||||||
]
|
|
||||||
|
|
||||||
export function Sidebar() {
|
|
||||||
const [sidebarOpen, setSidebarOpen] = useState(false)
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Transition.Root show={sidebarOpen} as={Fragment}>
|
|
||||||
<Dialog as="div" className="relative z-50 lg:hidden" onClose={setSidebarOpen}>
|
|
||||||
<Transition.Child
|
|
||||||
as={Fragment}
|
|
||||||
enter="transition-opacity ease-linear duration-300"
|
|
||||||
enterFrom="opacity-0"
|
|
||||||
enterTo="opacity-100"
|
|
||||||
leave="transition-opacity ease-linear duration-300"
|
|
||||||
leaveFrom="opacity-100"
|
|
||||||
leaveTo="opacity-0"
|
|
||||||
>
|
|
||||||
<div className="fixed inset-0 bg-black/80" />
|
|
||||||
</Transition.Child>
|
|
||||||
|
|
||||||
<div className="fixed inset-0 flex">
|
|
||||||
<Transition.Child
|
|
||||||
as={Fragment}
|
|
||||||
enter="transition ease-in-out duration-300 transform"
|
|
||||||
enterFrom="-translate-x-full"
|
|
||||||
enterTo="translate-x-0"
|
|
||||||
leave="transition ease-in-out duration-300 transform"
|
|
||||||
leaveFrom="translate-x-0"
|
|
||||||
leaveTo="-translate-x-full"
|
|
||||||
>
|
|
||||||
<Dialog.Panel className="relative mr-16 flex w-full max-w-xs flex-1">
|
|
||||||
<Transition.Child
|
|
||||||
as={Fragment}
|
|
||||||
enter="ease-in-out duration-300"
|
|
||||||
enterFrom="opacity-0"
|
|
||||||
enterTo="opacity-100"
|
|
||||||
leave="ease-in-out duration-300"
|
|
||||||
leaveFrom="opacity-100"
|
|
||||||
leaveTo="opacity-0"
|
|
||||||
>
|
|
||||||
<div className="absolute left-full top-0 flex w-16 justify-center pt-5">
|
|
||||||
<button type="button" className="-m-2.5 p-2.5" onClick={() => setSidebarOpen(false)}>
|
|
||||||
<span className="sr-only">Close sidebar</span>
|
|
||||||
<XMarkIcon className="h-6 w-6 text-white" aria-hidden="true" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</Transition.Child>
|
|
||||||
{/* Sidebar component, swap this element with another sidebar if you like */}
|
|
||||||
<div className="flex grow flex-col gap-y-5 overflow-y-auto bg-black px-6 pb-2 ring-1 ring-white/10">
|
|
||||||
<div className="flex h-16 shrink-0 items-center">
|
|
||||||
<span className="text-amber-600 text-2xl font-bold">backoffice</span>
|
|
||||||
</div>
|
|
||||||
<nav className="flex flex-1 flex-col">
|
|
||||||
<ul role="list" className="flex flex-1 flex-col gap-y-7">
|
|
||||||
<li>
|
|
||||||
<ul role="list" className="-mx-2 space-y-1">
|
|
||||||
{navigation.map((item) => (
|
|
||||||
<li key={item.name}>
|
|
||||||
<Link
|
|
||||||
href={item.href}
|
|
||||||
className={clsx(
|
|
||||||
router.pathname === item.href
|
|
||||||
? 'bg-gray-800 text-white'
|
|
||||||
: 'text-gray-400 hover:text-white hover:bg-gray-800',
|
|
||||||
'group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold'
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<item.icon className="h-6 w-6 shrink-0" aria-hidden="true" />
|
|
||||||
{item.name}
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
</Dialog.Panel>
|
|
||||||
</Transition.Child>
|
|
||||||
</div>
|
|
||||||
</Dialog>
|
|
||||||
</Transition.Root>
|
|
||||||
|
|
||||||
{/* Static sidebar for desktop */}
|
|
||||||
<div className="hidden lg:fixed lg:inset-y-0 lg:z-50 lg:flex lg:w-72 lg:flex-col">
|
|
||||||
{/* Sidebar component, swap this element with another sidebar if you like */}
|
|
||||||
<div className="flex grow flex-col gap-y-5 overflow-y-auto bg-black px-6">
|
|
||||||
<div className="flex h-16 shrink-0 items-center">
|
|
||||||
<span className="text-amber-600 text-2xl font-bold">backoffice</span>
|
|
||||||
</div>
|
|
||||||
<nav className="flex flex-1 flex-col">
|
|
||||||
<ul role="list" className="flex flex-1 flex-col gap-y-7">
|
|
||||||
<li>
|
|
||||||
<ul role="list" className="-mx-2 space-y-1">
|
|
||||||
{navigation.map((item) => (
|
|
||||||
<li key={item.name}>
|
|
||||||
<Link
|
|
||||||
href={item.href}
|
|
||||||
className={clsx(
|
|
||||||
router.pathname === item.href
|
|
||||||
? 'bg-gray-800 text-white'
|
|
||||||
: 'text-gray-400 hover:text-white hover:bg-gray-800',
|
|
||||||
'group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold'
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<item.icon className="h-6 w-6 shrink-0" aria-hidden="true" />
|
|
||||||
{item.name}
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li className="-mx-6 mt-auto">
|
|
||||||
<Link
|
|
||||||
href="/api/auth/signout"
|
|
||||||
className="flex items-center gap-x-4 px-6 py-3 text-sm font-semibold leading-6 text-white hover:bg-gray-800"
|
|
||||||
>
|
|
||||||
<ArrowRightOnRectangleIcon className="h-6 w-6" />
|
|
||||||
<span>Sign out</span>
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="sticky top-0 z-40 flex items-center gap-x-6 bg-black px-4 py-4 shadow-sm sm:px-6 lg:hidden">
|
|
||||||
<button type="button" className="-m-2.5 p-2.5 text-gray-400 lg:hidden" onClick={() => setSidebarOpen(true)}>
|
|
||||||
<span className="sr-only">Open sidebar</span>
|
|
||||||
<Bars3Icon className="h-6 w-6" aria-hidden="true" />
|
|
||||||
</button>
|
|
||||||
<div className="flex-1 text-sm font-semibold leading-6 text-white">Dashboard</div>
|
|
||||||
<Link href="/api/auth/signout">
|
|
||||||
<span className="sr-only">Sign out</span>
|
|
||||||
<ArrowRightOnRectangleIcon className="h-6 w-6 text-white" />
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
import fs from 'fs';
|
|
||||||
import path from 'path';
|
|
||||||
import matter from 'gray-matter';
|
|
||||||
|
|
||||||
type Training = {
|
|
||||||
metadata: {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
slug: string;
|
|
||||||
days: number;
|
|
||||||
weight: number;
|
|
||||||
draft?: boolean;
|
|
||||||
logoURL?: string;
|
|
||||||
svgIconURL?: string;
|
|
||||||
repositoryURL?: string;
|
|
||||||
priceOpen: number;
|
|
||||||
priceCorporate: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
content: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type { Training };
|
|
||||||
|
|
||||||
const root = process.cwd();
|
|
||||||
|
|
||||||
export function getTrainingFiles() {
|
|
||||||
return fs.readdirSync(path.join(root, 'content', 'training'), 'utf-8');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getTrainingBySlug(slug: string) {
|
|
||||||
const source = fs.readFileSync(path.join(root, 'content', 'training', `${slug}.md`), 'utf8');
|
|
||||||
|
|
||||||
const { data, content } = matter(source);
|
|
||||||
|
|
||||||
return {
|
|
||||||
metadata: data,
|
|
||||||
content: content,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getAllTrainingsWithMetadata(): Training[] {
|
|
||||||
const files = fs.readdirSync(path.join(root, 'content', 'training'))
|
|
||||||
|
|
||||||
const trainings = [] as Training[];
|
|
||||||
|
|
||||||
for (const fileName of files) {
|
|
||||||
const source = fs.readFileSync(path.join(root, 'content', 'training', fileName), 'utf8');
|
|
||||||
const { data: metadata, content: content } = matter(source);
|
|
||||||
trainings.push({metadata, content} as Training);
|
|
||||||
}
|
|
||||||
|
|
||||||
return trainings;
|
|
||||||
|
|
||||||
// return files.reduce((allTrainings, fileName) => {
|
|
||||||
// const source = fs.readFileSync(path.join(root, 'content', 'training', fileName), 'utf8');
|
|
||||||
// const training = matter(source);
|
|
||||||
|
|
||||||
// return [
|
|
||||||
// training,
|
|
||||||
// ...allTrainings,
|
|
||||||
// ]
|
|
||||||
// }, [])
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
export function formatCurrency(price: number | bigint, locale ='en-US', currency = 'CZK'): string {
|
|
||||||
const currencyFormatter = new Intl.NumberFormat(locale, {
|
|
||||||
style: 'currency',
|
|
||||||
currency: currency,
|
|
||||||
maximumFractionDigits: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
return currencyFormatter.format(price).replace(',', ' ')
|
|
||||||
}
|
|
||||||
56
src/env.mjs
56
src/env.mjs
|
|
@ -1,56 +0,0 @@
|
||||||
import { createEnv } from "@t3-oss/env-nextjs";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
export const env = createEnv({
|
|
||||||
/**
|
|
||||||
* Specify your server-side environment variables schema here. This way you can ensure the app
|
|
||||||
* isn't built with invalid env vars.
|
|
||||||
*/
|
|
||||||
server: {
|
|
||||||
DATABASE_URL: z.string().url(),
|
|
||||||
NODE_ENV: z.enum(["development", "test", "production"]),
|
|
||||||
NEXTAUTH_SECRET:
|
|
||||||
process.env.NODE_ENV === "production"
|
|
||||||
? z.string().min(1)
|
|
||||||
: z.string().min(1).optional(),
|
|
||||||
NEXTAUTH_URL: z.preprocess(
|
|
||||||
// This makes Vercel deployments not fail if you don't set NEXTAUTH_URL
|
|
||||||
// Since NextAuth.js automatically uses the VERCEL_URL if present.
|
|
||||||
(str) => process.env.VERCEL_URL ?? str,
|
|
||||||
// VERCEL_URL doesn't include `https` so it cant be validated as a URL
|
|
||||||
process.env.VERCEL ? z.string().min(1) : z.string().url(),
|
|
||||||
),
|
|
||||||
// Add `.min(1) on ID and SECRET if you want to make sure they're not empty
|
|
||||||
KEYCLOAK_CLIENT_ID: z.string(),
|
|
||||||
KEYCLOAK_CLIENT_SECRET: z.string(),
|
|
||||||
KEYCLOAK_ISSUER: z.string(),
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Specify your client-side environment variables schema here. This way you can ensure the app
|
|
||||||
* isn't built with invalid env vars. To expose them to the client, prefix them with
|
|
||||||
* `NEXT_PUBLIC_`.
|
|
||||||
*/
|
|
||||||
client: {
|
|
||||||
// NEXT_PUBLIC_CLIENTVAR: z.string().min(1),
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* You can't destruct `process.env` as a regular object in the Next.js edge runtimes (e.g.
|
|
||||||
* middlewares) or client-side so we need to destruct manually.
|
|
||||||
*/
|
|
||||||
runtimeEnv: {
|
|
||||||
DATABASE_URL: process.env.DATABASE_URL,
|
|
||||||
NODE_ENV: process.env.NODE_ENV,
|
|
||||||
NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET,
|
|
||||||
NEXTAUTH_URL: process.env.NEXTAUTH_URL,
|
|
||||||
KEYCLOAK_CLIENT_ID: process.env.KEYCLOAK_CLIENT_ID,
|
|
||||||
KEYCLOAK_CLIENT_SECRET: process.env.KEYCLOAK_CLIENT_SECRET,
|
|
||||||
KEYCLOAK_ISSUER: process.env.KEYCLOAK_ISSUER,
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation.
|
|
||||||
* This is especially useful for Docker builds.
|
|
||||||
*/
|
|
||||||
skipValidation: !!process.env.SKIP_ENV_VALIDATION,
|
|
||||||
});
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
import { type Session } from "next-auth";
|
|
||||||
import { SessionProvider } from "next-auth/react";
|
|
||||||
import { type AppType } from "next/app";
|
|
||||||
import "~/styles/globals.css";
|
|
||||||
|
|
||||||
const MyApp: AppType<{ session: Session | null }> = ({
|
|
||||||
Component,
|
|
||||||
pageProps: { session, ...pageProps },
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<SessionProvider session={session}>
|
|
||||||
<Component {...pageProps} />
|
|
||||||
</SessionProvider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default MyApp;
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
import { Html, Head, Main, NextScript } from 'next/document'
|
|
||||||
|
|
||||||
export default function Document() {
|
|
||||||
return (
|
|
||||||
<Html className="h-full bg-white">
|
|
||||||
<Head />
|
|
||||||
<body className="h-full">
|
|
||||||
<Main />
|
|
||||||
<NextScript />
|
|
||||||
</body>
|
|
||||||
</Html>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
import NextAuth from "next-auth";
|
|
||||||
import { authOptions } from "~/server/auth";
|
|
||||||
|
|
||||||
export default NextAuth(authOptions);
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
import type {NextApiRequest, NextApiResponse} from 'next'
|
|
||||||
|
|
||||||
// GET /api/livez
|
|
||||||
export default function handle(
|
|
||||||
req: NextApiRequest,
|
|
||||||
res: NextApiResponse,
|
|
||||||
) {
|
|
||||||
if (req.method !== 'GET') {
|
|
||||||
res.status(405).end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
res.status(200).json({status: 'ok'});
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
import type { NextApiRequest, NextApiResponse } from 'next'
|
|
||||||
import { prisma } from '~/server/db';
|
|
||||||
|
|
||||||
// GET /api/v1/trainings/:id
|
|
||||||
export default async function handle(
|
|
||||||
req: NextApiRequest,
|
|
||||||
res: NextApiResponse,
|
|
||||||
) {
|
|
||||||
if (req.method !== 'GET') return res.status(405);
|
|
||||||
|
|
||||||
const trainingId = req.query.id as string;
|
|
||||||
const training = await prisma.training.findUnique({ where: { id: trainingId }});
|
|
||||||
|
|
||||||
if (!training) return res.status(404).json({ message: 'Not found' });
|
|
||||||
|
|
||||||
return res.status(200).json(training)
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
import type { NextApiRequest, NextApiResponse } from 'next'
|
|
||||||
import { prisma } from '~/server/db';
|
|
||||||
|
|
||||||
// GET /api/v1/trainings
|
|
||||||
export default async function handle(
|
|
||||||
req: NextApiRequest,
|
|
||||||
res: NextApiResponse,
|
|
||||||
) {
|
|
||||||
if (req.method !== 'GET') return res.status(405);
|
|
||||||
|
|
||||||
const trainings = await prisma.training.findMany();
|
|
||||||
return res.status(200).json(trainings)
|
|
||||||
}
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
import Head from "next/head";
|
|
||||||
import { type GetServerSideProps } from "next";
|
|
||||||
|
|
||||||
import { getServerAuthSession } from "~/server/auth";
|
|
||||||
import { Layout } from "~/components/Layout";
|
|
||||||
|
|
||||||
export const getServerSideProps: GetServerSideProps = async (ctx) => {
|
|
||||||
const session = await getServerAuthSession(ctx);
|
|
||||||
|
|
||||||
if (!session) return { redirect: { destination: '/api/auth/signin', permanent: false } };
|
|
||||||
|
|
||||||
return { props: { session } };
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Home() {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Head>
|
|
||||||
<title>Dashboard | MaresHQ backoffice</title>
|
|
||||||
<meta name="description" content="Generated by create-t3-app" />
|
|
||||||
<link rel="icon" href="/favicon.ico" />
|
|
||||||
</Head>
|
|
||||||
<Layout>
|
|
||||||
<div></div>
|
|
||||||
</Layout>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,107 +0,0 @@
|
||||||
import ReactMarkdown from 'react-markdown';
|
|
||||||
import { type GetServerSideProps } from "next";
|
|
||||||
import Head from "next/head";
|
|
||||||
import Link from "next/link";
|
|
||||||
|
|
||||||
import { formatCurrency } from "~/currency/formatter";
|
|
||||||
|
|
||||||
import { getServerAuthSession } from "~/server/auth";
|
|
||||||
import { prisma } from "~/server/db";
|
|
||||||
import { Layout } from "~/components/Layout";
|
|
||||||
|
|
||||||
type Training = {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
slug: string;
|
|
||||||
days: number;
|
|
||||||
draft: boolean;
|
|
||||||
priceOpen: number;
|
|
||||||
priceCorporate: number;
|
|
||||||
logoURL: string;
|
|
||||||
svgIconURL: string;
|
|
||||||
repositoryURL: string;
|
|
||||||
content: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getServerSideProps: GetServerSideProps = async (ctx) => {
|
|
||||||
const session = await getServerAuthSession(ctx);
|
|
||||||
if (!session) return { redirect: { destination: '/api/auth/signin', permanent: false } };
|
|
||||||
|
|
||||||
const trainingSlug = ctx.query.slug as string;
|
|
||||||
const training = await prisma.training.findUnique({ where: { slug: trainingSlug }});
|
|
||||||
|
|
||||||
if (!training) return { notFound: true };
|
|
||||||
|
|
||||||
return { props: { training: training, session } };
|
|
||||||
}
|
|
||||||
|
|
||||||
function Detail({ training }: { training: Training }) {
|
|
||||||
return (
|
|
||||||
<div className="overflow-hidden bg-white shadow ring-1 ring-black ring-opacity-5 sm:rounded-lg">
|
|
||||||
<div className="px-4 py-6 sm:px-6">
|
|
||||||
<h3 className="text-base font-semibold leading-7 text-gray-900">Training</h3>
|
|
||||||
{/* <p className="mt-1 max-w-2xl text-sm leading-6 text-gray-500">Personal details and application.</p> */}
|
|
||||||
</div>
|
|
||||||
<div className="border-t border-gray-100">
|
|
||||||
<dl className="divide-y divide-gray-100">
|
|
||||||
<div className="px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
|
||||||
<dt className="text-sm font-medium text-gray-900">backoffice ID</dt>
|
|
||||||
<dd className="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0"><code>{training.id}</code></dd>
|
|
||||||
</div>
|
|
||||||
<div className="px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
|
||||||
<dt className="text-sm font-medium text-gray-900">Name</dt>
|
|
||||||
<dd className="mt-1 text-sm leading-6 font-medium text-gray-900 sm:col-span-2 sm:mt-0">{training.name}</dd>
|
|
||||||
</div>
|
|
||||||
<div className="px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
|
||||||
<dt className="text-sm font-medium text-gray-900">Days</dt>
|
|
||||||
<dd className="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">{training.days}</dd>
|
|
||||||
</div>
|
|
||||||
<div className="px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
|
||||||
<dt className="text-sm font-medium text-gray-900">Draft</dt>
|
|
||||||
<dd className="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">{training.draft ? 'Yes' : 'No'}</dd>
|
|
||||||
</div>
|
|
||||||
<div className="px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
|
||||||
<dt className="text-sm font-medium text-gray-900">Logo</dt>
|
|
||||||
<dd className="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">{training.logoURL}</dd>
|
|
||||||
</div>
|
|
||||||
<div className="px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
|
||||||
<dt className="text-sm font-medium text-gray-900">Repository</dt>
|
|
||||||
<dd className="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0"><Link href={training.repositoryURL} className="text-black underline">{training.repositoryURL}</Link></dd>
|
|
||||||
</div>
|
|
||||||
<div className="px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
|
||||||
<dt className="text-sm font-medium text-gray-900">Price</dt>
|
|
||||||
<dd className="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">Open: {formatCurrency(training.priceOpen)}, Corporate: {formatCurrency(training.priceCorporate)}</dd>
|
|
||||||
</div>
|
|
||||||
{/* <div className="px-4 py-6 sm:grid sm:grid-cols-2 sm:gap-4 sm:px-6">
|
|
||||||
<dt className="text-sm font-medium text-gray-900">Content</dt>
|
|
||||||
<dd className="mt-1 text-sm leading-6 text-gray-700 sm:col-span-1 sm:mt-0">
|
|
||||||
<div className="prose prose:text-black prose-p:text-slate-700 prose-h1:text-2xl prose-h1:font-medium prose-h2:text-xl prose-h2:font-medium prose-h3:text-lg prose-h3:font-medium prose-li:my-0">
|
|
||||||
<ReactMarkdown>
|
|
||||||
{training.content}
|
|
||||||
</ReactMarkdown>
|
|
||||||
</div>
|
|
||||||
</dd>
|
|
||||||
</div> */}
|
|
||||||
<div className="mx-auto px-4 py-6 sm:gap-4 sm:px-6 prose prose:text-black prose-p:text-slate-700 prose-h1:text-2xl prose-h1:font-medium prose-h2:text-xl prose-h2:font-medium prose-h3:text-lg prose-h3:font-medium prose-li:my-0">
|
|
||||||
<ReactMarkdown>
|
|
||||||
{training.content}
|
|
||||||
</ReactMarkdown>
|
|
||||||
</div>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Training({ training }: { training: Training }) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Head>
|
|
||||||
<title>Training | MaresHQ backoffice</title>
|
|
||||||
</Head>
|
|
||||||
<Layout>
|
|
||||||
<Detail training={training} />
|
|
||||||
</Layout>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,133 +0,0 @@
|
||||||
import { type GetServerSideProps } from "next";
|
|
||||||
import Head from "next/head";
|
|
||||||
import Link from "next/link";
|
|
||||||
|
|
||||||
import { formatCurrency } from "~/currency/formatter";
|
|
||||||
|
|
||||||
import { getServerAuthSession } from "~/server/auth";
|
|
||||||
import { prisma } from "~/server/db";
|
|
||||||
import { Training } from "~/content/training";
|
|
||||||
import { Layout } from "~/components/Layout";
|
|
||||||
import { Button } from "~/components/Button";
|
|
||||||
|
|
||||||
type Trainings = [
|
|
||||||
{
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
slug: string;
|
|
||||||
days: number;
|
|
||||||
draft: boolean;
|
|
||||||
priceOpen: number;
|
|
||||||
priceCorporate: number;
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
export const getServerSideProps: GetServerSideProps = async (ctx) => {
|
|
||||||
const session = await getServerAuthSession(ctx);
|
|
||||||
|
|
||||||
if (!session) return { redirect: { destination: '/api/auth/signin', permanent: false } };
|
|
||||||
|
|
||||||
const trainings = await prisma.training.findMany({
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
name: true,
|
|
||||||
slug: true,
|
|
||||||
days: true,
|
|
||||||
draft: true,
|
|
||||||
priceOpen: true,
|
|
||||||
priceCorporate: true,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return { props: { trainings: trainings, session } };
|
|
||||||
}
|
|
||||||
|
|
||||||
function Table({trainings}: { trainings: Trainings }) {
|
|
||||||
return (
|
|
||||||
<div className="px-4 sm:px-6 lg:px-8">
|
|
||||||
<div className="sm:flex sm:items-center">
|
|
||||||
<div className="sm:flex-auto">
|
|
||||||
<h1 className="text-base font-semibold leading-6 text-gray-900">Trainings</h1>
|
|
||||||
{/* <p className="mt-2 text-sm text-gray-700">
|
|
||||||
A list of all the users in your account including their name, title, email and role.
|
|
||||||
</p> */}
|
|
||||||
</div>
|
|
||||||
<div className="mt-4 sm:ml-16 sm:mt-0 sm:flex-none">
|
|
||||||
{/* <button
|
|
||||||
type="button"
|
|
||||||
className="block rounded-md bg-indigo-600 px-3 py-2 text-center text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
|
|
||||||
>
|
|
||||||
Add user
|
|
||||||
</button> */}
|
|
||||||
<Button href="https://github.com/vojtechmares/backoffice/new/main/content/training">
|
|
||||||
Add training
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mt-8 flow-root">
|
|
||||||
<div className="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
|
|
||||||
<div className="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">
|
|
||||||
<div className="overflow-hidden shadow ring-1 ring-black ring-opacity-5 sm:rounded-lg">
|
|
||||||
<table className="min-w-full divide-y divide-gray-300">
|
|
||||||
<thead className="bg-gray-50">
|
|
||||||
<tr>
|
|
||||||
<th scope="col" className="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6">
|
|
||||||
Name
|
|
||||||
</th>
|
|
||||||
<th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
|
||||||
Days
|
|
||||||
</th>
|
|
||||||
<th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
|
||||||
Draft
|
|
||||||
</th>
|
|
||||||
<th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
|
||||||
Price Open
|
|
||||||
</th>
|
|
||||||
<th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
|
||||||
Price Corporate
|
|
||||||
</th>
|
|
||||||
<th scope="col" className="relative py-3.5 pl-3 pr-4 sm:pr-6">
|
|
||||||
<span className="sr-only">Edit</span>
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody className="divide-y divide-gray-200 bg-white">
|
|
||||||
{trainings.map((training) => (
|
|
||||||
<tr key={training.id}>
|
|
||||||
<td className="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:pl-6">
|
|
||||||
{training.name}
|
|
||||||
</td>
|
|
||||||
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500">{training.days}</td>
|
|
||||||
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500">{training.draft ? 'Yes' : 'No'}</td>
|
|
||||||
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500">{formatCurrency(training.priceOpen)}</td>
|
|
||||||
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500">{formatCurrency(training.priceCorporate)}</td>
|
|
||||||
<td className="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6">
|
|
||||||
<Link href={"/training/" + training.slug} className="text-black underline">
|
|
||||||
Detail<span className="sr-only"> of {training.name}</span>
|
|
||||||
</Link>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Training({ trainings }: { trainings: Trainings }) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Head>
|
|
||||||
<title>Training | MaresHQ backoffice</title>
|
|
||||||
</Head>
|
|
||||||
<Layout>
|
|
||||||
{/* { JSON.stringify(trainings) } */}
|
|
||||||
<Table trainings={trainings} />
|
|
||||||
</Layout>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,147 +0,0 @@
|
||||||
import { PrismaAdapter } from "@next-auth/prisma-adapter";
|
|
||||||
import { type GetServerSidePropsContext } from "next";
|
|
||||||
import {
|
|
||||||
getServerSession,
|
|
||||||
type NextAuthOptions,
|
|
||||||
type DefaultSession,
|
|
||||||
} from "next-auth";
|
|
||||||
import KeycloakProvider from "next-auth/providers/keycloak";
|
|
||||||
import { type AdapterAccount } from "next-auth/adapters";
|
|
||||||
import { type JWT } from "next-auth/jwt";
|
|
||||||
import { env } from "~/env.mjs";
|
|
||||||
import { prisma } from "~/server/db";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module augmentation for `next-auth` types. Allows us to add custom properties to the `session`
|
|
||||||
* object and keep type safety.
|
|
||||||
*
|
|
||||||
* @see https://next-auth.js.org/getting-started/typescript#module-augmentation
|
|
||||||
*/
|
|
||||||
declare module "next-auth" {
|
|
||||||
interface Session extends DefaultSession {
|
|
||||||
user: {
|
|
||||||
id: string;
|
|
||||||
// ...other properties
|
|
||||||
// role: UserRole;
|
|
||||||
} & DefaultSession["user"];
|
|
||||||
}
|
|
||||||
|
|
||||||
// interface User {
|
|
||||||
// // ...other properties
|
|
||||||
// // role: UserRole;
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Part of the Keycloak fix/workaround, see code bellow for method `signOut`.
|
|
||||||
*
|
|
||||||
* @see https://stackoverflow.com/a/75526977
|
|
||||||
*/
|
|
||||||
declare module "next-auth/jwt" {
|
|
||||||
interface JWT {
|
|
||||||
id_token?: string;
|
|
||||||
provider?: string;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const adapter = PrismaAdapter(prisma);
|
|
||||||
const originLinkAccount = adapter.linkAccount;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method override handles Keycloak response with fields we are not expecting,
|
|
||||||
* as a part of the response and we have no database fields for them,
|
|
||||||
* which caused error on writing data to database.
|
|
||||||
*
|
|
||||||
* @see https://stackoverflow.com/questions/69910570/prisma-with-next-auth-user-creation-fails-cause-of-keycloaks-api-response-key
|
|
||||||
*/
|
|
||||||
adapter.linkAccount = (account: AdapterAccount) => {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
const { "not-before-policy": _, refresh_expires_in, ...data } = account;
|
|
||||||
|
|
||||||
if (!originLinkAccount) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return originLinkAccount(data);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Options for NextAuth.js used to configure adapters, providers, callbacks, etc.
|
|
||||||
*
|
|
||||||
* @see https://next-auth.js.org/configuration/options
|
|
||||||
*/
|
|
||||||
export const authOptions: NextAuthOptions = {
|
|
||||||
callbacks: {
|
|
||||||
session: ({ session, user }) => ({
|
|
||||||
...session,
|
|
||||||
user: {
|
|
||||||
...session.user,
|
|
||||||
id: user.id,
|
|
||||||
email: user.email,
|
|
||||||
image: user.image,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
/**
|
|
||||||
* Part of the Keycloak fix/workaround, see code bellow for method `signOut`.
|
|
||||||
*
|
|
||||||
* @see https://stackoverflow.com/a/75526977
|
|
||||||
*/
|
|
||||||
jwt({ token, account }) {
|
|
||||||
if (account) {
|
|
||||||
token.id_token = account?.id_token;
|
|
||||||
token.provider = account?.provider;
|
|
||||||
}
|
|
||||||
return token;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
adapter: adapter,
|
|
||||||
providers: [
|
|
||||||
KeycloakProvider({
|
|
||||||
clientId: env.KEYCLOAK_CLIENT_ID,
|
|
||||||
clientSecret: env.KEYCLOAK_CLIENT_SECRET,
|
|
||||||
issuer: env.KEYCLOAK_ISSUER,
|
|
||||||
// authorizationUrl: env.KEYCLOAK_ISSUER + "/protocol/openid-connect/auth",
|
|
||||||
// accessTokenUrl: env.KEYCLOAK_ISSUER + "/protocol/openid-connect/token",
|
|
||||||
// profileUrl: env.KEYCLOAK_ISSUER + "/protocol/openid-connect/userinfo",
|
|
||||||
// wellKnown: env.KEYCLOAK_ISSUER + "/.well-known/openid-configuration",
|
|
||||||
}),
|
|
||||||
/**
|
|
||||||
* ...add more providers here.
|
|
||||||
*
|
|
||||||
* Most other providers require a bit more work than the Discord provider. For example, the
|
|
||||||
* GitHub provider requires you to add the `refresh_token_expires_in` field to the Account
|
|
||||||
* model. Refer to the NextAuth.js docs for the provider you want to use. Example:
|
|
||||||
*
|
|
||||||
* @see https://next-auth.js.org/providers/github
|
|
||||||
*/
|
|
||||||
],
|
|
||||||
events: {
|
|
||||||
/**
|
|
||||||
* Fix for Keycloak not destroying the session token on logout,
|
|
||||||
* we must send an extra request to delete the session.
|
|
||||||
*
|
|
||||||
* @see https://stackoverflow.com/a/75526977
|
|
||||||
*/
|
|
||||||
async signOut({ token }: { token: JWT }) {
|
|
||||||
if (token.provider === "keycloak") {
|
|
||||||
const logOutURL = new URL(
|
|
||||||
`${env.KEYCLOAK_ISSUER}/protocol/openid-connect/logout`
|
|
||||||
);
|
|
||||||
logOutURL.searchParams.set("id_token_hint", token.id_token ?? "");
|
|
||||||
await fetch(logOutURL);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wrapper for `getServerSession` so that you don't need to import the `authOptions` in every file.
|
|
||||||
*
|
|
||||||
* @see https://next-auth.js.org/configuration/nextjs
|
|
||||||
*/
|
|
||||||
export const getServerAuthSession = (ctx: {
|
|
||||||
req: GetServerSidePropsContext["req"];
|
|
||||||
res: GetServerSidePropsContext["res"];
|
|
||||||
}) => {
|
|
||||||
return getServerSession(ctx.req, ctx.res, authOptions);
|
|
||||||
};
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
import { PrismaClient } from "@prisma/client";
|
|
||||||
import { env } from "~/env.mjs";
|
|
||||||
|
|
||||||
const globalForPrisma = globalThis as unknown as {
|
|
||||||
prisma: PrismaClient | undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const prisma =
|
|
||||||
globalForPrisma.prisma ??
|
|
||||||
new PrismaClient({
|
|
||||||
log:
|
|
||||||
env.NODE_ENV === "development" ? ["query", "error", "warn"] : ["error"],
|
|
||||||
});
|
|
||||||
|
|
||||||
if (env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
@tailwind base;
|
|
||||||
@tailwind components;
|
|
||||||
@tailwind utilities;
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
import { type Config } from "tailwindcss";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
content: ["./src/**/*.{js,ts,jsx,tsx}"],
|
|
||||||
theme: {
|
|
||||||
extend: {},
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
require('@tailwindcss/typography'),
|
|
||||||
],
|
|
||||||
} satisfies Config;
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "es2017",
|
|
||||||
"lib": ["dom", "dom.iterable", "esnext"],
|
|
||||||
"allowJs": true,
|
|
||||||
"checkJs": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"strict": true,
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
|
||||||
"noEmit": true,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"module": "esnext",
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"isolatedModules": true,
|
|
||||||
"jsx": "preserve",
|
|
||||||
"incremental": true,
|
|
||||||
"noUncheckedIndexedAccess": true,
|
|
||||||
"baseUrl": ".",
|
|
||||||
"paths": {
|
|
||||||
"~/*": ["./src/*"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"include": [
|
|
||||||
".eslintrc.cjs",
|
|
||||||
"next-env.d.ts",
|
|
||||||
"**/*.ts",
|
|
||||||
"**/*.tsx",
|
|
||||||
"**/*.cjs",
|
|
||||||
"**/*.mjs"
|
|
||||||
],
|
|
||||||
"exclude": ["node_modules"]
|
|
||||||
}
|
|
||||||
Reference in a new issue