mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-19 13:21:46 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 249ab217f8 | |||
| 499097fa3c | |||
| 002ea91be8 |
@@ -23,4 +23,4 @@ jobs:
|
|||||||
- name: Publish to GitHub wiki
|
- name: Publish to GitHub wiki
|
||||||
uses: Andrew-Chen-Wang/github-wiki-action@v5
|
uses: Andrew-Chen-Wang/github-wiki-action@v5
|
||||||
with:
|
with:
|
||||||
strategy: init
|
strategy: clone
|
||||||
|
|||||||
@@ -400,6 +400,7 @@ Caddy handles TLS and WebSockets automatically.
|
|||||||
| `DEFAULT_LANGUAGE` | Default language on the login page for users with no saved preference. Browser/OS language is auto-detected first; this is the fallback. Supported: `de`, `en`, `es`, `fr`, `hu`, `nl`, `br`, `cs`, `pl`, `ru`, `zh`, `zh-TW`, `it`, `ar` | `en` |
|
| `DEFAULT_LANGUAGE` | Default language on the login page for users with no saved preference. Browser/OS language is auto-detected first; this is the fallback. Supported: `de`, `en`, `es`, `fr`, `hu`, `nl`, `br`, `cs`, `pl`, `ru`, `zh`, `zh-TW`, `it`, `ar` | `en` |
|
||||||
| `ALLOWED_ORIGINS` | Comma-separated origins for CORS and email links | same-origin |
|
| `ALLOWED_ORIGINS` | Comma-separated origins for CORS and email links | same-origin |
|
||||||
| `FORCE_HTTPS` | Optional. When `true`: 301-redirects HTTP to HTTPS, sends HSTS, adds CSP `upgrade-insecure-requests`, forces the session cookie `secure` flag. Useful behind a TLS-terminating reverse proxy. Requires `TRUST_PROXY`. | `false` |
|
| `FORCE_HTTPS` | Optional. When `true`: 301-redirects HTTP to HTTPS, sends HSTS, adds CSP `upgrade-insecure-requests`, forces the session cookie `secure` flag. Useful behind a TLS-terminating reverse proxy. Requires `TRUST_PROXY`. | `false` |
|
||||||
|
| `HSTS_INCLUDE_SUBDOMAINS` | When `true`: adds the `includeSubDomains` directive to the HSTS header, extending HTTPS enforcement to all subdomains. Only effective when HSTS is active (`FORCE_HTTPS=true` or `NODE_ENV=production`). Leave `false` if you run other services on sibling subdomains over plain HTTP. | `false` |
|
||||||
| `COOKIE_SECURE` | Controls the `secure` flag on the `trek_session` cookie. Auto-derived: on when `NODE_ENV=production` or `FORCE_HTTPS=true`. Escape hatch: set `false` to allow session cookies over plain HTTP. Not recommended in production. | auto |
|
| `COOKIE_SECURE` | Controls the `secure` flag on the `trek_session` cookie. Auto-derived: on when `NODE_ENV=production` or `FORCE_HTTPS=true`. Escape hatch: set `false` to allow session cookies over plain HTTP. Not recommended in production. | auto |
|
||||||
| `TRUST_PROXY` | Number of trusted reverse proxies. Tells Express to read client IP from `X-Forwarded-For` and protocol from `X-Forwarded-Proto`. Defaults to `1` in production; off in dev unless set. | `1` |
|
| `TRUST_PROXY` | Number of trusted reverse proxies. Tells Express to read client IP from `X-Forwarded-For` and protocol from `X-Forwarded-Proto`. Defaults to `1` in production; off in dev unless set. | `1` |
|
||||||
| `ALLOW_INTERNAL_NETWORK` | Allow outbound requests to private/RFC-1918 IPs (e.g. Immich on your LAN). Loopback and link-local addresses remain blocked. | `false` |
|
| `ALLOW_INTERNAL_NETWORK` | Allow outbound requests to private/RFC-1918 IPs (e.g. Immich on your LAN). Loopback and link-local addresses remain blocked. | `false` |
|
||||||
|
|||||||
+121
@@ -0,0 +1,121 @@
|
|||||||
|
# Trademark Policy
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
This is the TREK project's policy for the use of our trademarks. While TREK is
|
||||||
|
available under the GNU Affero General Public License v3.0 (AGPL-3.0), that
|
||||||
|
license does not include a license to use our trademarks.
|
||||||
|
|
||||||
|
This policy describes how you may use our trademarks. Our goal is to strike a
|
||||||
|
balance between: 1) our need to ensure that our trademarks remain reliable
|
||||||
|
indicators of the software we release; and 2) our community members' desire to
|
||||||
|
be full participants in the TREK project.
|
||||||
|
|
||||||
|
## Our trademarks
|
||||||
|
|
||||||
|
This policy covers the name "TREK" as well as any associated logos, trade dress,
|
||||||
|
goodwill, or designs (our "Marks").
|
||||||
|
|
||||||
|
## In general
|
||||||
|
|
||||||
|
Whenever you use our Marks, you must always do so in a way that does not mislead
|
||||||
|
anyone about exactly who is the source of the software. For example, you cannot
|
||||||
|
say you are distributing TREK when you're distributing a modified version of it,
|
||||||
|
because people would think they would be getting the same software that they
|
||||||
|
can get directly from us when they aren't. You also cannot use our Marks on
|
||||||
|
your website in a way that suggests that your website is an official TREK
|
||||||
|
website or that we endorse your website. But, if true, you can say you like
|
||||||
|
TREK, that you participate in the TREK community, that you are providing an
|
||||||
|
unmodified version of TREK, or that you wrote a guide describing how to use
|
||||||
|
TREK.
|
||||||
|
|
||||||
|
This fundamental requirement, that it is always clear to people what they are
|
||||||
|
getting and from whom, is reflected throughout this policy. It should also
|
||||||
|
serve as your guide if you are not sure about how you are using the Marks.
|
||||||
|
|
||||||
|
In addition:
|
||||||
|
|
||||||
|
* You may not use or register, in whole or in part, the Marks as part of your
|
||||||
|
own trademark, service mark, domain name, company name, trade name, product
|
||||||
|
name or service name.
|
||||||
|
* Trademark law does not allow your use of names or trademarks that are too
|
||||||
|
similar to ours. You therefore may not use an obvious variation of any of our
|
||||||
|
Marks or any phonetic equivalent, foreign language equivalent, takeoff, or
|
||||||
|
abbreviation for a similar or compatible product or service.
|
||||||
|
* You agree that you will not acquire any rights in the Marks and that any
|
||||||
|
goodwill generated by your use of the Marks and participation in our
|
||||||
|
community inures solely to our benefit.
|
||||||
|
|
||||||
|
## Distribution of unmodified source code or unmodified executable code we have compiled
|
||||||
|
|
||||||
|
When you redistribute an unmodified copy of TREK, you are not changing the
|
||||||
|
quality or nature of it. Therefore, you may retain the Marks we have placed on
|
||||||
|
the software to identify your redistribution. This kind of use only applies if
|
||||||
|
you are redistributing an official TREK distribution that has not been changed
|
||||||
|
in any way.
|
||||||
|
|
||||||
|
## Distribution of executable code that you have compiled, or modified code
|
||||||
|
|
||||||
|
You may use the word mark "TREK", but not any TREK logos, to truthfully
|
||||||
|
describe the origin of the software that you are providing, that is, that the
|
||||||
|
code you are distributing is a modification of TREK. You may say, for example,
|
||||||
|
that "this software is derived from the source code for TREK."
|
||||||
|
|
||||||
|
Of course, you can place your own trademarks or logos on versions of the
|
||||||
|
software to which you have made substantive modifications, because by modifying
|
||||||
|
the software, you have become the origin of that exact version. In that case,
|
||||||
|
you should not use our Marks.
|
||||||
|
|
||||||
|
However, you may use our Marks for the distribution of code (source or
|
||||||
|
executable) on the condition that any executable is built from an official TREK
|
||||||
|
source code release and that any modifications are limited to switching on or
|
||||||
|
off features already included in the software, translations into other
|
||||||
|
languages, and incorporating minor bug-fix patches. Use of our Marks on any
|
||||||
|
further modification is not permitted.
|
||||||
|
|
||||||
|
## Mobile wrappers, hosted instances, and forks
|
||||||
|
|
||||||
|
The following clarifications apply specifically to common ways TREK is
|
||||||
|
redistributed:
|
||||||
|
|
||||||
|
* **Self-hosted instances of unmodified TREK.** You may refer to your instance
|
||||||
|
as "a TREK instance" or "running TREK." You may not name the service itself
|
||||||
|
in a way that suggests it is the official TREK ("TREK Cloud," "TREK
|
||||||
|
Official," etc.).
|
||||||
|
* **Mobile wrappers (WebView shells, Capacitor apps, native apps) pointing at
|
||||||
|
TREK.** You may describe your app as "a mobile client for TREK" or "for use
|
||||||
|
with TREK." You may not publish it on app stores under the name "TREK" or a
|
||||||
|
confusingly similar name, and you may not use the TREK logo as the app icon
|
||||||
|
unless your wrapper distributes only an unmodified, official TREK instance
|
||||||
|
and you have obtained permission.
|
||||||
|
* **Forks of the TREK source code.** Forks that diverge from upstream must use
|
||||||
|
a different name. You may state that your fork is "based on TREK" or "a fork
|
||||||
|
of TREK," but the project name itself must be your own.
|
||||||
|
|
||||||
|
## Statements about your software's relation to TREK
|
||||||
|
|
||||||
|
You may use the word mark, but not TREK logos, to truthfully describe the
|
||||||
|
relationship between your software and ours. The word mark "TREK" should be
|
||||||
|
used after a verb or preposition that describes the relationship between your
|
||||||
|
software and ours. So you may say, for example, "Bob's app for TREK" but may
|
||||||
|
not say "Bob's TREK app." Some other examples that may work for you are:
|
||||||
|
|
||||||
|
* [Your software] uses TREK
|
||||||
|
* [Your software] is powered by TREK
|
||||||
|
* [Your software] runs on TREK
|
||||||
|
* [Your software] for use with TREK
|
||||||
|
* [Your software] for TREK
|
||||||
|
|
||||||
|
## Questions and permission requests
|
||||||
|
|
||||||
|
If you are not sure whether your intended use of the Marks is permitted under
|
||||||
|
this policy, or if you would like to request explicit permission for a use that
|
||||||
|
is not covered, please open an issue on the TREK GitHub repository or contact
|
||||||
|
the maintainers directly.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
These guidelines are based on the
|
||||||
|
[Model Trademark Guidelines](http://www.modeltrademarkguidelines.org), used
|
||||||
|
under a
|
||||||
|
[Creative Commons Attribution 3.0 Unported license](https://creativecommons.org/licenses/by/3.0/deed.en_US).
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
apiVersion: v2
|
apiVersion: v2
|
||||||
name: trek
|
name: trek
|
||||||
version: 2.9.14
|
version: 3.0.9
|
||||||
description: Minimal Helm chart for TREK app
|
description: Minimal Helm chart for TREK app
|
||||||
appVersion: "2.9.14"
|
appVersion: "3.0.9"
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ data:
|
|||||||
{{- if .Values.env.FORCE_HTTPS }}
|
{{- if .Values.env.FORCE_HTTPS }}
|
||||||
FORCE_HTTPS: {{ .Values.env.FORCE_HTTPS | quote }}
|
FORCE_HTTPS: {{ .Values.env.FORCE_HTTPS | quote }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
{{- if .Values.env.HSTS_INCLUDE_SUBDOMAINS }}
|
||||||
|
HSTS_INCLUDE_SUBDOMAINS: {{ .Values.env.HSTS_INCLUDE_SUBDOMAINS | quote }}
|
||||||
|
{{- end }}
|
||||||
{{- if .Values.env.COOKIE_SECURE }}
|
{{- if .Values.env.COOKIE_SECURE }}
|
||||||
COOKIE_SECURE: {{ .Values.env.COOKIE_SECURE | quote }}
|
COOKIE_SECURE: {{ .Values.env.COOKIE_SECURE | quote }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ env:
|
|||||||
# Also used as the base URL for links in email notifications and other external links.
|
# Also used as the base URL for links in email notifications and other external links.
|
||||||
# FORCE_HTTPS: "false"
|
# FORCE_HTTPS: "false"
|
||||||
# Optional. When "true": HTTPS redirect, HSTS, CSP upgrade-insecure-requests, secure cookies. Only behind a TLS proxy. Requires TRUST_PROXY.
|
# Optional. When "true": HTTPS redirect, HSTS, CSP upgrade-insecure-requests, secure cookies. Only behind a TLS proxy. Requires TRUST_PROXY.
|
||||||
|
# HSTS_INCLUDE_SUBDOMAINS: "false"
|
||||||
|
# When "true": adds includeSubDomains to the HSTS header. Only effective when HSTS is active. Leave "false" if sibling subdomains still run over plain HTTP.
|
||||||
# COOKIE_SECURE: "true"
|
# COOKIE_SECURE: "true"
|
||||||
# Auto-derived (true in production or when FORCE_HTTPS=true). Set "false" to force cookies over plain HTTP. Not recommended for production.
|
# Auto-derived (true in production or when FORCE_HTTPS=true). Set "false" to force cookies over plain HTTP. Not recommended for production.
|
||||||
# TRUST_PROXY: "1"
|
# TRUST_PROXY: "1"
|
||||||
|
|||||||
Generated
+5
-5
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "trek-client",
|
"name": "trek-client",
|
||||||
"version": "2.9.14",
|
"version": "3.0.9",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "trek-client",
|
"name": "trek-client",
|
||||||
"version": "2.9.14",
|
"version": "3.0.9",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@react-pdf/renderer": "^4.3.2",
|
"@react-pdf/renderer": "^4.3.2",
|
||||||
"axios": "^1.6.7",
|
"axios": "^1.6.7",
|
||||||
@@ -8907,9 +8907,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.5.9",
|
"version": "8.5.10",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.9.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz",
|
||||||
"integrity": "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==",
|
"integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "trek-client",
|
"name": "trek-client",
|
||||||
"version": "2.9.14",
|
"version": "3.0.9",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -634,7 +634,7 @@ export default function BudgetPanel({ tripId, tripMembers = [] }: BudgetPanelPro
|
|||||||
}
|
}
|
||||||
const handleRenameCategory = async (oldName, newName) => {
|
const handleRenameCategory = async (oldName, newName) => {
|
||||||
if (!newName.trim() || newName.trim() === oldName) return
|
if (!newName.trim() || newName.trim() === oldName) return
|
||||||
const items = grouped[oldName] || []
|
const items = grouped.get(oldName) || []
|
||||||
for (const item of Array.from(items)) await updateBudgetItem(tripId, item.id, { category: newName.trim() })
|
for (const item of Array.from(items)) await updateBudgetItem(tripId, item.id, { category: newName.trim() })
|
||||||
}
|
}
|
||||||
const handleAddCategory = () => {
|
const handleAddCategory = () => {
|
||||||
|
|||||||
@@ -266,17 +266,22 @@ export default function DemoBanner(): React.ReactElement | null {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{
|
<div style={{
|
||||||
position: 'fixed', inset: 0, zIndex: 9999,
|
position: 'fixed', inset: 0, zIndex: 99999,
|
||||||
background: 'rgba(0,0,0,0.6)', backdropFilter: 'blur(8px)',
|
background: 'rgba(0,0,0,0.6)', backdropFilter: 'blur(8px)',
|
||||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||||
padding: 16, overflow: 'auto',
|
paddingTop: 'max(16px, env(safe-area-inset-top))',
|
||||||
|
paddingBottom: 'max(16px, calc(env(safe-area-inset-bottom) + 80px))',
|
||||||
|
paddingLeft: 16, paddingRight: 16,
|
||||||
|
overflow: 'auto',
|
||||||
fontFamily: "-apple-system, BlinkMacSystemFont, 'SF Pro Text', system-ui, sans-serif",
|
fontFamily: "-apple-system, BlinkMacSystemFont, 'SF Pro Text', system-ui, sans-serif",
|
||||||
}} onClick={() => setDismissed(true)}>
|
}} onClick={() => setDismissed(true)}>
|
||||||
<div style={{
|
<div style={{
|
||||||
background: 'white', borderRadius: 20, padding: '28px 24px 20px',
|
background: 'white', borderRadius: 20, padding: '28px 24px 0',
|
||||||
maxWidth: 480, width: '100%',
|
maxWidth: 480, width: '100%',
|
||||||
boxShadow: '0 20px 60px rgba(0,0,0,0.3)',
|
boxShadow: '0 20px 60px rgba(0,0,0,0.3)',
|
||||||
maxHeight: '90vh', overflow: 'auto',
|
maxHeight: 'min(90vh, calc(100dvh - 96px))',
|
||||||
|
overflow: 'auto',
|
||||||
|
display: 'flex', flexDirection: 'column',
|
||||||
}} onClick={(e: React.MouseEvent<HTMLDivElement>) => e.stopPropagation()}>
|
}} onClick={(e: React.MouseEvent<HTMLDivElement>) => e.stopPropagation()}>
|
||||||
|
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
@@ -367,8 +372,10 @@ export default function DemoBanner(): React.ReactElement | null {
|
|||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<div style={{
|
<div style={{
|
||||||
paddingTop: 14, borderTop: '1px solid #e5e7eb',
|
padding: '14px 0 20px', borderTop: '1px solid #e5e7eb',
|
||||||
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
|
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
|
||||||
|
position: 'sticky', bottom: 0, background: 'white',
|
||||||
|
marginTop: 'auto',
|
||||||
}}>
|
}}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 11, color: '#9ca3af' }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 11, color: '#9ca3af' }}>
|
||||||
<Github size={13} />
|
<Github size={13} />
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ const transportReservation = {
|
|||||||
id: 400,
|
id: 400,
|
||||||
title: 'Flight to Rome',
|
title: 'Flight to Rome',
|
||||||
type: 'flight',
|
type: 'flight',
|
||||||
|
day_id: 10,
|
||||||
reservation_time: '2025-06-01T14:30:00',
|
reservation_time: '2025-06-01T14:30:00',
|
||||||
confirmation_number: 'ABC123',
|
confirmation_number: 'ABC123',
|
||||||
metadata: JSON.stringify({ airline: 'Air Italia', flight_number: 'AI123', departure_airport: 'CDG', arrival_airport: 'FCO' }),
|
metadata: JSON.stringify({ airline: 'Air Italia', flight_number: 'AI123', departure_airport: 'CDG', arrival_airport: 'FCO' }),
|
||||||
|
|||||||
@@ -140,23 +140,58 @@ export async function downloadTripPDF({ trip, days, places, assignments, categor
|
|||||||
const totalCost = Object.values(assignments || {})
|
const totalCost = Object.values(assignments || {})
|
||||||
.flatMap(a => a).reduce((s, a) => s + (parseFloat(a.place?.price) || 0), 0)
|
.flatMap(a => a).reduce((s, a) => s + (parseFloat(a.place?.price) || 0), 0)
|
||||||
|
|
||||||
|
// Span helpers for multi-day transport (mirrors DayPlanSidebar logic)
|
||||||
|
const pdfGetDayOrder = (d: Day) => d.day_number
|
||||||
|
const pdfGetSpanPhase = (r: any, dayId: number): 'single' | 'start' | 'middle' | 'end' => {
|
||||||
|
const startId = r.day_id
|
||||||
|
const endId = r.end_day_id ?? startId
|
||||||
|
if (!startId || startId === endId) return 'single'
|
||||||
|
if (dayId === startId) return 'start'
|
||||||
|
if (dayId === endId) return 'end'
|
||||||
|
return 'middle'
|
||||||
|
}
|
||||||
|
const pdfGetDisplayTime = (r: any, dayId: number): string | null => {
|
||||||
|
const phase = pdfGetSpanPhase(r, dayId)
|
||||||
|
if (phase === 'end') return r.reservation_end_time || null
|
||||||
|
if (phase === 'middle') return null
|
||||||
|
return r.reservation_time || null
|
||||||
|
}
|
||||||
|
const pdfGetSpanLabel = (r: any, phase: string): string | null => {
|
||||||
|
if (phase === 'single') return null
|
||||||
|
if (r.type === 'flight') return tr(`reservations.span.${phase === 'start' ? 'departure' : phase === 'end' ? 'arrival' : 'inTransit'}`)
|
||||||
|
if (r.type === 'car') return tr(`reservations.span.${phase === 'start' ? 'pickup' : phase === 'end' ? 'return' : 'active'}`)
|
||||||
|
return tr(`reservations.span.${phase === 'start' ? 'start' : phase === 'end' ? 'end' : 'ongoing'}`)
|
||||||
|
}
|
||||||
|
const pdfGetTransportForDay = (dayId: number) => (reservations || []).filter(r => {
|
||||||
|
if (r.type === 'hotel') return false
|
||||||
|
const startId = r.day_id
|
||||||
|
const endId = r.end_day_id ?? startId
|
||||||
|
if (startId == null) return false
|
||||||
|
if (endId !== startId) {
|
||||||
|
const startDay = sorted.find(d => d.id === startId)
|
||||||
|
const endDay = sorted.find(d => d.id === endId)
|
||||||
|
const thisDay = sorted.find(d => d.id === dayId)
|
||||||
|
if (!startDay || !endDay || !thisDay) return false
|
||||||
|
return pdfGetDayOrder(thisDay) >= pdfGetDayOrder(startDay) && pdfGetDayOrder(thisDay) <= pdfGetDayOrder(endDay)
|
||||||
|
}
|
||||||
|
return startId === dayId
|
||||||
|
})
|
||||||
|
|
||||||
// Build day HTML
|
// Build day HTML
|
||||||
const daysHtml = sorted.map((day, di) => {
|
const daysHtml = sorted.map((day, di) => {
|
||||||
const assigned = assignments[String(day.id)] || []
|
const assigned = assignments[String(day.id)] || []
|
||||||
const notes = (dayNotes || []).filter(n => n.day_id === day.id)
|
const notes = (dayNotes || []).filter(n => n.day_id === day.id)
|
||||||
const cost = dayCost(assignments, day.id, loc)
|
const cost = dayCost(assignments, day.id, loc)
|
||||||
|
|
||||||
// Reservations for this day (hotel rendered via accommodations block)
|
// Reservations for this day (hotel rendered via accommodations block; car middle-phase rendered in sidebar header only)
|
||||||
const dayReservations = (reservations || []).filter(r => {
|
const dayReservations = pdfGetTransportForDay(day.id)
|
||||||
if (!r.reservation_time || r.type === 'hotel') return false
|
.filter(r => !(r.type === 'car' && pdfGetSpanPhase(r, day.id) === 'middle'))
|
||||||
return day.date && r.reservation_time.split('T')[0] === day.date
|
|
||||||
})
|
|
||||||
|
|
||||||
const merged = []
|
const merged = []
|
||||||
assigned.forEach(a => merged.push({ type: 'place', k: a.order_index ?? a.sort_order ?? 0, data: a }))
|
assigned.forEach(a => merged.push({ type: 'place', k: a.order_index ?? a.sort_order ?? 0, data: a }))
|
||||||
notes.forEach(n => merged.push({ type: 'note', k: n.sort_order ?? 0, data: n }))
|
notes.forEach(n => merged.push({ type: 'note', k: n.sort_order ?? 0, data: n }))
|
||||||
dayReservations.forEach(r => {
|
dayReservations.forEach(r => {
|
||||||
const pos = r.day_plan_position ?? (merged.length > 0 ? Math.max(...merged.map(m => m.k)) + 0.5 : 0.5)
|
const pos = r.day_positions?.[day.id] ?? r.day_positions?.[String(day.id)] ?? r.day_plan_position ?? (merged.length > 0 ? Math.max(...merged.map(m => m.k)) + 0.5 : 0.5)
|
||||||
merged.push({ type: 'reservation', k: pos, data: r })
|
merged.push({ type: 'reservation', k: pos, data: r })
|
||||||
})
|
})
|
||||||
merged.sort((a, b) => a.k - b.k)
|
merged.sort((a, b) => a.k - b.k)
|
||||||
@@ -177,13 +212,17 @@ export async function downloadTripPDF({ trip, days, places, assignments, categor
|
|||||||
else if (r.type === 'event') subtitle = [meta.venue].filter(Boolean).join(' · ')
|
else if (r.type === 'event') subtitle = [meta.venue].filter(Boolean).join(' · ')
|
||||||
else if (r.type === 'tour') subtitle = [meta.operator].filter(Boolean).join(' · ')
|
else if (r.type === 'tour') subtitle = [meta.operator].filter(Boolean).join(' · ')
|
||||||
const locationLine = r.location || meta.location || ''
|
const locationLine = r.location || meta.location || ''
|
||||||
const time = r.reservation_time?.includes('T') ? r.reservation_time.split('T')[1]?.substring(0, 5) : ''
|
const phase = pdfGetSpanPhase(r, day.id)
|
||||||
|
const spanLabel = pdfGetSpanLabel(r, phase)
|
||||||
|
const displayTime = pdfGetDisplayTime(r, day.id)
|
||||||
|
const time = displayTime?.includes('T') ? displayTime.split('T')[1]?.substring(0, 5) : ''
|
||||||
|
const titleHtml = `${spanLabel ? escHtml(spanLabel) + ': ' : ''}${escHtml(r.title)}`
|
||||||
return `
|
return `
|
||||||
<div class="note-card" style="border-left: 3px solid ${color};">
|
<div class="note-card" style="border-left: 3px solid ${color};">
|
||||||
<div class="note-line" style="background: ${color};"></div>
|
<div class="note-line" style="background: ${color};"></div>
|
||||||
<span class="note-icon">${icon}</span>
|
<span class="note-icon">${icon}</span>
|
||||||
<div class="note-body">
|
<div class="note-body">
|
||||||
<div class="note-text" style="font-weight: 600;">${escHtml(r.title)}${time ? ` <span style="color:#6b7280;font-weight:400;font-size:10px;">${time}</span>` : ''}</div>
|
<div class="note-text" style="font-weight: 600;">${titleHtml}${time ? ` <span style="color:#6b7280;font-weight:400;font-size:10px;">${time}</span>` : ''}</div>
|
||||||
${subtitle ? `<div class="note-time">${escHtml(subtitle)}</div>` : ''}
|
${subtitle ? `<div class="note-time">${escHtml(subtitle)}</div>` : ''}
|
||||||
${locationLine ? `<div class="note-time">${escHtml(locationLine)}</div>` : ''}
|
${locationLine ? `<div class="note-time">${escHtml(locationLine)}</div>` : ''}
|
||||||
${r.confirmation_number ? `<div class="note-time" style="font-size:9px;">Code: ${escHtml(r.confirmation_number)}</div>` : ''}
|
${r.confirmation_number ? `<div class="note-time" style="font-size:9px;">Code: ${escHtml(r.confirmation_number)}</div>` : ''}
|
||||||
|
|||||||
@@ -66,7 +66,11 @@ export default function DayDetailPanel({ day, days, places, categories = [], tri
|
|||||||
const isFahrenheit = useSettingsStore(s => s.settings.temperature_unit) === 'fahrenheit'
|
const isFahrenheit = useSettingsStore(s => s.settings.temperature_unit) === 'fahrenheit'
|
||||||
const is12h = useSettingsStore(s => s.settings.time_format) === '12h'
|
const is12h = useSettingsStore(s => s.settings.time_format) === '12h'
|
||||||
const blurCodes = useSettingsStore(s => s.settings.blur_booking_codes)
|
const blurCodes = useSettingsStore(s => s.settings.blur_booking_codes)
|
||||||
const fmtTime = (v) => formatTime12(v, is12h)
|
const fmtTime = (v) => {
|
||||||
|
if (!v) return v
|
||||||
|
if (v.includes('T')) return new Date(v).toLocaleTimeString(locale, { hour: '2-digit', minute: '2-digit', hour12: is12h })
|
||||||
|
return formatTime12(v, is12h)
|
||||||
|
}
|
||||||
const unit = isFahrenheit ? '°F' : '°C'
|
const unit = isFahrenheit ? '°F' : '°C'
|
||||||
const collapsed = collapsedProp
|
const collapsed = collapsedProp
|
||||||
const toggleCollapse = () => onToggleCollapse?.()
|
const toggleCollapse = () => onToggleCollapse?.()
|
||||||
|
|||||||
@@ -1576,7 +1576,10 @@ const DayPlanSidebar = React.memo(function DayPlanSidebar({
|
|||||||
{res.reservation_time?.includes('T') && (
|
{res.reservation_time?.includes('T') && (
|
||||||
<span style={{ fontWeight: 400 }}>
|
<span style={{ fontWeight: 400 }}>
|
||||||
{new Date(res.reservation_time).toLocaleTimeString(locale, { hour: '2-digit', minute: '2-digit', hour12: timeFormat === '12h' })}
|
{new Date(res.reservation_time).toLocaleTimeString(locale, { hour: '2-digit', minute: '2-digit', hour12: timeFormat === '12h' })}
|
||||||
{res.reservation_end_time && ` – ${res.reservation_end_time}`}
|
{res.reservation_end_time && ` – ${(() => {
|
||||||
|
const endStr = res.reservation_end_time.includes('T') ? res.reservation_end_time : (res.reservation_time.split('T')[0] + 'T' + res.reservation_end_time)
|
||||||
|
return new Date(endStr).toLocaleTimeString(locale, { hour: '2-digit', minute: '2-digit', hour12: timeFormat === '12h' })
|
||||||
|
})()}`}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{(() => {
|
{(() => {
|
||||||
|
|||||||
@@ -182,6 +182,8 @@ export function ReservationModal({ isOpen, onClose, onSave, reservation, days, p
|
|||||||
let combinedEndTime = form.reservation_end_time
|
let combinedEndTime = form.reservation_end_time
|
||||||
if (form.end_date) {
|
if (form.end_date) {
|
||||||
combinedEndTime = form.reservation_end_time ? `${form.end_date}T${form.reservation_end_time}` : form.end_date
|
combinedEndTime = form.reservation_end_time ? `${form.end_date}T${form.reservation_end_time}` : form.end_date
|
||||||
|
} else if (form.reservation_end_time && form.reservation_time) {
|
||||||
|
combinedEndTime = `${form.reservation_time.split('T')[0]}T${form.reservation_end_time}`
|
||||||
}
|
}
|
||||||
if (isBudgetEnabled) {
|
if (isBudgetEnabled) {
|
||||||
if (form.price) metadata.price = form.price
|
if (form.price) metadata.price = form.price
|
||||||
|
|||||||
@@ -236,7 +236,16 @@ function ReservationCard({ r, tripId, onEdit, onDelete, files = [], onNavigateTo
|
|||||||
<div style={fieldLabelStyle}>{t('reservations.date')}</div>
|
<div style={fieldLabelStyle}>{t('reservations.date')}</div>
|
||||||
<div style={{ ...fieldValueStyle, textAlign: 'center' }}>
|
<div style={{ ...fieldValueStyle, textAlign: 'center' }}>
|
||||||
{fmtDate(r.reservation_time)}
|
{fmtDate(r.reservation_time)}
|
||||||
{r.reservation_end_time && (r.reservation_end_time.includes('T') ? r.reservation_end_time.split('T')[0] : r.reservation_end_time) !== r.reservation_time.split('T')[0] && (
|
{(() => {
|
||||||
|
const endDatePart = r.reservation_end_time
|
||||||
|
? r.reservation_end_time.includes('T')
|
||||||
|
? r.reservation_end_time.split('T')[0]
|
||||||
|
: /^\d{4}-\d{2}-\d{2}$/.test(r.reservation_end_time)
|
||||||
|
? r.reservation_end_time
|
||||||
|
: null
|
||||||
|
: null
|
||||||
|
return endDatePart && endDatePart !== r.reservation_time.split('T')[0]
|
||||||
|
})() && (
|
||||||
<> – {fmtDate(r.reservation_end_time)}</>
|
<> – {fmtDate(r.reservation_end_time)}</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export default function ConfirmDialog({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="fixed inset-0 z-[10000] flex items-center justify-center px-4 trek-backdrop-enter"
|
className="fixed inset-0 z-[10000] flex items-center justify-center px-4 trek-backdrop-enter"
|
||||||
style={{ backgroundColor: 'rgba(15, 23, 42, 0.5)' }}
|
style={{ backgroundColor: 'rgba(15, 23, 42, 0.5)', paddingBottom: 'var(--bottom-nav-h)' }}
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export default function CopyTripDialog({ isOpen, tripTitle, onClose, onConfirm }
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="fixed inset-0 z-[10000] flex items-center justify-center px-4 trek-backdrop-enter"
|
className="fixed inset-0 z-[10000] flex items-center justify-center px-4 trek-backdrop-enter"
|
||||||
style={{ backgroundColor: 'rgba(15, 23, 42, 0.5)' }}
|
style={{ backgroundColor: 'rgba(15, 23, 42, 0.5)', paddingBottom: 'var(--bottom-nav-h)' }}
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -343,7 +343,10 @@ export default function TripPlannerPage(): React.ReactElement | null {
|
|||||||
}, [tripId])
|
}, [tripId])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (tripId) tripActions.loadReservations(tripId)
|
if (tripId) {
|
||||||
|
tripActions.loadReservations(tripId)
|
||||||
|
tripActions.loadBudgetItems?.(tripId)
|
||||||
|
}
|
||||||
}, [tripId])
|
}, [tripId])
|
||||||
|
|
||||||
useTripWebSocket(tripId)
|
useTripWebSocket(tripId)
|
||||||
@@ -1106,7 +1109,7 @@ export default function TripPlannerPage(): React.ReactElement | null {
|
|||||||
</div>
|
</div>
|
||||||
<div style={{ flex: 1, overflow: 'auto' }}>
|
<div style={{ flex: 1, overflow: 'auto' }}>
|
||||||
{mobileSidebarOpen === 'left'
|
{mobileSidebarOpen === 'left'
|
||||||
? <DayPlanSidebar tripId={tripId} trip={trip} days={days} places={places} categories={categories} assignments={assignments} selectedDayId={selectedDayId} selectedPlaceId={selectedPlaceId} selectedAssignmentId={selectedAssignmentId} onSelectDay={(id) => { handleSelectDay(id); setMobileSidebarOpen(null) }} onPlaceClick={(placeId, assignmentId) => { handlePlaceClick(placeId, assignmentId); setMobileSidebarOpen(null) }} onReorder={handleReorder} onUpdateDayTitle={handleUpdateDayTitle} onAssignToDay={handleAssignToDay} onRouteCalculated={(r) => { if (r) { setRoute(r.coordinates); setRouteInfo({ distance: r.distanceText, duration: r.durationText }) } }} reservations={reservations} onAddReservation={(dayId) => { setEditingReservation(null); tripActions.setSelectedDay(dayId); setShowReservationModal(true); setMobileSidebarOpen(null) }} onAddPlace={() => { setEditingPlace(null); setShowPlaceForm(true); setMobileSidebarOpen(null) }} onDayDetail={(day) => { setShowDayDetail(day); setSelectedPlaceId(null); selectAssignment(null); setMobileSidebarOpen(null) }} accommodations={tripAccommodations} onNavigateToFiles={() => { setMobileSidebarOpen(null); handleTabChange('dateien') }} onExpandedDaysChange={setExpandedDayIds} pushUndo={pushUndo} canUndo={canUndo} lastActionLabel={lastActionLabel} onUndo={handleUndo} onEditTransport={can('day_edit', trip) ? (reservation) => { setEditingTransport(reservation); setTransportModalDayId(reservation.day_id ?? null); setShowTransportModal(true); setMobileSidebarOpen(null) } : undefined} onEditReservation={can('reservation_edit', trip) ? (r) => { setEditingReservation(r); setShowReservationModal(true); setMobileSidebarOpen(null) } : undefined} />
|
? <DayPlanSidebar tripId={tripId} trip={trip} days={days} places={places} categories={categories} assignments={assignments} selectedDayId={selectedDayId} selectedPlaceId={selectedPlaceId} selectedAssignmentId={selectedAssignmentId} onSelectDay={(id) => { handleSelectDay(id); setMobileSidebarOpen(null) }} onPlaceClick={(placeId, assignmentId) => { handlePlaceClick(placeId, assignmentId); setMobileSidebarOpen(null) }} onReorder={handleReorder} onUpdateDayTitle={handleUpdateDayTitle} onAssignToDay={handleAssignToDay} onRouteCalculated={(r) => { if (r) { setRoute(r.coordinates); setRouteInfo({ distance: r.distanceText, duration: r.durationText }) } }} reservations={reservations} visibleConnectionIds={visibleConnections} onToggleConnection={toggleConnection} onAddReservation={(dayId) => { setEditingReservation(null); tripActions.setSelectedDay(dayId); setShowReservationModal(true); setMobileSidebarOpen(null) }} onAddPlace={() => { setEditingPlace(null); setShowPlaceForm(true); setMobileSidebarOpen(null) }} onDayDetail={(day) => { setShowDayDetail(day); setSelectedPlaceId(null); selectAssignment(null); setMobileSidebarOpen(null) }} accommodations={tripAccommodations} onNavigateToFiles={() => { setMobileSidebarOpen(null); handleTabChange('dateien') }} onExpandedDaysChange={setExpandedDayIds} pushUndo={pushUndo} canUndo={canUndo} lastActionLabel={lastActionLabel} onUndo={handleUndo} onEditTransport={can('day_edit', trip) ? (reservation) => { setEditingTransport(reservation); setTransportModalDayId(reservation.day_id ?? null); setShowTransportModal(true); setMobileSidebarOpen(null) } : undefined} onEditReservation={can('reservation_edit', trip) ? (r) => { setEditingReservation(r); setShowReservationModal(true); setMobileSidebarOpen(null) } : undefined} />
|
||||||
: <PlacesSidebar tripId={tripId} places={places} categories={categories} assignments={assignments} selectedDayId={selectedDayId} selectedPlaceId={selectedPlaceId} onPlaceClick={(placeId) => { handlePlaceClick(placeId); setMobileSidebarOpen(null) }} onAddPlace={() => { setEditingPlace(null); setShowPlaceForm(true); setMobileSidebarOpen(null) }} onAssignToDay={handleAssignToDay} onEditPlace={(place) => { setEditingPlace(place); setEditingAssignmentId(null); setShowPlaceForm(true); setMobileSidebarOpen(null) }} onDeletePlace={(placeId) => handleDeletePlace(placeId)} onBulkDeletePlaces={(ids) => setDeletePlaceIds(ids)} onBulkDeleteConfirm={(ids) => confirmDeletePlaces(ids)} days={days} isMobile onCategoryFilterChange={setMapCategoryFilter} onPlacesFilterChange={setMapPlacesFilter} pushUndo={pushUndo} />
|
: <PlacesSidebar tripId={tripId} places={places} categories={categories} assignments={assignments} selectedDayId={selectedDayId} selectedPlaceId={selectedPlaceId} onPlaceClick={(placeId) => { handlePlaceClick(placeId); setMobileSidebarOpen(null) }} onAddPlace={() => { setEditingPlace(null); setShowPlaceForm(true); setMobileSidebarOpen(null) }} onAssignToDay={handleAssignToDay} onEditPlace={(place) => { setEditingPlace(place); setEditingAssignmentId(null); setShowPlaceForm(true); setMobileSidebarOpen(null) }} onDeletePlace={(placeId) => handleDeletePlace(placeId)} onBulkDeletePlaces={(ids) => setDeletePlaceIds(ids)} onBulkDeleteConfirm={(ids) => confirmDeletePlaces(ids)} days={days} isMobile onCategoryFilterChange={setMapCategoryFilter} onPlacesFilterChange={setMapPlacesFilter} pushUndo={pushUndo} />
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -355,6 +355,37 @@ describe('journeyStore', () => {
|
|||||||
expect(useJourneyStore.getState().loading).toBe(false);
|
expect(useJourneyStore.getState().loading).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ── reorderEntries ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
it('FE-STORE-JOURNEY-018: reorderEntries reorders by sort_order not entry_time', async () => {
|
||||||
|
const a = buildEntry({ id: 201, entry_date: '2026-04-01', entry_time: '09:00', sort_order: 0 });
|
||||||
|
const b = buildEntry({ id: 202, entry_date: '2026-04-01', entry_time: '11:00', sort_order: 1 });
|
||||||
|
const c = buildEntry({ id: 203, entry_date: '2026-04-01', entry_time: '14:00', sort_order: 2 });
|
||||||
|
const detail = buildJourneyDetail({ id: 55, entries: [a, b, c] });
|
||||||
|
useJourneyStore.setState({ current: detail });
|
||||||
|
|
||||||
|
server.use(
|
||||||
|
http.put('/api/journeys/55/entries/reorder', () => HttpResponse.json({ success: true }))
|
||||||
|
);
|
||||||
|
await useJourneyStore.getState().reorderEntries(55, [202, 201, 203]);
|
||||||
|
const ids = useJourneyStore.getState().current?.entries.map(e => e.id);
|
||||||
|
expect(ids).toEqual([202, 201, 203]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('FE-STORE-JOURNEY-019: reorderEntries rolls back on API failure', async () => {
|
||||||
|
const a = buildEntry({ id: 211, entry_date: '2026-04-01', sort_order: 0 });
|
||||||
|
const b = buildEntry({ id: 212, entry_date: '2026-04-01', sort_order: 1 });
|
||||||
|
const detail = buildJourneyDetail({ id: 56, entries: [a, b] });
|
||||||
|
useJourneyStore.setState({ current: detail });
|
||||||
|
|
||||||
|
server.use(
|
||||||
|
http.put('/api/journeys/56/entries/reorder', () => HttpResponse.json({}, { status: 403 }))
|
||||||
|
);
|
||||||
|
await expect(useJourneyStore.getState().reorderEntries(56, [212, 211])).rejects.toBeTruthy();
|
||||||
|
const ids = useJourneyStore.getState().current?.entries.map(e => e.id);
|
||||||
|
expect(ids).toEqual([211, 212]);
|
||||||
|
});
|
||||||
|
|
||||||
// ── clear ────────────────────────────────────────────────────────────────
|
// ── clear ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
it('FE-STORE-JOURNEY-015: clear resets state', () => {
|
it('FE-STORE-JOURNEY-015: clear resets state', () => {
|
||||||
|
|||||||
@@ -223,10 +223,8 @@ export const useJourneyStore = create<JourneyState>((set, get) => ({
|
|||||||
)
|
)
|
||||||
entries.sort((a, b) => {
|
entries.sort((a, b) => {
|
||||||
if (a.entry_date !== b.entry_date) return a.entry_date.localeCompare(b.entry_date)
|
if (a.entry_date !== b.entry_date) return a.entry_date.localeCompare(b.entry_date)
|
||||||
const atime = a.entry_time || ''
|
if (a.sort_order !== b.sort_order) return (a.sort_order || 0) - (b.sort_order || 0)
|
||||||
const btime = b.entry_time || ''
|
return a.id - b.id
|
||||||
if (atime !== btime) return atime.localeCompare(btime)
|
|
||||||
return (a.sort_order || 0) - (b.sort_order || 0)
|
|
||||||
})
|
})
|
||||||
return { current: { ...s.current, entries } }
|
return { current: { ...s.current, entries } }
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -32,6 +32,13 @@ function triggerAnchorDownload(blobUrl: string, filename?: string): void {
|
|||||||
setTimeout(() => { URL.revokeObjectURL(blobUrl); a.remove() }, 100)
|
setTimeout(() => { URL.revokeObjectURL(blobUrl); a.remove() }, 100)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// navigator.standalone is true only on iOS when running as an
|
||||||
|
// add-to-home-screen PWA. In that context, target="_blank" hands off to
|
||||||
|
// Safari, which cannot access blob URLs sandboxed to the WebView.
|
||||||
|
function isIosStandalone(): boolean {
|
||||||
|
return (navigator as any).standalone === true
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches a protected file using cookie auth (credentials: include) and
|
* Fetches a protected file using cookie auth (credentials: include) and
|
||||||
* triggers a browser download. Works inside PWA standalone mode because the
|
* triggers a browser download. Works inside PWA standalone mode because the
|
||||||
@@ -56,7 +63,13 @@ export async function downloadFile(url: string, filename?: string): Promise<void
|
|||||||
* (including text/html and image/svg+xml which can execute script) are forced
|
* (including text/html and image/svg+xml which can execute script) are forced
|
||||||
* to download so that an uploaded file cannot run code in the TREK origin.
|
* to download so that an uploaded file cannot run code in the TREK origin.
|
||||||
*
|
*
|
||||||
* Falls back to a download trigger if the popup is blocked.
|
* Uses a synthetic <a target="_blank" rel="noopener noreferrer"> click rather
|
||||||
|
* than window.open(). window.open() called with the "noreferrer"/"noopener"
|
||||||
|
* window feature returns null per spec, which previously made the popup-block
|
||||||
|
* fallback trigger a download in the *current* tab on top of the new-tab open
|
||||||
|
* — i.e. the file opened twice. The anchor approach avoids that ambiguity:
|
||||||
|
* the new tab is opened by the browser's normal link-handling path, and no
|
||||||
|
* spurious in-page download is triggered.
|
||||||
*/
|
*/
|
||||||
export async function openFile(url: string, filename?: string): Promise<void> {
|
export async function openFile(url: string, filename?: string): Promise<void> {
|
||||||
assertRelativeUrl(url)
|
assertRelativeUrl(url)
|
||||||
@@ -71,11 +84,19 @@ export async function openFile(url: string, filename?: string): Promise<void> {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const win = window.open(blobUrl, '_blank', 'noreferrer')
|
// iOS PWA: target="_blank" would open Safari, which can't access the blob
|
||||||
if (win) {
|
if (isIosStandalone()) {
|
||||||
setTimeout(() => URL.revokeObjectURL(blobUrl), 30_000)
|
|
||||||
} else {
|
|
||||||
// Popup blocked — fall back to download
|
|
||||||
triggerAnchorDownload(blobUrl, filename)
|
triggerAnchorDownload(blobUrl, filename)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const a = document.createElement('a')
|
||||||
|
a.href = blobUrl
|
||||||
|
a.target = '_blank'
|
||||||
|
a.rel = 'noopener noreferrer'
|
||||||
|
document.body.appendChild(a)
|
||||||
|
a.click()
|
||||||
|
// Keep the blob URL alive long enough for the new tab to load it, then
|
||||||
|
// clean up the DOM node and revoke the URL.
|
||||||
|
setTimeout(() => { URL.revokeObjectURL(blobUrl); a.remove() }, 30_000)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,32 +74,42 @@ describe('downloadFile', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('openFile', () => {
|
describe('openFile', () => {
|
||||||
it('fetches with credentials:include and opens blob URL in new tab', async () => {
|
it('fetches with credentials:include and opens blob URL via target=_blank anchor', async () => {
|
||||||
vi.stubGlobal('fetch', makeFetchMock(200))
|
vi.stubGlobal('fetch', makeFetchMock(200))
|
||||||
const mockWin = { closed: false }
|
const openSpy = vi.spyOn(window, 'open').mockReturnValue(null)
|
||||||
const openSpy = vi.spyOn(window, 'open').mockReturnValue(mockWin as Window)
|
const clickSpy = vi.spyOn(HTMLAnchorElement.prototype, 'click').mockImplementation(() => {})
|
||||||
|
|
||||||
await openFile('/uploads/files/doc.pdf')
|
await openFile('/uploads/files/doc.pdf')
|
||||||
|
|
||||||
expect(window.fetch).toHaveBeenCalledWith('/uploads/files/doc.pdf', { credentials: 'include' })
|
expect(window.fetch).toHaveBeenCalledWith('/uploads/files/doc.pdf', { credentials: 'include' })
|
||||||
expect(URL.createObjectURL).toHaveBeenCalled()
|
expect(URL.createObjectURL).toHaveBeenCalled()
|
||||||
expect(openSpy).toHaveBeenCalledWith('blob:mock-url', '_blank', 'noreferrer')
|
// Must NOT call window.open — that path returns null when noreferrer is
|
||||||
|
// set, which previously caused the file to also open in the current tab.
|
||||||
|
expect(openSpy).not.toHaveBeenCalled()
|
||||||
|
expect(clickSpy).toHaveBeenCalledTimes(1)
|
||||||
|
|
||||||
|
// The anchor used to open the new tab must be target=_blank, must NOT
|
||||||
|
// carry a `download` attribute (otherwise it would download in-page
|
||||||
|
// instead of opening), and must use rel=noopener noreferrer.
|
||||||
|
const appendCalls = (document.body.appendChild as ReturnType<typeof vi.fn>).mock.calls
|
||||||
|
const anchor = appendCalls[0]?.[0] as HTMLAnchorElement
|
||||||
|
expect(anchor.target).toBe('_blank')
|
||||||
|
expect(anchor.rel).toBe('noopener noreferrer')
|
||||||
|
expect(anchor.hasAttribute('download')).toBe(false)
|
||||||
|
|
||||||
// Revoke happens after 30s timeout
|
// Revoke happens after 30s timeout
|
||||||
vi.runAllTimers()
|
vi.runAllTimers()
|
||||||
expect(URL.revokeObjectURL).toHaveBeenCalledWith('blob:mock-url')
|
expect(URL.revokeObjectURL).toHaveBeenCalledWith('blob:mock-url')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('falls back to anchor download when popup is blocked', async () => {
|
it('does not trigger a second in-page action for safe inline types (regression: no double-open)', async () => {
|
||||||
vi.stubGlobal('fetch', makeFetchMock(200))
|
vi.stubGlobal('fetch', makeFetchMock(200))
|
||||||
vi.spyOn(window, 'open').mockReturnValue(null)
|
|
||||||
const clickSpy = vi.spyOn(HTMLAnchorElement.prototype, 'click').mockImplementation(() => {})
|
const clickSpy = vi.spyOn(HTMLAnchorElement.prototype, 'click').mockImplementation(() => {})
|
||||||
|
|
||||||
await openFile('/uploads/files/doc.pdf')
|
await openFile('/uploads/files/doc.pdf', 'doc.pdf')
|
||||||
|
|
||||||
expect(clickSpy).toHaveBeenCalled()
|
// Exactly ONE anchor click — opening the new tab. No fallback download.
|
||||||
vi.runAllTimers()
|
expect(clickSpy).toHaveBeenCalledTimes(1)
|
||||||
expect(URL.revokeObjectURL).toHaveBeenCalledWith('blob:mock-url')
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('throws on 401 response', async () => {
|
it('throws on 401 response', async () => {
|
||||||
@@ -108,28 +118,55 @@ describe('openFile', () => {
|
|||||||
expect(URL.createObjectURL).not.toHaveBeenCalled()
|
expect(URL.createObjectURL).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('forces download for unsafe MIME types (HTML, SVG) instead of opening inline', async () => {
|
it('forces download for unsafe MIME types (HTML) instead of opening inline', async () => {
|
||||||
const htmlBlob = new Blob(['<script>alert(1)</script>'], { type: 'text/html' })
|
const htmlBlob = new Blob(['<script>alert(1)</script>'], { type: 'text/html' })
|
||||||
vi.stubGlobal('fetch', makeFetchMock(200, htmlBlob))
|
vi.stubGlobal('fetch', makeFetchMock(200, htmlBlob))
|
||||||
const openSpy = vi.spyOn(window, 'open').mockReturnValue({} as Window)
|
const openSpy = vi.spyOn(window, 'open').mockReturnValue({} as Window)
|
||||||
const clickSpy = vi.spyOn(HTMLAnchorElement.prototype, 'click').mockImplementation(() => {})
|
const clickSpy = vi.spyOn(HTMLAnchorElement.prototype, 'click').mockImplementation(() => {})
|
||||||
|
|
||||||
await openFile('/uploads/files/malicious.html')
|
await openFile('/uploads/files/malicious.html', 'malicious.html')
|
||||||
|
|
||||||
// Must NOT open inline — download anchor clicked instead
|
// Must NOT open inline — download anchor clicked instead
|
||||||
expect(openSpy).not.toHaveBeenCalled()
|
expect(openSpy).not.toHaveBeenCalled()
|
||||||
expect(clickSpy).toHaveBeenCalled()
|
expect(clickSpy).toHaveBeenCalledTimes(1)
|
||||||
|
|
||||||
|
const appendCalls = (document.body.appendChild as ReturnType<typeof vi.fn>).mock.calls
|
||||||
|
const anchor = appendCalls[0]?.[0] as HTMLAnchorElement
|
||||||
|
expect(anchor.download).toBe('malicious.html')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('forces download for SVG MIME type', async () => {
|
it('forces download for SVG MIME type', async () => {
|
||||||
const svgBlob = new Blob(['<svg><script>alert(1)</script></svg>'], { type: 'image/svg+xml' })
|
const svgBlob = new Blob(['<svg><script>alert(1)</script></svg>'], { type: 'image/svg+xml' })
|
||||||
vi.stubGlobal('fetch', makeFetchMock(200, svgBlob))
|
vi.stubGlobal('fetch', makeFetchMock(200, svgBlob))
|
||||||
vi.spyOn(window, 'open').mockReturnValue({} as Window)
|
const openSpy = vi.spyOn(window, 'open').mockReturnValue({} as Window)
|
||||||
const clickSpy = vi.spyOn(HTMLAnchorElement.prototype, 'click').mockImplementation(() => {})
|
const clickSpy = vi.spyOn(HTMLAnchorElement.prototype, 'click').mockImplementation(() => {})
|
||||||
|
|
||||||
await openFile('/uploads/files/malicious.svg')
|
await openFile('/uploads/files/malicious.svg')
|
||||||
|
|
||||||
expect(window.open).not.toHaveBeenCalled()
|
expect(openSpy).not.toHaveBeenCalled()
|
||||||
expect(clickSpy).toHaveBeenCalled()
|
expect(clickSpy).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('falls back to download in iOS PWA standalone mode (blob URL inaccessible to Safari)', async () => {
|
||||||
|
vi.stubGlobal('fetch', makeFetchMock(200))
|
||||||
|
const clickSpy = vi.spyOn(HTMLAnchorElement.prototype, 'click').mockImplementation(() => {})
|
||||||
|
// Simulate iOS PWA (Add-to-Home-Screen) context
|
||||||
|
Object.defineProperty(navigator, 'standalone', { configurable: true, value: true })
|
||||||
|
|
||||||
|
try {
|
||||||
|
await openFile('/uploads/files/doc.pdf', 'doc.pdf')
|
||||||
|
|
||||||
|
// Single anchor click — and it must be a DOWNLOAD anchor (no target=_blank),
|
||||||
|
// because target="_blank" in iOS PWA would hand off to Safari which cannot
|
||||||
|
// read the in-WebView blob URL.
|
||||||
|
expect(clickSpy).toHaveBeenCalledTimes(1)
|
||||||
|
const appendCalls = (document.body.appendChild as ReturnType<typeof vi.fn>).mock.calls
|
||||||
|
const anchor = appendCalls[0]?.[0] as HTMLAnchorElement
|
||||||
|
expect(anchor.target).toBe('')
|
||||||
|
expect(anchor.download).toBe('doc.pdf')
|
||||||
|
} finally {
|
||||||
|
// Clean up the non-standard iOS-only property we forced above.
|
||||||
|
delete (navigator as any).standalone
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ services:
|
|||||||
# - DEFAULT_LANGUAGE=en # Default language on the login page for users with no saved preference. Browser/OS language is auto-detected first; this is the fallback. Supported: de, en, es, fr, hu, nl, br, cs, pl, ru, zh, zh-TW, it, ar
|
# - DEFAULT_LANGUAGE=en # Default language on the login page for users with no saved preference. Browser/OS language is auto-detected first; this is the fallback. Supported: de, en, es, fr, hu, nl, br, cs, pl, ru, zh, zh-TW, it, ar
|
||||||
- ALLOWED_ORIGINS=${ALLOWED_ORIGINS:-} # Comma-separated origins for CORS and email notification links
|
- ALLOWED_ORIGINS=${ALLOWED_ORIGINS:-} # Comma-separated origins for CORS and email notification links
|
||||||
# - FORCE_HTTPS=true # Optional. Enables HTTPS redirect, HSTS, CSP upgrade-insecure-requests, and secure cookies behind a TLS proxy
|
# - FORCE_HTTPS=true # Optional. Enables HTTPS redirect, HSTS, CSP upgrade-insecure-requests, and secure cookies behind a TLS proxy
|
||||||
|
# - HSTS_INCLUDE_SUBDOMAINS=false # When true: adds includeSubDomains to the HSTS header. Only effective when HSTS is active. Leave false if sibling subdomains still run over plain HTTP.
|
||||||
# - COOKIE_SECURE=false # Escape hatch: force session cookies over plain HTTP even in production. Not recommended.
|
# - COOKIE_SECURE=false # Escape hatch: force session cookies over plain HTTP even in production. Not recommended.
|
||||||
# - TRUST_PROXY=1 # Trusted proxy count for X-Forwarded-For / X-Forwarded-Proto. Required for FORCE_HTTPS to work.
|
# - TRUST_PROXY=1 # Trusted proxy count for X-Forwarded-For / X-Forwarded-Proto. Required for FORCE_HTTPS to work.
|
||||||
# - ALLOW_INTERNAL_NETWORK=false # Set to true if Immich or other services are hosted on your local network (RFC-1918 IPs). Loopback and link-local addresses remain blocked regardless.
|
# - ALLOW_INTERNAL_NETWORK=false # Set to true if Immich or other services are hosted on your local network (RFC-1918 IPs). Loopback and link-local addresses remain blocked regardless.
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
# compiled output
|
||||||
|
/dist
|
||||||
|
/node_modules
|
||||||
|
/build
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Tests
|
||||||
|
/coverage
|
||||||
|
/.nyc_output
|
||||||
|
|
||||||
|
# IDEs and editors
|
||||||
|
/.idea
|
||||||
|
.project
|
||||||
|
.classpath
|
||||||
|
.c9/
|
||||||
|
*.launch
|
||||||
|
.settings/
|
||||||
|
*.sublime-workspace
|
||||||
|
|
||||||
|
# IDE - VSCode
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
|
.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# temp directory
|
||||||
|
.temp
|
||||||
|
.tmp
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "all"
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
<p align="center">
|
||||||
|
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="120" alt="Nest Logo" /></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
|
||||||
|
[circleci-url]: https://circleci.com/gh/nestjs/nest
|
||||||
|
|
||||||
|
<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p>
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
|
||||||
|
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
|
||||||
|
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/common.svg" alt="NPM Downloads" /></a>
|
||||||
|
<a href="https://circleci.com/gh/nestjs/nest" target="_blank"><img src="https://img.shields.io/circleci/build/github/nestjs/nest/master" alt="CircleCI" /></a>
|
||||||
|
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
|
||||||
|
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
|
||||||
|
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
|
||||||
|
<a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg" alt="Donate us"/></a>
|
||||||
|
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://img.shields.io/badge/Support%20us-Open%20Collective-41B883.svg" alt="Support us"></a>
|
||||||
|
<a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow" alt="Follow us on Twitter"></a>
|
||||||
|
</p>
|
||||||
|
<!--[](https://opencollective.com/nest#backer)
|
||||||
|
[](https://opencollective.com/nest#sponsor)-->
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
|
||||||
|
|
||||||
|
## Project setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
## Compile and run the project
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# development
|
||||||
|
$ npm run start
|
||||||
|
|
||||||
|
# watch mode
|
||||||
|
$ npm run start:dev
|
||||||
|
|
||||||
|
# production mode
|
||||||
|
$ npm run start:prod
|
||||||
|
```
|
||||||
|
|
||||||
|
## Run tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# unit tests
|
||||||
|
$ npm run test
|
||||||
|
|
||||||
|
# e2e tests
|
||||||
|
$ npm run test:e2e
|
||||||
|
|
||||||
|
# test coverage
|
||||||
|
$ npm run test:cov
|
||||||
|
```
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
When you're ready to deploy your NestJS application to production, there are some key steps you can take to ensure it runs as efficiently as possible. Check out the [deployment documentation](https://docs.nestjs.com/deployment) for more information.
|
||||||
|
|
||||||
|
If you are looking for a cloud-based platform to deploy your NestJS application, check out [Mau](https://mau.nestjs.com), our official platform for deploying NestJS applications on AWS. Mau makes deployment straightforward and fast, requiring just a few simple steps:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ npm install -g @nestjs/mau
|
||||||
|
$ mau deploy
|
||||||
|
```
|
||||||
|
|
||||||
|
With Mau, you can deploy your application in just a few clicks, allowing you to focus on building features rather than managing infrastructure.
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
Check out a few resources that may come in handy when working with NestJS:
|
||||||
|
|
||||||
|
- Visit the [NestJS Documentation](https://docs.nestjs.com) to learn more about the framework.
|
||||||
|
- For questions and support, please visit our [Discord channel](https://discord.gg/G7Qnnhy).
|
||||||
|
- To dive deeper and get more hands-on experience, check out our official video [courses](https://courses.nestjs.com/).
|
||||||
|
- Deploy your application to AWS with the help of [NestJS Mau](https://mau.nestjs.com) in just a few clicks.
|
||||||
|
- Visualize your application graph and interact with the NestJS application in real-time using [NestJS Devtools](https://devtools.nestjs.com).
|
||||||
|
- Need help with your project (part-time to full-time)? Check out our official [enterprise support](https://enterprise.nestjs.com).
|
||||||
|
- To stay in the loop and get updates, follow us on [X](https://x.com/nestframework) and [LinkedIn](https://linkedin.com/company/nestjs).
|
||||||
|
- Looking for a job, or have a job to offer? Check out our official [Jobs board](https://jobs.nestjs.com).
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
|
||||||
|
|
||||||
|
## Stay in touch
|
||||||
|
|
||||||
|
- Author - [Kamil Myśliwiec](https://twitter.com/kammysliwiec)
|
||||||
|
- Website - [https://nestjs.com](https://nestjs.com/)
|
||||||
|
- Twitter - [@nestframework](https://twitter.com/nestframework)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Nest is [MIT licensed](https://github.com/nestjs/nest/blob/master/LICENSE).
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
// @ts-check
|
||||||
|
import eslint from '@eslint/js';
|
||||||
|
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
|
||||||
|
import globals from 'globals';
|
||||||
|
import tseslint from 'typescript-eslint';
|
||||||
|
|
||||||
|
export default tseslint.config(
|
||||||
|
{
|
||||||
|
ignores: ['eslint.config.mjs'],
|
||||||
|
},
|
||||||
|
eslint.configs.recommended,
|
||||||
|
...tseslint.configs.recommendedTypeChecked,
|
||||||
|
eslintPluginPrettierRecommended,
|
||||||
|
{
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.node,
|
||||||
|
...globals.jest,
|
||||||
|
},
|
||||||
|
sourceType: 'commonjs',
|
||||||
|
parserOptions: {
|
||||||
|
projectService: true,
|
||||||
|
tsconfigRootDir: import.meta.dirname,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/no-explicit-any': 'off',
|
||||||
|
'@typescript-eslint/no-floating-promises': 'warn',
|
||||||
|
'@typescript-eslint/no-unsafe-argument': 'warn',
|
||||||
|
"prettier/prettier": ["error", { endOfLine: "auto" }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/nest-cli",
|
||||||
|
"collection": "@nestjs/schematics",
|
||||||
|
"sourceRoot": "src",
|
||||||
|
"compilerOptions": {
|
||||||
|
"deleteOutDir": true
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
+11956
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,87 @@
|
|||||||
|
{
|
||||||
|
"name": "server-nest-2",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "",
|
||||||
|
"author": "",
|
||||||
|
"private": true,
|
||||||
|
"license": "UNLICENSED",
|
||||||
|
"scripts": {
|
||||||
|
"build": "nest build",
|
||||||
|
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
||||||
|
"start": "nest start",
|
||||||
|
"start:dev": "nest start --watch",
|
||||||
|
"start:debug": "nest start --debug --watch",
|
||||||
|
"start:prod": "node dist/main",
|
||||||
|
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
||||||
|
"test": "jest",
|
||||||
|
"test:watch": "jest --watch",
|
||||||
|
"test:cov": "jest --coverage",
|
||||||
|
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
||||||
|
"test:e2e": "jest --config ./test/jest-e2e.json"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@better-auth/oauth-provider": "^1.6.9",
|
||||||
|
"@better-auth/passkey": "^1.6.9",
|
||||||
|
"@hedystia/better-auth-typeorm": "^0.8.2",
|
||||||
|
"@nestjs/common": "^11.1.19",
|
||||||
|
"@nestjs/config": "^4.0.4",
|
||||||
|
"@nestjs/core": "^11.1.19",
|
||||||
|
"@nestjs/platform-express": "^11.1.19",
|
||||||
|
"@nestjs/platform-socket.io": "^11.1.19",
|
||||||
|
"@nestjs/throttler": "^6.5.0",
|
||||||
|
"@nestjs/typeorm": "^11.0.1",
|
||||||
|
"@nestjs/websockets": "^11.1.19",
|
||||||
|
"@thallesp/nestjs-better-auth": "^2.6.0",
|
||||||
|
"better-auth": "^1.6.9",
|
||||||
|
"better-sqlite3": "^12.9.0",
|
||||||
|
"csrf-csrf": "^4.0.3",
|
||||||
|
"helmet": "^8.1.0",
|
||||||
|
"mysql2": "^3.22.2",
|
||||||
|
"pg": "^8.20.0",
|
||||||
|
"reflect-metadata": "^0.2.2",
|
||||||
|
"rxjs": "^7.8.2",
|
||||||
|
"typeorm": "^0.3.28"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/eslintrc": "^3.2.0",
|
||||||
|
"@eslint/js": "^9.18.0",
|
||||||
|
"@nestjs/cli": "^11.0.0",
|
||||||
|
"@nestjs/schematics": "^11.0.0",
|
||||||
|
"@nestjs/testing": "^11.0.1",
|
||||||
|
"@types/express": "^5.0.0",
|
||||||
|
"@types/jest": "^30.0.0",
|
||||||
|
"@types/node": "^24.0.0",
|
||||||
|
"@types/supertest": "^7.0.0",
|
||||||
|
"eslint": "^9.18.0",
|
||||||
|
"eslint-config-prettier": "^10.0.1",
|
||||||
|
"eslint-plugin-prettier": "^5.2.2",
|
||||||
|
"globals": "^17.0.0",
|
||||||
|
"jest": "^30.0.0",
|
||||||
|
"prettier": "^3.4.2",
|
||||||
|
"source-map-support": "^0.5.21",
|
||||||
|
"supertest": "^7.0.0",
|
||||||
|
"ts-jest": "^29.2.5",
|
||||||
|
"ts-loader": "^9.5.2",
|
||||||
|
"ts-node": "^10.9.2",
|
||||||
|
"tsconfig-paths": "^4.2.0",
|
||||||
|
"typescript": "^5.7.3",
|
||||||
|
"typescript-eslint": "^8.20.0"
|
||||||
|
},
|
||||||
|
"jest": {
|
||||||
|
"moduleFileExtensions": [
|
||||||
|
"js",
|
||||||
|
"json",
|
||||||
|
"ts"
|
||||||
|
],
|
||||||
|
"rootDir": "src",
|
||||||
|
"testRegex": ".*\\.spec\\.ts$",
|
||||||
|
"transform": {
|
||||||
|
"^.+\\.(t|j)s$": "ts-jest"
|
||||||
|
},
|
||||||
|
"collectCoverageFrom": [
|
||||||
|
"**/*.(t|j)s"
|
||||||
|
],
|
||||||
|
"coverageDirectory": "../coverage",
|
||||||
|
"testEnvironment": "node"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import authConfig from './config/auth.config.js';
|
||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { AuthModule } from './auth/auth.module.js';
|
||||||
|
|
||||||
|
type SupportedDbType = 'mysql' | 'mariadb' | 'postgres' | 'sqlite';
|
||||||
|
|
||||||
|
function resolveDriver(type: SupportedDbType) {
|
||||||
|
switch (type) {
|
||||||
|
case 'mysql':
|
||||||
|
case 'mariadb':
|
||||||
|
return require('mysql2');
|
||||||
|
case 'postgres':
|
||||||
|
return require('pg');
|
||||||
|
case 'sqlite':
|
||||||
|
return require('better-sqlite3');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
ConfigModule.forRoot({ isGlobal: true, load: [authConfig] }),
|
||||||
|
AuthModule,
|
||||||
|
TypeOrmModule.forRootAsync({
|
||||||
|
imports: [ConfigModule],
|
||||||
|
useFactory: (config: ConfigService) => {
|
||||||
|
const type = config.get<SupportedDbType>('DB_TYPE', 'sqlite');
|
||||||
|
|
||||||
|
return {
|
||||||
|
type,
|
||||||
|
driver: resolveDriver(type),
|
||||||
|
host: config.get<string>('DB_HOST', 'localhost'),
|
||||||
|
port: config.get<number>('DB_PORT', 5432),
|
||||||
|
username: config.get<string>('DB_USER', 'usr'),
|
||||||
|
password: config.get<string>('DB_PASS', 'pwd'),
|
||||||
|
database: config.get<string>('DB_NAME', 'data/travel.db'),
|
||||||
|
autoLoadEntities: true,
|
||||||
|
synchronize: config.get<string>('NODE_ENV') !== 'production',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
inject: [ConfigService],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class AppModule {}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { betterAuth } from 'better-auth';
|
||||||
|
import { typeormAdapter } from '@hedystia/better-auth-typeorm';
|
||||||
|
import { DataSource } from 'typeorm';
|
||||||
|
import { AuthConfig } from '../config/auth.config.js';
|
||||||
|
|
||||||
|
export function createAuth(dataSource: DataSource, cfg: AuthConfig) {
|
||||||
|
return betterAuth({
|
||||||
|
database: typeormAdapter(dataSource, { debugLogs: cfg.debugLogs }),
|
||||||
|
secret: cfg.secret,
|
||||||
|
baseURL: cfg.baseURL,
|
||||||
|
basePath: '/api/auth',
|
||||||
|
trustedOrigins: cfg.frontendUrl ? [cfg.frontendUrl] : [],
|
||||||
|
advanced: {
|
||||||
|
cookies: { session_token: { name: 'trek_session' } },
|
||||||
|
useSecureCookies: cfg.cookieSecure,
|
||||||
|
},
|
||||||
|
emailAndPassword: { enabled: true },
|
||||||
|
plugins: [],
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { betterAuth } from 'better-auth';
|
||||||
|
import { typeormAdapter } from '@hedystia/better-auth-typeorm';
|
||||||
|
import { magicLink } from 'better-auth/plugins/magic-link';
|
||||||
|
import { genericOAuth } from 'better-auth/plugins/generic-oauth';
|
||||||
|
import { jwt } from 'better-auth/plugins/jwt';
|
||||||
|
import { oauthProvider } from '@better-auth/oauth-provider';
|
||||||
|
import { passkey } from '@better-auth/passkey';
|
||||||
|
import { DataSource } from 'typeorm';
|
||||||
|
|
||||||
|
// Used only by `npx @better-auth/cli generate`.
|
||||||
|
// Not imported at runtime — auth.factory.ts uses the DI DataSource.
|
||||||
|
const dataSource = new DataSource({
|
||||||
|
type: 'better-sqlite3',
|
||||||
|
database: ':memory:',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const auth = betterAuth({
|
||||||
|
baseURL: 'http://localhost:3000',
|
||||||
|
database: typeormAdapter(dataSource, {
|
||||||
|
entitiesDir: './src/models/entities/auth',
|
||||||
|
migrationsDir: './src/database/migrations',
|
||||||
|
}),
|
||||||
|
emailAndPassword: {
|
||||||
|
enabled: true,
|
||||||
|
requireEmailVerification: true,
|
||||||
|
sendVerificationEmail: async () => {},
|
||||||
|
sendResetPassword: async () => {},
|
||||||
|
},
|
||||||
|
emailVerification: {
|
||||||
|
sendVerificationEmail: async () => {},
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
jwt(),
|
||||||
|
magicLink({ sendMagicLink: async () => {} }),
|
||||||
|
genericOAuth({ config: [] }),
|
||||||
|
oauthProvider({ loginPage: '/login' }),
|
||||||
|
passkey(),
|
||||||
|
],
|
||||||
|
});
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { AuthModule as BetterAuthNestModule } from '@thallesp/nestjs-better-auth';
|
||||||
|
import { DataSource } from 'typeorm';
|
||||||
|
import { createAuth } from './auth.factory.js';
|
||||||
|
import { AuthConfig } from '../config/auth.config.js';
|
||||||
|
import { User } from '../models/entities/auth/User.js';
|
||||||
|
import { Account } from '../models/entities/auth/Account.js';
|
||||||
|
import { Session } from '../models/entities/auth/Session.js';
|
||||||
|
import { Verification } from '../models/entities/auth/Verification.js';
|
||||||
|
import { Passkey } from '../models/entities/auth/Passkey.js';
|
||||||
|
import { Jwks } from '../models/entities/auth/Jwks.js';
|
||||||
|
import { OauthClient } from '../models/entities/auth/OauthClient.js';
|
||||||
|
import { OauthAccessToken } from '../models/entities/auth/OauthAccessToken.js';
|
||||||
|
import { OauthRefreshToken } from '../models/entities/auth/OauthRefreshToken.js';
|
||||||
|
import { OauthConsent } from '../models/entities/auth/OauthConsent.js';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
TypeOrmModule.forFeature([
|
||||||
|
User,
|
||||||
|
Account,
|
||||||
|
Session,
|
||||||
|
Verification,
|
||||||
|
Passkey,
|
||||||
|
Jwks,
|
||||||
|
OauthClient,
|
||||||
|
OauthAccessToken,
|
||||||
|
OauthRefreshToken,
|
||||||
|
OauthConsent,
|
||||||
|
]),
|
||||||
|
BetterAuthNestModule.forRootAsync({
|
||||||
|
imports: [ConfigModule],
|
||||||
|
inject: [DataSource, ConfigService],
|
||||||
|
useFactory: (ds: DataSource, config: ConfigService) => ({
|
||||||
|
auth: createAuth(ds, config.get<AuthConfig>('auth')!),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
exports: [BetterAuthNestModule],
|
||||||
|
})
|
||||||
|
export class AuthModule {}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { registerAs } from '@nestjs/config';
|
||||||
|
import { boolean } from 'better-auth';
|
||||||
|
|
||||||
|
export interface AuthConfig {
|
||||||
|
secret: string;
|
||||||
|
baseURL: string;
|
||||||
|
frontendUrl: string | undefined;
|
||||||
|
cookieSecure: boolean;
|
||||||
|
debugLogs: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default registerAs(
|
||||||
|
'auth',
|
||||||
|
(): AuthConfig => ({
|
||||||
|
secret: process.env.BETTER_AUTH_SECRET ?? 'changeme',
|
||||||
|
baseURL: process.env.BETTER_AUTH_URL ?? 'http://localhost:3000',
|
||||||
|
frontendUrl: process.env.BASE_URL,
|
||||||
|
cookieSecure:
|
||||||
|
process.env.COOKIE_SECURE === 'true' &&
|
||||||
|
process.env.NODE_ENV === 'production' &&
|
||||||
|
process.env.BASE_URL?.startsWith('https') === true,
|
||||||
|
debugLogs:
|
||||||
|
process.env.BETTER_AUTH_DEBUG_LOGS === 'true' ||
|
||||||
|
process.env.NODE_ENV === 'development',
|
||||||
|
}),
|
||||||
|
);
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
import {
|
||||||
|
type MigrationInterface,
|
||||||
|
type QueryRunner,
|
||||||
|
Table,
|
||||||
|
TableColumn,
|
||||||
|
TableForeignKey,
|
||||||
|
TableIndex,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export class CreateAccount1777216318138 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.createTable(
|
||||||
|
new Table({
|
||||||
|
name: 'account',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
type: 'text',
|
||||||
|
isPrimary: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'accountId',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'providerId',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'userId',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'accessToken',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'refreshToken',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'idToken',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'accessTokenExpiresAt',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'refreshTokenExpiresAt',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'scope',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'password',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
type: 'datetime',
|
||||||
|
default: 'CURRENT_TIMESTAMP',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'updatedAt',
|
||||||
|
type: 'datetime',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createIndex(
|
||||||
|
'account',
|
||||||
|
new TableIndex({
|
||||||
|
name: 'account_userId_idx',
|
||||||
|
columnNames: ['userId'],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createForeignKey(
|
||||||
|
'account',
|
||||||
|
new TableForeignKey({
|
||||||
|
columnNames: ['userId'],
|
||||||
|
referencedTableName: 'user',
|
||||||
|
referencedColumnNames: ['id'],
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.dropTable('account');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import {
|
||||||
|
type MigrationInterface,
|
||||||
|
type QueryRunner,
|
||||||
|
Table,
|
||||||
|
TableColumn,
|
||||||
|
TableForeignKey,
|
||||||
|
TableIndex,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export class CreateSession1777216318138 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.createTable(
|
||||||
|
new Table({
|
||||||
|
name: 'session',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
type: 'text',
|
||||||
|
isPrimary: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'expiresAt',
|
||||||
|
type: 'datetime',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'token',
|
||||||
|
type: 'text',
|
||||||
|
isUnique: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
type: 'datetime',
|
||||||
|
default: 'CURRENT_TIMESTAMP',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'updatedAt',
|
||||||
|
type: 'datetime',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'ipAddress',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'userAgent',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'userId',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createIndex(
|
||||||
|
'session',
|
||||||
|
new TableIndex({
|
||||||
|
name: 'session_userId_idx',
|
||||||
|
columnNames: ['userId'],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createForeignKey(
|
||||||
|
'session',
|
||||||
|
new TableForeignKey({
|
||||||
|
columnNames: ['userId'],
|
||||||
|
referencedTableName: 'user',
|
||||||
|
referencedColumnNames: ['id'],
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.dropTable('session');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import {
|
||||||
|
type MigrationInterface,
|
||||||
|
type QueryRunner,
|
||||||
|
Table,
|
||||||
|
TableColumn,
|
||||||
|
TableForeignKey,
|
||||||
|
TableIndex,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export class CreateUser1777216318138 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.createTable(
|
||||||
|
new Table({
|
||||||
|
name: 'user',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
type: 'text',
|
||||||
|
isPrimary: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'email',
|
||||||
|
type: 'text',
|
||||||
|
isUnique: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'emailVerified',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'image',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
type: 'datetime',
|
||||||
|
default: 'CURRENT_TIMESTAMP',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'updatedAt',
|
||||||
|
type: 'datetime',
|
||||||
|
default: 'CURRENT_TIMESTAMP',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.dropTable('user');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import {
|
||||||
|
type MigrationInterface,
|
||||||
|
type QueryRunner,
|
||||||
|
Table,
|
||||||
|
TableColumn,
|
||||||
|
TableForeignKey,
|
||||||
|
TableIndex,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export class CreateVerification1777216318138 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.createTable(
|
||||||
|
new Table({
|
||||||
|
name: 'verification',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
type: 'text',
|
||||||
|
isPrimary: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'identifier',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'value',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'expiresAt',
|
||||||
|
type: 'datetime',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
type: 'datetime',
|
||||||
|
default: 'CURRENT_TIMESTAMP',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'updatedAt',
|
||||||
|
type: 'datetime',
|
||||||
|
default: 'CURRENT_TIMESTAMP',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createIndex(
|
||||||
|
'verification',
|
||||||
|
new TableIndex({
|
||||||
|
name: 'verification_identifier_idx',
|
||||||
|
columnNames: ['identifier'],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.dropTable('verification');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
import {
|
||||||
|
type MigrationInterface,
|
||||||
|
type QueryRunner,
|
||||||
|
Table,
|
||||||
|
TableColumn,
|
||||||
|
TableForeignKey,
|
||||||
|
TableIndex,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export class CreateAccount1777217712285 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.createTable(
|
||||||
|
new Table({
|
||||||
|
name: 'account',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
type: 'text',
|
||||||
|
isPrimary: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'accountId',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'providerId',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'userId',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'accessToken',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'refreshToken',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'idToken',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'accessTokenExpiresAt',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'refreshTokenExpiresAt',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'scope',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'password',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
type: 'datetime',
|
||||||
|
default: 'CURRENT_TIMESTAMP',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'updatedAt',
|
||||||
|
type: 'datetime',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createIndex(
|
||||||
|
'account',
|
||||||
|
new TableIndex({
|
||||||
|
name: 'account_userId_idx',
|
||||||
|
columnNames: ['userId'],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createForeignKey(
|
||||||
|
'account',
|
||||||
|
new TableForeignKey({
|
||||||
|
columnNames: ['userId'],
|
||||||
|
referencedTableName: 'user',
|
||||||
|
referencedColumnNames: ['id'],
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.dropTable('account');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
import {
|
||||||
|
type MigrationInterface,
|
||||||
|
type QueryRunner,
|
||||||
|
Table,
|
||||||
|
TableColumn,
|
||||||
|
TableForeignKey,
|
||||||
|
TableIndex,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export class CreateOauthAccessToken1777217712285 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.createTable(
|
||||||
|
new Table({
|
||||||
|
name: 'oauthAccessToken',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
type: 'text',
|
||||||
|
isPrimary: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'accessToken',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
isUnique: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'refreshToken',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
isUnique: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'accessTokenExpiresAt',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'refreshTokenExpiresAt',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'clientId',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'userId',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'scopes',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'updatedAt',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createIndex(
|
||||||
|
'oauthAccessToken',
|
||||||
|
new TableIndex({
|
||||||
|
name: 'oauthAccessToken_clientId_idx',
|
||||||
|
columnNames: ['clientId'],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createForeignKey(
|
||||||
|
'oauthAccessToken',
|
||||||
|
new TableForeignKey({
|
||||||
|
columnNames: ['clientId'],
|
||||||
|
referencedTableName: 'oauthApplication',
|
||||||
|
referencedColumnNames: ['clientId'],
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createIndex(
|
||||||
|
'oauthAccessToken',
|
||||||
|
new TableIndex({
|
||||||
|
name: 'oauthAccessToken_userId_idx',
|
||||||
|
columnNames: ['userId'],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createForeignKey(
|
||||||
|
'oauthAccessToken',
|
||||||
|
new TableForeignKey({
|
||||||
|
columnNames: ['userId'],
|
||||||
|
referencedTableName: 'user',
|
||||||
|
referencedColumnNames: ['id'],
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.dropTable('oauthAccessToken');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
import {
|
||||||
|
type MigrationInterface,
|
||||||
|
type QueryRunner,
|
||||||
|
Table,
|
||||||
|
TableColumn,
|
||||||
|
TableForeignKey,
|
||||||
|
TableIndex,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export class CreateOauthApplication1777217712285 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.createTable(
|
||||||
|
new Table({
|
||||||
|
name: 'oauthApplication',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
type: 'text',
|
||||||
|
isPrimary: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'icon',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'metadata',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'clientId',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
isUnique: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'clientSecret',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'redirectUrls',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'type',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'disabled',
|
||||||
|
type: 'boolean',
|
||||||
|
isNullable: true,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'userId',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'updatedAt',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createIndex(
|
||||||
|
'oauthApplication',
|
||||||
|
new TableIndex({
|
||||||
|
name: 'oauthApplication_userId_idx',
|
||||||
|
columnNames: ['userId'],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createForeignKey(
|
||||||
|
'oauthApplication',
|
||||||
|
new TableForeignKey({
|
||||||
|
columnNames: ['userId'],
|
||||||
|
referencedTableName: 'user',
|
||||||
|
referencedColumnNames: ['id'],
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.dropTable('oauthApplication');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
import {
|
||||||
|
type MigrationInterface,
|
||||||
|
type QueryRunner,
|
||||||
|
Table,
|
||||||
|
TableColumn,
|
||||||
|
TableForeignKey,
|
||||||
|
TableIndex,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export class CreateOauthConsent1777217712285 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.createTable(
|
||||||
|
new Table({
|
||||||
|
name: 'oauthConsent',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
type: 'text',
|
||||||
|
isPrimary: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'clientId',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'userId',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'scopes',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'updatedAt',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'consentGiven',
|
||||||
|
type: 'boolean',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createIndex(
|
||||||
|
'oauthConsent',
|
||||||
|
new TableIndex({
|
||||||
|
name: 'oauthConsent_clientId_idx',
|
||||||
|
columnNames: ['clientId'],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createForeignKey(
|
||||||
|
'oauthConsent',
|
||||||
|
new TableForeignKey({
|
||||||
|
columnNames: ['clientId'],
|
||||||
|
referencedTableName: 'oauthApplication',
|
||||||
|
referencedColumnNames: ['clientId'],
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createIndex(
|
||||||
|
'oauthConsent',
|
||||||
|
new TableIndex({
|
||||||
|
name: 'oauthConsent_userId_idx',
|
||||||
|
columnNames: ['userId'],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createForeignKey(
|
||||||
|
'oauthConsent',
|
||||||
|
new TableForeignKey({
|
||||||
|
columnNames: ['userId'],
|
||||||
|
referencedTableName: 'user',
|
||||||
|
referencedColumnNames: ['id'],
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.dropTable('oauthConsent');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
import {
|
||||||
|
type MigrationInterface,
|
||||||
|
type QueryRunner,
|
||||||
|
Table,
|
||||||
|
TableColumn,
|
||||||
|
TableForeignKey,
|
||||||
|
TableIndex,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export class CreatePasskey1777217712285 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.createTable(
|
||||||
|
new Table({
|
||||||
|
name: 'passkey',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
type: 'text',
|
||||||
|
isPrimary: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'publicKey',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'userId',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'credentialID',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'counter',
|
||||||
|
type: 'integer',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'deviceType',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'backedUp',
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'transports',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'aaguid',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createIndex(
|
||||||
|
'passkey',
|
||||||
|
new TableIndex({
|
||||||
|
name: 'passkey_userId_idx',
|
||||||
|
columnNames: ['userId'],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createForeignKey(
|
||||||
|
'passkey',
|
||||||
|
new TableForeignKey({
|
||||||
|
columnNames: ['userId'],
|
||||||
|
referencedTableName: 'user',
|
||||||
|
referencedColumnNames: ['id'],
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createIndex(
|
||||||
|
'passkey',
|
||||||
|
new TableIndex({
|
||||||
|
name: 'passkey_credentialID_idx',
|
||||||
|
columnNames: ['credentialID'],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.dropTable('passkey');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import {
|
||||||
|
type MigrationInterface,
|
||||||
|
type QueryRunner,
|
||||||
|
Table,
|
||||||
|
TableColumn,
|
||||||
|
TableForeignKey,
|
||||||
|
TableIndex,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export class CreateSession1777217712285 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.createTable(
|
||||||
|
new Table({
|
||||||
|
name: 'session',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
type: 'text',
|
||||||
|
isPrimary: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'expiresAt',
|
||||||
|
type: 'datetime',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'token',
|
||||||
|
type: 'text',
|
||||||
|
isUnique: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
type: 'datetime',
|
||||||
|
default: 'CURRENT_TIMESTAMP',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'updatedAt',
|
||||||
|
type: 'datetime',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'ipAddress',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'userAgent',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'userId',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createIndex(
|
||||||
|
'session',
|
||||||
|
new TableIndex({
|
||||||
|
name: 'session_userId_idx',
|
||||||
|
columnNames: ['userId'],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createForeignKey(
|
||||||
|
'session',
|
||||||
|
new TableForeignKey({
|
||||||
|
columnNames: ['userId'],
|
||||||
|
referencedTableName: 'user',
|
||||||
|
referencedColumnNames: ['id'],
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.dropTable('session');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import {
|
||||||
|
type MigrationInterface,
|
||||||
|
type QueryRunner,
|
||||||
|
Table,
|
||||||
|
TableColumn,
|
||||||
|
TableForeignKey,
|
||||||
|
TableIndex,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export class CreateUser1777217712285 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.createTable(
|
||||||
|
new Table({
|
||||||
|
name: 'user',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
type: 'text',
|
||||||
|
isPrimary: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'email',
|
||||||
|
type: 'text',
|
||||||
|
isUnique: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'emailVerified',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'image',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
type: 'datetime',
|
||||||
|
default: 'CURRENT_TIMESTAMP',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'updatedAt',
|
||||||
|
type: 'datetime',
|
||||||
|
default: 'CURRENT_TIMESTAMP',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.dropTable('user');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import {
|
||||||
|
type MigrationInterface,
|
||||||
|
type QueryRunner,
|
||||||
|
Table,
|
||||||
|
TableColumn,
|
||||||
|
TableForeignKey,
|
||||||
|
TableIndex,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export class CreateVerification1777217712285 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.createTable(
|
||||||
|
new Table({
|
||||||
|
name: 'verification',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
type: 'text',
|
||||||
|
isPrimary: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'identifier',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'value',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'expiresAt',
|
||||||
|
type: 'datetime',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
type: 'datetime',
|
||||||
|
default: 'CURRENT_TIMESTAMP',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'updatedAt',
|
||||||
|
type: 'datetime',
|
||||||
|
default: 'CURRENT_TIMESTAMP',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createIndex(
|
||||||
|
'verification',
|
||||||
|
new TableIndex({
|
||||||
|
name: 'verification_identifier_idx',
|
||||||
|
columnNames: ['identifier'],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.dropTable('verification');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
import {
|
||||||
|
type MigrationInterface,
|
||||||
|
type QueryRunner,
|
||||||
|
Table,
|
||||||
|
TableColumn,
|
||||||
|
TableForeignKey,
|
||||||
|
TableIndex,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export class CreateAccount1777217820713 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.createTable(
|
||||||
|
new Table({
|
||||||
|
name: 'account',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
type: 'text',
|
||||||
|
isPrimary: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'accountId',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'providerId',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'userId',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'accessToken',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'refreshToken',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'idToken',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'accessTokenExpiresAt',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'refreshTokenExpiresAt',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'scope',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'password',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
type: 'datetime',
|
||||||
|
default: 'CURRENT_TIMESTAMP',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'updatedAt',
|
||||||
|
type: 'datetime',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createIndex(
|
||||||
|
'account',
|
||||||
|
new TableIndex({
|
||||||
|
name: 'account_userId_idx',
|
||||||
|
columnNames: ['userId'],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createForeignKey(
|
||||||
|
'account',
|
||||||
|
new TableForeignKey({
|
||||||
|
columnNames: ['userId'],
|
||||||
|
referencedTableName: 'user',
|
||||||
|
referencedColumnNames: ['id'],
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.dropTable('account');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
import {
|
||||||
|
type MigrationInterface,
|
||||||
|
type QueryRunner,
|
||||||
|
Table,
|
||||||
|
TableColumn,
|
||||||
|
TableForeignKey,
|
||||||
|
TableIndex,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export class CreateOauthAccessToken1777217820713 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.createTable(
|
||||||
|
new Table({
|
||||||
|
name: 'oauthAccessToken',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
type: 'text',
|
||||||
|
isPrimary: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'token',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
isUnique: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'clientId',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sessionId',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'userId',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'referenceId',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'refreshId',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'expiresAt',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'scopes',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createForeignKey(
|
||||||
|
'oauthAccessToken',
|
||||||
|
new TableForeignKey({
|
||||||
|
columnNames: ['clientId'],
|
||||||
|
referencedTableName: 'oauthClient',
|
||||||
|
referencedColumnNames: ['clientId'],
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createForeignKey(
|
||||||
|
'oauthAccessToken',
|
||||||
|
new TableForeignKey({
|
||||||
|
columnNames: ['sessionId'],
|
||||||
|
referencedTableName: 'session',
|
||||||
|
referencedColumnNames: ['id'],
|
||||||
|
onDelete: 'SET NULL',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createForeignKey(
|
||||||
|
'oauthAccessToken',
|
||||||
|
new TableForeignKey({
|
||||||
|
columnNames: ['userId'],
|
||||||
|
referencedTableName: 'user',
|
||||||
|
referencedColumnNames: ['id'],
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createForeignKey(
|
||||||
|
'oauthAccessToken',
|
||||||
|
new TableForeignKey({
|
||||||
|
columnNames: ['refreshId'],
|
||||||
|
referencedTableName: 'oauthRefreshToken',
|
||||||
|
referencedColumnNames: ['id'],
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.dropTable('oauthAccessToken');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,184 @@
|
|||||||
|
import {
|
||||||
|
type MigrationInterface,
|
||||||
|
type QueryRunner,
|
||||||
|
Table,
|
||||||
|
TableColumn,
|
||||||
|
TableForeignKey,
|
||||||
|
TableIndex,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export class CreateOauthClient1777217820713 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.createTable(
|
||||||
|
new Table({
|
||||||
|
name: 'oauthClient',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
type: 'text',
|
||||||
|
isPrimary: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'clientId',
|
||||||
|
type: 'text',
|
||||||
|
isUnique: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'clientSecret',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'disabled',
|
||||||
|
type: 'boolean',
|
||||||
|
isNullable: true,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'skipConsent',
|
||||||
|
type: 'boolean',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'enableEndSession',
|
||||||
|
type: 'boolean',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'subjectType',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'scopes',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'userId',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'updatedAt',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'uri',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'icon',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'contacts',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'tos',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'policy',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'softwareId',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'softwareVersion',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'softwareStatement',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'redirectUris',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'postLogoutRedirectUris',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'tokenEndpointAuthMethod',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'grantTypes',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'responseTypes',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'public',
|
||||||
|
type: 'boolean',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'type',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'requirePKCE',
|
||||||
|
type: 'boolean',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'referenceId',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'metadata',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createForeignKey(
|
||||||
|
'oauthClient',
|
||||||
|
new TableForeignKey({
|
||||||
|
columnNames: ['userId'],
|
||||||
|
referencedTableName: 'user',
|
||||||
|
referencedColumnNames: ['id'],
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.dropTable('oauthClient');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import {
|
||||||
|
type MigrationInterface,
|
||||||
|
type QueryRunner,
|
||||||
|
Table,
|
||||||
|
TableColumn,
|
||||||
|
TableForeignKey,
|
||||||
|
TableIndex,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export class CreateOauthConsent1777217820713 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.createTable(
|
||||||
|
new Table({
|
||||||
|
name: 'oauthConsent',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
type: 'text',
|
||||||
|
isPrimary: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'clientId',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'userId',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'referenceId',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'scopes',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'updatedAt',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createForeignKey(
|
||||||
|
'oauthConsent',
|
||||||
|
new TableForeignKey({
|
||||||
|
columnNames: ['clientId'],
|
||||||
|
referencedTableName: 'oauthClient',
|
||||||
|
referencedColumnNames: ['clientId'],
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createForeignKey(
|
||||||
|
'oauthConsent',
|
||||||
|
new TableForeignKey({
|
||||||
|
columnNames: ['userId'],
|
||||||
|
referencedTableName: 'user',
|
||||||
|
referencedColumnNames: ['id'],
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.dropTable('oauthConsent');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
import {
|
||||||
|
type MigrationInterface,
|
||||||
|
type QueryRunner,
|
||||||
|
Table,
|
||||||
|
TableColumn,
|
||||||
|
TableForeignKey,
|
||||||
|
TableIndex,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export class CreateOauthRefreshToken1777217820713 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.createTable(
|
||||||
|
new Table({
|
||||||
|
name: 'oauthRefreshToken',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
type: 'text',
|
||||||
|
isPrimary: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'token',
|
||||||
|
type: 'text',
|
||||||
|
isUnique: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'clientId',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sessionId',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'userId',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'referenceId',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'expiresAt',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'revoked',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'authTime',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'scopes',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createForeignKey(
|
||||||
|
'oauthRefreshToken',
|
||||||
|
new TableForeignKey({
|
||||||
|
columnNames: ['clientId'],
|
||||||
|
referencedTableName: 'oauthClient',
|
||||||
|
referencedColumnNames: ['clientId'],
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createForeignKey(
|
||||||
|
'oauthRefreshToken',
|
||||||
|
new TableForeignKey({
|
||||||
|
columnNames: ['sessionId'],
|
||||||
|
referencedTableName: 'session',
|
||||||
|
referencedColumnNames: ['id'],
|
||||||
|
onDelete: 'SET NULL',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createForeignKey(
|
||||||
|
'oauthRefreshToken',
|
||||||
|
new TableForeignKey({
|
||||||
|
columnNames: ['userId'],
|
||||||
|
referencedTableName: 'user',
|
||||||
|
referencedColumnNames: ['id'],
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.dropTable('oauthRefreshToken');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
import {
|
||||||
|
type MigrationInterface,
|
||||||
|
type QueryRunner,
|
||||||
|
Table,
|
||||||
|
TableColumn,
|
||||||
|
TableForeignKey,
|
||||||
|
TableIndex,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export class CreatePasskey1777217820713 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.createTable(
|
||||||
|
new Table({
|
||||||
|
name: 'passkey',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
type: 'text',
|
||||||
|
isPrimary: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'publicKey',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'userId',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'credentialID',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'counter',
|
||||||
|
type: 'integer',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'deviceType',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'backedUp',
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'transports',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'aaguid',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createIndex(
|
||||||
|
'passkey',
|
||||||
|
new TableIndex({
|
||||||
|
name: 'passkey_userId_idx',
|
||||||
|
columnNames: ['userId'],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createForeignKey(
|
||||||
|
'passkey',
|
||||||
|
new TableForeignKey({
|
||||||
|
columnNames: ['userId'],
|
||||||
|
referencedTableName: 'user',
|
||||||
|
referencedColumnNames: ['id'],
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createIndex(
|
||||||
|
'passkey',
|
||||||
|
new TableIndex({
|
||||||
|
name: 'passkey_credentialID_idx',
|
||||||
|
columnNames: ['credentialID'],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.dropTable('passkey');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import {
|
||||||
|
type MigrationInterface,
|
||||||
|
type QueryRunner,
|
||||||
|
Table,
|
||||||
|
TableColumn,
|
||||||
|
TableForeignKey,
|
||||||
|
TableIndex,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export class CreateSession1777217820713 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.createTable(
|
||||||
|
new Table({
|
||||||
|
name: 'session',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
type: 'text',
|
||||||
|
isPrimary: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'expiresAt',
|
||||||
|
type: 'datetime',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'token',
|
||||||
|
type: 'text',
|
||||||
|
isUnique: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
type: 'datetime',
|
||||||
|
default: 'CURRENT_TIMESTAMP',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'updatedAt',
|
||||||
|
type: 'datetime',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'ipAddress',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'userAgent',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'userId',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createIndex(
|
||||||
|
'session',
|
||||||
|
new TableIndex({
|
||||||
|
name: 'session_userId_idx',
|
||||||
|
columnNames: ['userId'],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createForeignKey(
|
||||||
|
'session',
|
||||||
|
new TableForeignKey({
|
||||||
|
columnNames: ['userId'],
|
||||||
|
referencedTableName: 'user',
|
||||||
|
referencedColumnNames: ['id'],
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.dropTable('session');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import {
|
||||||
|
type MigrationInterface,
|
||||||
|
type QueryRunner,
|
||||||
|
Table,
|
||||||
|
TableColumn,
|
||||||
|
TableForeignKey,
|
||||||
|
TableIndex,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export class CreateUser1777217820713 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.createTable(
|
||||||
|
new Table({
|
||||||
|
name: 'user',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
type: 'text',
|
||||||
|
isPrimary: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'email',
|
||||||
|
type: 'text',
|
||||||
|
isUnique: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'emailVerified',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'image',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
type: 'datetime',
|
||||||
|
default: 'CURRENT_TIMESTAMP',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'updatedAt',
|
||||||
|
type: 'datetime',
|
||||||
|
default: 'CURRENT_TIMESTAMP',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.dropTable('user');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import {
|
||||||
|
type MigrationInterface,
|
||||||
|
type QueryRunner,
|
||||||
|
Table,
|
||||||
|
TableColumn,
|
||||||
|
TableForeignKey,
|
||||||
|
TableIndex,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export class CreateVerification1777217820713 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.createTable(
|
||||||
|
new Table({
|
||||||
|
name: 'verification',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
type: 'text',
|
||||||
|
isPrimary: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'identifier',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'value',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'expiresAt',
|
||||||
|
type: 'datetime',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
type: 'datetime',
|
||||||
|
default: 'CURRENT_TIMESTAMP',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'updatedAt',
|
||||||
|
type: 'datetime',
|
||||||
|
default: 'CURRENT_TIMESTAMP',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createIndex(
|
||||||
|
'verification',
|
||||||
|
new TableIndex({
|
||||||
|
name: 'verification_identifier_idx',
|
||||||
|
columnNames: ['identifier'],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.dropTable('verification');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
import {
|
||||||
|
type MigrationInterface,
|
||||||
|
type QueryRunner,
|
||||||
|
Table,
|
||||||
|
TableColumn,
|
||||||
|
TableForeignKey,
|
||||||
|
TableIndex,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export class CreateAccount1777217882945 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.createTable(
|
||||||
|
new Table({
|
||||||
|
name: 'account',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
type: 'text',
|
||||||
|
isPrimary: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'accountId',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'providerId',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'userId',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'accessToken',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'refreshToken',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'idToken',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'accessTokenExpiresAt',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'refreshTokenExpiresAt',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'scope',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'password',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
type: 'datetime',
|
||||||
|
default: 'CURRENT_TIMESTAMP',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'updatedAt',
|
||||||
|
type: 'datetime',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createIndex(
|
||||||
|
'account',
|
||||||
|
new TableIndex({
|
||||||
|
name: 'account_userId_idx',
|
||||||
|
columnNames: ['userId'],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createForeignKey(
|
||||||
|
'account',
|
||||||
|
new TableForeignKey({
|
||||||
|
columnNames: ['userId'],
|
||||||
|
referencedTableName: 'user',
|
||||||
|
referencedColumnNames: ['id'],
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.dropTable('account');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import {
|
||||||
|
type MigrationInterface,
|
||||||
|
type QueryRunner,
|
||||||
|
Table,
|
||||||
|
TableColumn,
|
||||||
|
TableForeignKey,
|
||||||
|
TableIndex,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export class CreateJwks1777217882945 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.createTable(
|
||||||
|
new Table({
|
||||||
|
name: 'jwks',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
type: 'text',
|
||||||
|
isPrimary: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'publicKey',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'privateKey',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
type: 'datetime',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'expiresAt',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.dropTable('jwks');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
import {
|
||||||
|
type MigrationInterface,
|
||||||
|
type QueryRunner,
|
||||||
|
Table,
|
||||||
|
TableColumn,
|
||||||
|
TableForeignKey,
|
||||||
|
TableIndex,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export class CreateOauthAccessToken1777217882945 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.createTable(
|
||||||
|
new Table({
|
||||||
|
name: 'oauthAccessToken',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
type: 'text',
|
||||||
|
isPrimary: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'token',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
isUnique: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'clientId',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sessionId',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'userId',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'referenceId',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'refreshId',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'expiresAt',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'scopes',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createForeignKey(
|
||||||
|
'oauthAccessToken',
|
||||||
|
new TableForeignKey({
|
||||||
|
columnNames: ['clientId'],
|
||||||
|
referencedTableName: 'oauthClient',
|
||||||
|
referencedColumnNames: ['clientId'],
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createForeignKey(
|
||||||
|
'oauthAccessToken',
|
||||||
|
new TableForeignKey({
|
||||||
|
columnNames: ['sessionId'],
|
||||||
|
referencedTableName: 'session',
|
||||||
|
referencedColumnNames: ['id'],
|
||||||
|
onDelete: 'SET NULL',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createForeignKey(
|
||||||
|
'oauthAccessToken',
|
||||||
|
new TableForeignKey({
|
||||||
|
columnNames: ['userId'],
|
||||||
|
referencedTableName: 'user',
|
||||||
|
referencedColumnNames: ['id'],
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createForeignKey(
|
||||||
|
'oauthAccessToken',
|
||||||
|
new TableForeignKey({
|
||||||
|
columnNames: ['refreshId'],
|
||||||
|
referencedTableName: 'oauthRefreshToken',
|
||||||
|
referencedColumnNames: ['id'],
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.dropTable('oauthAccessToken');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,184 @@
|
|||||||
|
import {
|
||||||
|
type MigrationInterface,
|
||||||
|
type QueryRunner,
|
||||||
|
Table,
|
||||||
|
TableColumn,
|
||||||
|
TableForeignKey,
|
||||||
|
TableIndex,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export class CreateOauthClient1777217882945 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.createTable(
|
||||||
|
new Table({
|
||||||
|
name: 'oauthClient',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
type: 'text',
|
||||||
|
isPrimary: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'clientId',
|
||||||
|
type: 'text',
|
||||||
|
isUnique: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'clientSecret',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'disabled',
|
||||||
|
type: 'boolean',
|
||||||
|
isNullable: true,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'skipConsent',
|
||||||
|
type: 'boolean',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'enableEndSession',
|
||||||
|
type: 'boolean',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'subjectType',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'scopes',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'userId',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'updatedAt',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'uri',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'icon',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'contacts',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'tos',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'policy',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'softwareId',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'softwareVersion',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'softwareStatement',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'redirectUris',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'postLogoutRedirectUris',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'tokenEndpointAuthMethod',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'grantTypes',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'responseTypes',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'public',
|
||||||
|
type: 'boolean',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'type',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'requirePKCE',
|
||||||
|
type: 'boolean',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'referenceId',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'metadata',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createForeignKey(
|
||||||
|
'oauthClient',
|
||||||
|
new TableForeignKey({
|
||||||
|
columnNames: ['userId'],
|
||||||
|
referencedTableName: 'user',
|
||||||
|
referencedColumnNames: ['id'],
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.dropTable('oauthClient');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import {
|
||||||
|
type MigrationInterface,
|
||||||
|
type QueryRunner,
|
||||||
|
Table,
|
||||||
|
TableColumn,
|
||||||
|
TableForeignKey,
|
||||||
|
TableIndex,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export class CreateOauthConsent1777217882945 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.createTable(
|
||||||
|
new Table({
|
||||||
|
name: 'oauthConsent',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
type: 'text',
|
||||||
|
isPrimary: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'clientId',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'userId',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'referenceId',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'scopes',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'updatedAt',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createForeignKey(
|
||||||
|
'oauthConsent',
|
||||||
|
new TableForeignKey({
|
||||||
|
columnNames: ['clientId'],
|
||||||
|
referencedTableName: 'oauthClient',
|
||||||
|
referencedColumnNames: ['clientId'],
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createForeignKey(
|
||||||
|
'oauthConsent',
|
||||||
|
new TableForeignKey({
|
||||||
|
columnNames: ['userId'],
|
||||||
|
referencedTableName: 'user',
|
||||||
|
referencedColumnNames: ['id'],
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.dropTable('oauthConsent');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
import {
|
||||||
|
type MigrationInterface,
|
||||||
|
type QueryRunner,
|
||||||
|
Table,
|
||||||
|
TableColumn,
|
||||||
|
TableForeignKey,
|
||||||
|
TableIndex,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export class CreateOauthRefreshToken1777217882945 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.createTable(
|
||||||
|
new Table({
|
||||||
|
name: 'oauthRefreshToken',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
type: 'text',
|
||||||
|
isPrimary: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'token',
|
||||||
|
type: 'text',
|
||||||
|
isUnique: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'clientId',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sessionId',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'userId',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'referenceId',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'expiresAt',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'revoked',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'authTime',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'scopes',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createForeignKey(
|
||||||
|
'oauthRefreshToken',
|
||||||
|
new TableForeignKey({
|
||||||
|
columnNames: ['clientId'],
|
||||||
|
referencedTableName: 'oauthClient',
|
||||||
|
referencedColumnNames: ['clientId'],
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createForeignKey(
|
||||||
|
'oauthRefreshToken',
|
||||||
|
new TableForeignKey({
|
||||||
|
columnNames: ['sessionId'],
|
||||||
|
referencedTableName: 'session',
|
||||||
|
referencedColumnNames: ['id'],
|
||||||
|
onDelete: 'SET NULL',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createForeignKey(
|
||||||
|
'oauthRefreshToken',
|
||||||
|
new TableForeignKey({
|
||||||
|
columnNames: ['userId'],
|
||||||
|
referencedTableName: 'user',
|
||||||
|
referencedColumnNames: ['id'],
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.dropTable('oauthRefreshToken');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
import {
|
||||||
|
type MigrationInterface,
|
||||||
|
type QueryRunner,
|
||||||
|
Table,
|
||||||
|
TableColumn,
|
||||||
|
TableForeignKey,
|
||||||
|
TableIndex,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export class CreatePasskey1777217882945 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.createTable(
|
||||||
|
new Table({
|
||||||
|
name: 'passkey',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
type: 'text',
|
||||||
|
isPrimary: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'publicKey',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'userId',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'credentialID',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'counter',
|
||||||
|
type: 'integer',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'deviceType',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'backedUp',
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'transports',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'aaguid',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createIndex(
|
||||||
|
'passkey',
|
||||||
|
new TableIndex({
|
||||||
|
name: 'passkey_userId_idx',
|
||||||
|
columnNames: ['userId'],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createForeignKey(
|
||||||
|
'passkey',
|
||||||
|
new TableForeignKey({
|
||||||
|
columnNames: ['userId'],
|
||||||
|
referencedTableName: 'user',
|
||||||
|
referencedColumnNames: ['id'],
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createIndex(
|
||||||
|
'passkey',
|
||||||
|
new TableIndex({
|
||||||
|
name: 'passkey_credentialID_idx',
|
||||||
|
columnNames: ['credentialID'],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.dropTable('passkey');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import {
|
||||||
|
type MigrationInterface,
|
||||||
|
type QueryRunner,
|
||||||
|
Table,
|
||||||
|
TableColumn,
|
||||||
|
TableForeignKey,
|
||||||
|
TableIndex,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export class CreateSession1777217882945 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.createTable(
|
||||||
|
new Table({
|
||||||
|
name: 'session',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
type: 'text',
|
||||||
|
isPrimary: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'expiresAt',
|
||||||
|
type: 'datetime',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'token',
|
||||||
|
type: 'text',
|
||||||
|
isUnique: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
type: 'datetime',
|
||||||
|
default: 'CURRENT_TIMESTAMP',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'updatedAt',
|
||||||
|
type: 'datetime',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'ipAddress',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'userAgent',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'userId',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createIndex(
|
||||||
|
'session',
|
||||||
|
new TableIndex({
|
||||||
|
name: 'session_userId_idx',
|
||||||
|
columnNames: ['userId'],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createForeignKey(
|
||||||
|
'session',
|
||||||
|
new TableForeignKey({
|
||||||
|
columnNames: ['userId'],
|
||||||
|
referencedTableName: 'user',
|
||||||
|
referencedColumnNames: ['id'],
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.dropTable('session');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import {
|
||||||
|
type MigrationInterface,
|
||||||
|
type QueryRunner,
|
||||||
|
Table,
|
||||||
|
TableColumn,
|
||||||
|
TableForeignKey,
|
||||||
|
TableIndex,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export class CreateUser1777217882945 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.createTable(
|
||||||
|
new Table({
|
||||||
|
name: 'user',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
type: 'text',
|
||||||
|
isPrimary: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'email',
|
||||||
|
type: 'text',
|
||||||
|
isUnique: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'emailVerified',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'image',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
type: 'datetime',
|
||||||
|
default: 'CURRENT_TIMESTAMP',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'updatedAt',
|
||||||
|
type: 'datetime',
|
||||||
|
default: 'CURRENT_TIMESTAMP',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.dropTable('user');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import {
|
||||||
|
type MigrationInterface,
|
||||||
|
type QueryRunner,
|
||||||
|
Table,
|
||||||
|
TableColumn,
|
||||||
|
TableForeignKey,
|
||||||
|
TableIndex,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export class CreateVerification1777217882945 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.createTable(
|
||||||
|
new Table({
|
||||||
|
name: 'verification',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
type: 'text',
|
||||||
|
isPrimary: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'identifier',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'value',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'expiresAt',
|
||||||
|
type: 'datetime',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
type: 'datetime',
|
||||||
|
default: 'CURRENT_TIMESTAMP',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'updatedAt',
|
||||||
|
type: 'datetime',
|
||||||
|
default: 'CURRENT_TIMESTAMP',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createIndex(
|
||||||
|
'verification',
|
||||||
|
new TableIndex({
|
||||||
|
name: 'verification_identifier_idx',
|
||||||
|
columnNames: ['identifier'],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.dropTable('verification');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
import {
|
||||||
|
type MigrationInterface,
|
||||||
|
type QueryRunner,
|
||||||
|
Table,
|
||||||
|
TableColumn,
|
||||||
|
TableForeignKey,
|
||||||
|
TableIndex,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export class CreateAccount1777217895075 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.createTable(
|
||||||
|
new Table({
|
||||||
|
name: 'account',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
type: 'text',
|
||||||
|
isPrimary: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'accountId',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'providerId',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'userId',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'accessToken',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'refreshToken',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'idToken',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'accessTokenExpiresAt',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'refreshTokenExpiresAt',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'scope',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'password',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
type: 'datetime',
|
||||||
|
default: 'CURRENT_TIMESTAMP',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'updatedAt',
|
||||||
|
type: 'datetime',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createIndex(
|
||||||
|
'account',
|
||||||
|
new TableIndex({
|
||||||
|
name: 'account_userId_idx',
|
||||||
|
columnNames: ['userId'],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createForeignKey(
|
||||||
|
'account',
|
||||||
|
new TableForeignKey({
|
||||||
|
columnNames: ['userId'],
|
||||||
|
referencedTableName: 'user',
|
||||||
|
referencedColumnNames: ['id'],
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.dropTable('account');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import {
|
||||||
|
type MigrationInterface,
|
||||||
|
type QueryRunner,
|
||||||
|
Table,
|
||||||
|
TableColumn,
|
||||||
|
TableForeignKey,
|
||||||
|
TableIndex,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export class CreateJwks1777217895075 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.createTable(
|
||||||
|
new Table({
|
||||||
|
name: 'jwks',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
type: 'text',
|
||||||
|
isPrimary: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'publicKey',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'privateKey',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
type: 'datetime',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'expiresAt',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.dropTable('jwks');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
import {
|
||||||
|
type MigrationInterface,
|
||||||
|
type QueryRunner,
|
||||||
|
Table,
|
||||||
|
TableColumn,
|
||||||
|
TableForeignKey,
|
||||||
|
TableIndex,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export class CreateOauthAccessToken1777217895075 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.createTable(
|
||||||
|
new Table({
|
||||||
|
name: 'oauthAccessToken',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
type: 'text',
|
||||||
|
isPrimary: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'token',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
isUnique: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'clientId',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sessionId',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'userId',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'referenceId',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'refreshId',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'expiresAt',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'scopes',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createForeignKey(
|
||||||
|
'oauthAccessToken',
|
||||||
|
new TableForeignKey({
|
||||||
|
columnNames: ['clientId'],
|
||||||
|
referencedTableName: 'oauthClient',
|
||||||
|
referencedColumnNames: ['clientId'],
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createForeignKey(
|
||||||
|
'oauthAccessToken',
|
||||||
|
new TableForeignKey({
|
||||||
|
columnNames: ['sessionId'],
|
||||||
|
referencedTableName: 'session',
|
||||||
|
referencedColumnNames: ['id'],
|
||||||
|
onDelete: 'SET NULL',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createForeignKey(
|
||||||
|
'oauthAccessToken',
|
||||||
|
new TableForeignKey({
|
||||||
|
columnNames: ['userId'],
|
||||||
|
referencedTableName: 'user',
|
||||||
|
referencedColumnNames: ['id'],
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createForeignKey(
|
||||||
|
'oauthAccessToken',
|
||||||
|
new TableForeignKey({
|
||||||
|
columnNames: ['refreshId'],
|
||||||
|
referencedTableName: 'oauthRefreshToken',
|
||||||
|
referencedColumnNames: ['id'],
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.dropTable('oauthAccessToken');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,184 @@
|
|||||||
|
import {
|
||||||
|
type MigrationInterface,
|
||||||
|
type QueryRunner,
|
||||||
|
Table,
|
||||||
|
TableColumn,
|
||||||
|
TableForeignKey,
|
||||||
|
TableIndex,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export class CreateOauthClient1777217895075 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.createTable(
|
||||||
|
new Table({
|
||||||
|
name: 'oauthClient',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
type: 'text',
|
||||||
|
isPrimary: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'clientId',
|
||||||
|
type: 'text',
|
||||||
|
isUnique: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'clientSecret',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'disabled',
|
||||||
|
type: 'boolean',
|
||||||
|
isNullable: true,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'skipConsent',
|
||||||
|
type: 'boolean',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'enableEndSession',
|
||||||
|
type: 'boolean',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'subjectType',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'scopes',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'userId',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'updatedAt',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'uri',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'icon',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'contacts',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'tos',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'policy',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'softwareId',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'softwareVersion',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'softwareStatement',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'redirectUris',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'postLogoutRedirectUris',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'tokenEndpointAuthMethod',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'grantTypes',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'responseTypes',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'public',
|
||||||
|
type: 'boolean',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'type',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'requirePKCE',
|
||||||
|
type: 'boolean',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'referenceId',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'metadata',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createForeignKey(
|
||||||
|
'oauthClient',
|
||||||
|
new TableForeignKey({
|
||||||
|
columnNames: ['userId'],
|
||||||
|
referencedTableName: 'user',
|
||||||
|
referencedColumnNames: ['id'],
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.dropTable('oauthClient');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import {
|
||||||
|
type MigrationInterface,
|
||||||
|
type QueryRunner,
|
||||||
|
Table,
|
||||||
|
TableColumn,
|
||||||
|
TableForeignKey,
|
||||||
|
TableIndex,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export class CreateOauthConsent1777217895075 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.createTable(
|
||||||
|
new Table({
|
||||||
|
name: 'oauthConsent',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
type: 'text',
|
||||||
|
isPrimary: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'clientId',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'userId',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'referenceId',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'scopes',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'updatedAt',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createForeignKey(
|
||||||
|
'oauthConsent',
|
||||||
|
new TableForeignKey({
|
||||||
|
columnNames: ['clientId'],
|
||||||
|
referencedTableName: 'oauthClient',
|
||||||
|
referencedColumnNames: ['clientId'],
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createForeignKey(
|
||||||
|
'oauthConsent',
|
||||||
|
new TableForeignKey({
|
||||||
|
columnNames: ['userId'],
|
||||||
|
referencedTableName: 'user',
|
||||||
|
referencedColumnNames: ['id'],
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.dropTable('oauthConsent');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
import {
|
||||||
|
type MigrationInterface,
|
||||||
|
type QueryRunner,
|
||||||
|
Table,
|
||||||
|
TableColumn,
|
||||||
|
TableForeignKey,
|
||||||
|
TableIndex,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export class CreateOauthRefreshToken1777217895075 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.createTable(
|
||||||
|
new Table({
|
||||||
|
name: 'oauthRefreshToken',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
type: 'text',
|
||||||
|
isPrimary: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'token',
|
||||||
|
type: 'text',
|
||||||
|
isUnique: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'clientId',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sessionId',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'userId',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'referenceId',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'expiresAt',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'revoked',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'authTime',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'scopes',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createForeignKey(
|
||||||
|
'oauthRefreshToken',
|
||||||
|
new TableForeignKey({
|
||||||
|
columnNames: ['clientId'],
|
||||||
|
referencedTableName: 'oauthClient',
|
||||||
|
referencedColumnNames: ['clientId'],
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createForeignKey(
|
||||||
|
'oauthRefreshToken',
|
||||||
|
new TableForeignKey({
|
||||||
|
columnNames: ['sessionId'],
|
||||||
|
referencedTableName: 'session',
|
||||||
|
referencedColumnNames: ['id'],
|
||||||
|
onDelete: 'SET NULL',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createForeignKey(
|
||||||
|
'oauthRefreshToken',
|
||||||
|
new TableForeignKey({
|
||||||
|
columnNames: ['userId'],
|
||||||
|
referencedTableName: 'user',
|
||||||
|
referencedColumnNames: ['id'],
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.dropTable('oauthRefreshToken');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
import {
|
||||||
|
type MigrationInterface,
|
||||||
|
type QueryRunner,
|
||||||
|
Table,
|
||||||
|
TableColumn,
|
||||||
|
TableForeignKey,
|
||||||
|
TableIndex,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export class CreatePasskey1777217895075 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.createTable(
|
||||||
|
new Table({
|
||||||
|
name: 'passkey',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
type: 'text',
|
||||||
|
isPrimary: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'publicKey',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'userId',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'credentialID',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'counter',
|
||||||
|
type: 'integer',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'deviceType',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'backedUp',
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'transports',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
type: 'datetime',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'aaguid',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createIndex(
|
||||||
|
'passkey',
|
||||||
|
new TableIndex({
|
||||||
|
name: 'passkey_userId_idx',
|
||||||
|
columnNames: ['userId'],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createForeignKey(
|
||||||
|
'passkey',
|
||||||
|
new TableForeignKey({
|
||||||
|
columnNames: ['userId'],
|
||||||
|
referencedTableName: 'user',
|
||||||
|
referencedColumnNames: ['id'],
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createIndex(
|
||||||
|
'passkey',
|
||||||
|
new TableIndex({
|
||||||
|
name: 'passkey_credentialID_idx',
|
||||||
|
columnNames: ['credentialID'],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.dropTable('passkey');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import {
|
||||||
|
type MigrationInterface,
|
||||||
|
type QueryRunner,
|
||||||
|
Table,
|
||||||
|
TableColumn,
|
||||||
|
TableForeignKey,
|
||||||
|
TableIndex,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export class CreateSession1777217895075 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.createTable(
|
||||||
|
new Table({
|
||||||
|
name: 'session',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
type: 'text',
|
||||||
|
isPrimary: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'expiresAt',
|
||||||
|
type: 'datetime',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'token',
|
||||||
|
type: 'text',
|
||||||
|
isUnique: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
type: 'datetime',
|
||||||
|
default: 'CURRENT_TIMESTAMP',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'updatedAt',
|
||||||
|
type: 'datetime',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'ipAddress',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'userAgent',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'userId',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createIndex(
|
||||||
|
'session',
|
||||||
|
new TableIndex({
|
||||||
|
name: 'session_userId_idx',
|
||||||
|
columnNames: ['userId'],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createForeignKey(
|
||||||
|
'session',
|
||||||
|
new TableForeignKey({
|
||||||
|
columnNames: ['userId'],
|
||||||
|
referencedTableName: 'user',
|
||||||
|
referencedColumnNames: ['id'],
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.dropTable('session');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import {
|
||||||
|
type MigrationInterface,
|
||||||
|
type QueryRunner,
|
||||||
|
Table,
|
||||||
|
TableColumn,
|
||||||
|
TableForeignKey,
|
||||||
|
TableIndex,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export class CreateUser1777217895075 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.createTable(
|
||||||
|
new Table({
|
||||||
|
name: 'user',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
type: 'text',
|
||||||
|
isPrimary: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'email',
|
||||||
|
type: 'text',
|
||||||
|
isUnique: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'emailVerified',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'image',
|
||||||
|
type: 'text',
|
||||||
|
isNullable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
type: 'datetime',
|
||||||
|
default: 'CURRENT_TIMESTAMP',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'updatedAt',
|
||||||
|
type: 'datetime',
|
||||||
|
default: 'CURRENT_TIMESTAMP',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.dropTable('user');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import {
|
||||||
|
type MigrationInterface,
|
||||||
|
type QueryRunner,
|
||||||
|
Table,
|
||||||
|
TableColumn,
|
||||||
|
TableForeignKey,
|
||||||
|
TableIndex,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export class CreateVerification1777217895075 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.createTable(
|
||||||
|
new Table({
|
||||||
|
name: 'verification',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
type: 'text',
|
||||||
|
isPrimary: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'identifier',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'value',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'expiresAt',
|
||||||
|
type: 'datetime',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
type: 'datetime',
|
||||||
|
default: 'CURRENT_TIMESTAMP',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'updatedAt',
|
||||||
|
type: 'datetime',
|
||||||
|
default: 'CURRENT_TIMESTAMP',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.createIndex(
|
||||||
|
'verification',
|
||||||
|
new TableIndex({
|
||||||
|
name: 'verification_identifier_idx',
|
||||||
|
columnNames: ['identifier'],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.dropTable('verification');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { NestFactory } from '@nestjs/core';
|
||||||
|
import { AppModule } from './app.module';
|
||||||
|
|
||||||
|
async function bootstrap() {
|
||||||
|
const app = await NestFactory.create(AppModule, { bodyParser: false });
|
||||||
|
await app.listen(process.env.PORT ?? 3001);
|
||||||
|
}
|
||||||
|
bootstrap();
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { Column, Entity, JoinColumn, ManyToOne } from 'typeorm';
|
||||||
|
import { IntBaseEntity } from '../base/BaseEntity';
|
||||||
|
import { SqliteUsers } from '../old-entities/SqliteUsers';
|
||||||
|
|
||||||
|
@Entity('bucket_list')
|
||||||
|
export class BucketList extends IntBaseEntity {
|
||||||
|
@Column({ name: 'name' })
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@Column({ name: 'lat', nullable: true })
|
||||||
|
lat: number | null;
|
||||||
|
|
||||||
|
@Column({ name: 'lng', nullable: true })
|
||||||
|
lng: number | null;
|
||||||
|
|
||||||
|
@Column({ name: 'country_code', nullable: true })
|
||||||
|
countryCode: string | null;
|
||||||
|
|
||||||
|
@Column('text', { name: 'notes', nullable: true })
|
||||||
|
notes: string | null;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'target_date',
|
||||||
|
nullable: true,
|
||||||
|
default: null,
|
||||||
|
})
|
||||||
|
targetDate: string | null;
|
||||||
|
|
||||||
|
@ManyToOne(() => SqliteUsers, (users) => users.bucketLists, {
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
})
|
||||||
|
@JoinColumn([{ name: 'user_id', referencedColumnName: 'id' }])
|
||||||
|
user: SqliteUsers;
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { Column, Entity, JoinColumn, ManyToOne } from 'typeorm';
|
||||||
|
import { IntBaseEntity } from '../base/BaseEntity';
|
||||||
|
import { SqliteUsers } from '../old-entities/SqliteUsers';
|
||||||
|
|
||||||
|
@Entity('visited_countries')
|
||||||
|
export class VisitedCountries extends IntBaseEntity {
|
||||||
|
@Column('text', { name: 'country_code' })
|
||||||
|
countryCode: string;
|
||||||
|
|
||||||
|
@ManyToOne(() => SqliteUsers, (users) => users.visitedCountries, {
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
})
|
||||||
|
@JoinColumn([{ name: 'user_id', referencedColumnName: 'id' }])
|
||||||
|
user: SqliteUsers;
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm';
|
||||||
|
import { IntBaseEntity } from '../base/BaseEntity';
|
||||||
|
import { SqliteUsers } from '../old-entities/SqliteUsers';
|
||||||
|
|
||||||
|
@Index('idx_visited_regions_country', ['countryCode'], {})
|
||||||
|
@Entity('visited_regions')
|
||||||
|
export class VisitedRegions extends IntBaseEntity {
|
||||||
|
@Column({ name: 'region_code' })
|
||||||
|
regionCode: string;
|
||||||
|
|
||||||
|
@Column({ name: 'region_name' })
|
||||||
|
regionName: string;
|
||||||
|
|
||||||
|
@Column({ name: 'country_code' })
|
||||||
|
countryCode: string;
|
||||||
|
|
||||||
|
@ManyToOne(() => SqliteUsers, (users) => users.visitedRegions, {
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
})
|
||||||
|
@JoinColumn([{ name: 'user_id', referencedColumnName: 'id' }])
|
||||||
|
user: SqliteUsers;
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
/**
|
||||||
|
* AUTOGENERATED
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
Column,
|
||||||
|
Entity,
|
||||||
|
Index,
|
||||||
|
JoinColumn,
|
||||||
|
ManyToOne,
|
||||||
|
PrimaryColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { User } from './User';
|
||||||
|
|
||||||
|
@Entity('account')
|
||||||
|
export class Account {
|
||||||
|
@PrimaryColumn('text')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Column('text', { name: 'accountId' })
|
||||||
|
accountId: string;
|
||||||
|
|
||||||
|
@Column('text', { name: 'providerId' })
|
||||||
|
providerId: string;
|
||||||
|
|
||||||
|
@Index('account_userId_idx')
|
||||||
|
@Column('text', { name: 'userId' })
|
||||||
|
userId: string;
|
||||||
|
|
||||||
|
@ManyToOne(() => User, { onDelete: 'CASCADE', nullable: false })
|
||||||
|
@JoinColumn({ name: 'userId', referencedColumnName: 'id' })
|
||||||
|
user: User;
|
||||||
|
|
||||||
|
@Column('text', { name: 'accessToken', nullable: true })
|
||||||
|
accessToken: string | null;
|
||||||
|
|
||||||
|
@Column('text', { name: 'refreshToken', nullable: true })
|
||||||
|
refreshToken: string | null;
|
||||||
|
|
||||||
|
@Column('text', { name: 'idToken', nullable: true })
|
||||||
|
idToken: string | null;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp', name: 'accessTokenExpiresAt', nullable: true })
|
||||||
|
accessTokenExpiresAt: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp', name: 'refreshTokenExpiresAt', nullable: true })
|
||||||
|
refreshTokenExpiresAt: Date | null;
|
||||||
|
|
||||||
|
@Column('text', { name: 'scope', nullable: true })
|
||||||
|
scope: string | null;
|
||||||
|
|
||||||
|
@Column('text', { name: 'password', nullable: true })
|
||||||
|
password: string | null;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp', name: 'createdAt' })
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp', name: 'updatedAt' })
|
||||||
|
updatedAt: Date;
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* AUTOGENERATED
|
||||||
|
*/
|
||||||
|
import { Column, Entity, PrimaryColumn } from 'typeorm';
|
||||||
|
|
||||||
|
@Entity('jwks')
|
||||||
|
export class Jwks {
|
||||||
|
@PrimaryColumn('text')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Column('text', { name: 'publicKey' })
|
||||||
|
publicKey: string;
|
||||||
|
|
||||||
|
@Column('text', { name: 'privateKey' })
|
||||||
|
privateKey: string;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp', name: 'createdAt' })
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp', name: 'expiresAt', nullable: true })
|
||||||
|
expiresAt: Date | null;
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
/**
|
||||||
|
* AUTOGENERATED
|
||||||
|
*/
|
||||||
|
import { Column, Entity, JoinColumn, ManyToOne, PrimaryColumn } from 'typeorm';
|
||||||
|
import { OauthClient } from './OauthClient';
|
||||||
|
import { OauthRefreshToken } from './OauthRefreshToken';
|
||||||
|
import { Session } from './Session';
|
||||||
|
import { User } from './User';
|
||||||
|
|
||||||
|
@Entity('oauthAccessToken')
|
||||||
|
export class OauthAccessToken {
|
||||||
|
@PrimaryColumn('text')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Column('text', { name: 'token', nullable: true, unique: true })
|
||||||
|
token: string | null;
|
||||||
|
|
||||||
|
@Column('text', { name: 'clientId' })
|
||||||
|
clientId: string;
|
||||||
|
|
||||||
|
@ManyToOne(() => OauthClient, { onDelete: 'CASCADE', nullable: false })
|
||||||
|
@JoinColumn({ name: 'clientId', referencedColumnName: 'clientId' })
|
||||||
|
client: OauthClient;
|
||||||
|
|
||||||
|
@Column('text', { name: 'sessionId', nullable: true })
|
||||||
|
sessionId: string | null;
|
||||||
|
|
||||||
|
@ManyToOne(() => Session, { onDelete: 'SET NULL', nullable: true })
|
||||||
|
@JoinColumn({ name: 'sessionId', referencedColumnName: 'id' })
|
||||||
|
session?: Session;
|
||||||
|
|
||||||
|
@Column('text', { name: 'userId', nullable: true })
|
||||||
|
userId: string | null;
|
||||||
|
|
||||||
|
@ManyToOne(() => User, { onDelete: 'CASCADE', nullable: true })
|
||||||
|
@JoinColumn({ name: 'userId', referencedColumnName: 'id' })
|
||||||
|
user?: User;
|
||||||
|
|
||||||
|
@Column('text', { name: 'referenceId', nullable: true })
|
||||||
|
referenceId: string | null;
|
||||||
|
|
||||||
|
@Column('text', { name: 'refreshId', nullable: true })
|
||||||
|
refreshId: string | null;
|
||||||
|
|
||||||
|
@ManyToOne(() => OauthRefreshToken, { onDelete: 'CASCADE', nullable: true })
|
||||||
|
@JoinColumn({ name: 'refreshId', referencedColumnName: 'id' })
|
||||||
|
refresh?: OauthRefreshToken;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp', name: 'expiresAt', nullable: true })
|
||||||
|
expiresAt: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp', name: 'createdAt', nullable: true })
|
||||||
|
createdAt: Date | null;
|
||||||
|
|
||||||
|
@Column('text', { name: 'scopes' })
|
||||||
|
scopes: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
/**
|
||||||
|
* AUTOGENERATED
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
Column,
|
||||||
|
Entity,
|
||||||
|
Index,
|
||||||
|
JoinColumn,
|
||||||
|
ManyToOne,
|
||||||
|
PrimaryColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { User } from './User';
|
||||||
|
|
||||||
|
@Entity('oauthApplication')
|
||||||
|
export class OauthApplication {
|
||||||
|
@PrimaryColumn('text')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Column('text', { name: 'name', nullable: true })
|
||||||
|
name: string | null;
|
||||||
|
|
||||||
|
@Column('text', { name: 'icon', nullable: true })
|
||||||
|
icon: string | null;
|
||||||
|
|
||||||
|
@Column('text', { name: 'metadata', nullable: true })
|
||||||
|
metadata: string | null;
|
||||||
|
|
||||||
|
@Column('text', { name: 'clientId', nullable: true, unique: true })
|
||||||
|
clientId: string | null;
|
||||||
|
|
||||||
|
@Column('text', { name: 'clientSecret', nullable: true })
|
||||||
|
clientSecret: string | null;
|
||||||
|
|
||||||
|
@Column('text', { name: 'redirectUrls', nullable: true })
|
||||||
|
redirectUrls: string | null;
|
||||||
|
|
||||||
|
@Column('text', { name: 'type', nullable: true })
|
||||||
|
type: string | null;
|
||||||
|
|
||||||
|
@Column('boolean', { name: 'disabled', nullable: true, default: false })
|
||||||
|
disabled: boolean | null;
|
||||||
|
|
||||||
|
@Index('oauthApplication_userId_idx')
|
||||||
|
@Column('text', { name: 'userId', nullable: true })
|
||||||
|
userId: string | null;
|
||||||
|
|
||||||
|
@ManyToOne(() => User, { onDelete: 'CASCADE', nullable: true })
|
||||||
|
@JoinColumn({ name: 'userId', referencedColumnName: 'id' })
|
||||||
|
user?: User;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp', name: 'createdAt', nullable: true })
|
||||||
|
createdAt: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp', name: 'updatedAt', nullable: true })
|
||||||
|
updatedAt: Date | null;
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
/**
|
||||||
|
* AUTOGENERATED
|
||||||
|
*/
|
||||||
|
import { Column, Entity, JoinColumn, ManyToOne, PrimaryColumn } from 'typeorm';
|
||||||
|
import { User } from './User';
|
||||||
|
|
||||||
|
@Entity('oauthClient')
|
||||||
|
export class OauthClient {
|
||||||
|
@PrimaryColumn('text')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Column('text', { name: 'clientId', unique: true })
|
||||||
|
clientId: string;
|
||||||
|
|
||||||
|
@Column('text', { name: 'clientSecret', nullable: true })
|
||||||
|
clientSecret: string | null;
|
||||||
|
|
||||||
|
@Column('boolean', { name: 'disabled', nullable: true, default: false })
|
||||||
|
disabled: boolean | null;
|
||||||
|
|
||||||
|
@Column('boolean', { name: 'skipConsent', nullable: true })
|
||||||
|
skipConsent: boolean | null;
|
||||||
|
|
||||||
|
@Column('boolean', { name: 'enableEndSession', nullable: true })
|
||||||
|
enableEndSession: boolean | null;
|
||||||
|
|
||||||
|
@Column('text', { name: 'subjectType', nullable: true })
|
||||||
|
subjectType: string | null;
|
||||||
|
|
||||||
|
@Column('text', { name: 'scopes', nullable: true })
|
||||||
|
scopes: string | null;
|
||||||
|
|
||||||
|
@Column('text', { name: 'userId', nullable: true })
|
||||||
|
userId: string | null;
|
||||||
|
|
||||||
|
@ManyToOne(() => User, { onDelete: 'CASCADE', nullable: true })
|
||||||
|
@JoinColumn({ name: 'userId', referencedColumnName: 'id' })
|
||||||
|
user?: User;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp', name: 'createdAt', nullable: true })
|
||||||
|
createdAt: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp', name: 'updatedAt', nullable: true })
|
||||||
|
updatedAt: Date | null;
|
||||||
|
|
||||||
|
@Column('text', { name: 'name', nullable: true })
|
||||||
|
name: string | null;
|
||||||
|
|
||||||
|
@Column('text', { name: 'uri', nullable: true })
|
||||||
|
uri: string | null;
|
||||||
|
|
||||||
|
@Column('text', { name: 'icon', nullable: true })
|
||||||
|
icon: string | null;
|
||||||
|
|
||||||
|
@Column('text', { name: 'contacts', nullable: true })
|
||||||
|
contacts: string | null;
|
||||||
|
|
||||||
|
@Column('text', { name: 'tos', nullable: true })
|
||||||
|
tos: string | null;
|
||||||
|
|
||||||
|
@Column('text', { name: 'policy', nullable: true })
|
||||||
|
policy: string | null;
|
||||||
|
|
||||||
|
@Column('text', { name: 'softwareId', nullable: true })
|
||||||
|
softwareId: string | null;
|
||||||
|
|
||||||
|
@Column('text', { name: 'softwareVersion', nullable: true })
|
||||||
|
softwareVersion: string | null;
|
||||||
|
|
||||||
|
@Column('text', { name: 'softwareStatement', nullable: true })
|
||||||
|
softwareStatement: string | null;
|
||||||
|
|
||||||
|
@Column('text', { name: 'redirectUris' })
|
||||||
|
redirectUris: string;
|
||||||
|
|
||||||
|
@Column('text', { name: 'postLogoutRedirectUris', nullable: true })
|
||||||
|
postLogoutRedirectUris: string | null;
|
||||||
|
|
||||||
|
@Column('text', { name: 'tokenEndpointAuthMethod', nullable: true })
|
||||||
|
tokenEndpointAuthMethod: string | null;
|
||||||
|
|
||||||
|
@Column('text', { name: 'grantTypes', nullable: true })
|
||||||
|
grantTypes: string | null;
|
||||||
|
|
||||||
|
@Column('text', { name: 'responseTypes', nullable: true })
|
||||||
|
responseTypes: string | null;
|
||||||
|
|
||||||
|
@Column('boolean', { name: 'public', nullable: true })
|
||||||
|
public: boolean | null;
|
||||||
|
|
||||||
|
@Column('text', { name: 'type', nullable: true })
|
||||||
|
type: string | null;
|
||||||
|
|
||||||
|
@Column('boolean', { name: 'requirePKCE', nullable: true })
|
||||||
|
requirePKCE: boolean | null;
|
||||||
|
|
||||||
|
@Column('text', { name: 'referenceId', nullable: true })
|
||||||
|
referenceId: string | null;
|
||||||
|
|
||||||
|
@Column('text', { name: 'metadata', nullable: true })
|
||||||
|
metadata: string | null;
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* AUTOGENERATED
|
||||||
|
*/
|
||||||
|
import { Column, Entity, JoinColumn, ManyToOne, PrimaryColumn } from 'typeorm';
|
||||||
|
import { OauthClient } from './OauthClient';
|
||||||
|
import { User } from './User';
|
||||||
|
|
||||||
|
@Entity('oauthConsent')
|
||||||
|
export class OauthConsent {
|
||||||
|
@PrimaryColumn('text')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Column('text', { name: 'clientId' })
|
||||||
|
clientId: string;
|
||||||
|
|
||||||
|
@ManyToOne(() => OauthClient, { onDelete: 'CASCADE', nullable: false })
|
||||||
|
@JoinColumn({ name: 'clientId', referencedColumnName: 'clientId' })
|
||||||
|
client: OauthClient;
|
||||||
|
|
||||||
|
@Column('text', { name: 'userId', nullable: true })
|
||||||
|
userId: string | null;
|
||||||
|
|
||||||
|
@ManyToOne(() => User, { onDelete: 'CASCADE', nullable: true })
|
||||||
|
@JoinColumn({ name: 'userId', referencedColumnName: 'id' })
|
||||||
|
user?: User;
|
||||||
|
|
||||||
|
@Column('text', { name: 'referenceId', nullable: true })
|
||||||
|
referenceId: string | null;
|
||||||
|
|
||||||
|
@Column('text', { name: 'scopes' })
|
||||||
|
scopes: string;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp', name: 'createdAt', nullable: true })
|
||||||
|
createdAt: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp', name: 'updatedAt', nullable: true })
|
||||||
|
updatedAt: Date | null;
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
/**
|
||||||
|
* AUTOGENERATED
|
||||||
|
*/
|
||||||
|
import { Column, Entity, JoinColumn, ManyToOne, PrimaryColumn } from 'typeorm';
|
||||||
|
import { OauthClient } from './OauthClient';
|
||||||
|
import { Session } from './Session';
|
||||||
|
import { User } from './User';
|
||||||
|
|
||||||
|
@Entity('oauthRefreshToken')
|
||||||
|
export class OauthRefreshToken {
|
||||||
|
@PrimaryColumn('text')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Column('text', { name: 'token', unique: true })
|
||||||
|
token: string;
|
||||||
|
|
||||||
|
@Column('text', { name: 'clientId' })
|
||||||
|
clientId: string;
|
||||||
|
|
||||||
|
@ManyToOne(() => OauthClient, { onDelete: 'CASCADE', nullable: false })
|
||||||
|
@JoinColumn({ name: 'clientId', referencedColumnName: 'clientId' })
|
||||||
|
client: OauthClient;
|
||||||
|
|
||||||
|
@Column('text', { name: 'sessionId', nullable: true })
|
||||||
|
sessionId: string | null;
|
||||||
|
|
||||||
|
@ManyToOne(() => Session, { onDelete: 'SET NULL', nullable: true })
|
||||||
|
@JoinColumn({ name: 'sessionId', referencedColumnName: 'id' })
|
||||||
|
session?: Session;
|
||||||
|
|
||||||
|
@Column('text', { name: 'userId' })
|
||||||
|
userId: string;
|
||||||
|
|
||||||
|
@ManyToOne(() => User, { onDelete: 'CASCADE', nullable: false })
|
||||||
|
@JoinColumn({ name: 'userId', referencedColumnName: 'id' })
|
||||||
|
user: User;
|
||||||
|
|
||||||
|
@Column('text', { name: 'referenceId', nullable: true })
|
||||||
|
referenceId: string | null;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp', name: 'expiresAt', nullable: true })
|
||||||
|
expiresAt: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp', name: 'createdAt', nullable: true })
|
||||||
|
createdAt: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp', name: 'revoked', nullable: true })
|
||||||
|
revoked: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp', name: 'authTime', nullable: true })
|
||||||
|
authTime: Date | null;
|
||||||
|
|
||||||
|
@Column('text', { name: 'scopes' })
|
||||||
|
scopes: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
/**
|
||||||
|
* AUTOGENERATED
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
Column,
|
||||||
|
Entity,
|
||||||
|
Index,
|
||||||
|
JoinColumn,
|
||||||
|
ManyToOne,
|
||||||
|
PrimaryColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { User } from './User';
|
||||||
|
|
||||||
|
@Entity('passkey')
|
||||||
|
export class Passkey {
|
||||||
|
@PrimaryColumn('text')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Column('text', { name: 'name', nullable: true })
|
||||||
|
name: string | null;
|
||||||
|
|
||||||
|
@Column('text', { name: 'publicKey' })
|
||||||
|
publicKey: string;
|
||||||
|
|
||||||
|
@Index('passkey_userId_idx')
|
||||||
|
@Column('text', { name: 'userId' })
|
||||||
|
userId: string;
|
||||||
|
|
||||||
|
@ManyToOne(() => User, { onDelete: 'CASCADE', nullable: false })
|
||||||
|
@JoinColumn({ name: 'userId', referencedColumnName: 'id' })
|
||||||
|
user: User;
|
||||||
|
|
||||||
|
@Index('passkey_credentialID_idx')
|
||||||
|
@Column('text', { name: 'credentialID' })
|
||||||
|
credentialID: string;
|
||||||
|
|
||||||
|
@Column('integer', { name: 'counter' })
|
||||||
|
counter: number;
|
||||||
|
|
||||||
|
@Column('text', { name: 'deviceType' })
|
||||||
|
deviceType: string;
|
||||||
|
|
||||||
|
@Column('boolean', { name: 'backedUp' })
|
||||||
|
backedUp: boolean;
|
||||||
|
|
||||||
|
@Column('text', { name: 'transports', nullable: true })
|
||||||
|
transports: string | null;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp', name: 'createdAt', nullable: true })
|
||||||
|
createdAt: Date | null;
|
||||||
|
|
||||||
|
@Column('text', { name: 'aaguid', nullable: true })
|
||||||
|
aaguid: string | null;
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* AUTOGENERATED
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
Column,
|
||||||
|
Entity,
|
||||||
|
Index,
|
||||||
|
JoinColumn,
|
||||||
|
ManyToOne,
|
||||||
|
PrimaryColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { User } from './User';
|
||||||
|
|
||||||
|
@Entity('session')
|
||||||
|
export class Session {
|
||||||
|
@PrimaryColumn('text')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp', name: 'expiresAt' })
|
||||||
|
expiresAt: Date;
|
||||||
|
|
||||||
|
@Column('text', { name: 'token', unique: true })
|
||||||
|
token: string;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp', name: 'createdAt' })
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp', name: 'updatedAt' })
|
||||||
|
updatedAt: Date;
|
||||||
|
|
||||||
|
@Column('text', { name: 'ipAddress', nullable: true })
|
||||||
|
ipAddress: string | null;
|
||||||
|
|
||||||
|
@Column('text', { name: 'userAgent', nullable: true })
|
||||||
|
userAgent: string | null;
|
||||||
|
|
||||||
|
@Index('session_userId_idx')
|
||||||
|
@Column('text', { name: 'userId' })
|
||||||
|
userId: string;
|
||||||
|
|
||||||
|
@ManyToOne(() => User, { onDelete: 'CASCADE', nullable: false })
|
||||||
|
@JoinColumn({ name: 'userId', referencedColumnName: 'id' })
|
||||||
|
user: User;
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* AUTOGENERATED
|
||||||
|
*/
|
||||||
|
import { Column, Entity, PrimaryColumn } from 'typeorm';
|
||||||
|
|
||||||
|
@Entity('user')
|
||||||
|
export class User {
|
||||||
|
@PrimaryColumn('text')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Column('text', { name: 'name' })
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@Column('text', { name: 'email', unique: true })
|
||||||
|
email: string;
|
||||||
|
|
||||||
|
@Column('boolean', { name: 'emailVerified', default: false })
|
||||||
|
emailVerified: boolean;
|
||||||
|
|
||||||
|
@Column('text', { name: 'image', nullable: true })
|
||||||
|
image: string | null;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp', name: 'createdAt' })
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp', name: 'updatedAt' })
|
||||||
|
updatedAt: Date;
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* AUTOGENERATED
|
||||||
|
*/
|
||||||
|
import { Column, Entity, Index, PrimaryColumn } from 'typeorm';
|
||||||
|
|
||||||
|
@Entity('verification')
|
||||||
|
export class Verification {
|
||||||
|
@PrimaryColumn('text')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Index('verification_identifier_idx')
|
||||||
|
@Column('text', { name: 'identifier' })
|
||||||
|
identifier: string;
|
||||||
|
|
||||||
|
@Column('text', { name: 'value' })
|
||||||
|
value: string;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp', name: 'expiresAt' })
|
||||||
|
expiresAt: Date;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp', name: 'createdAt' })
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp', name: 'updatedAt' })
|
||||||
|
updatedAt: Date;
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import {
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
DeleteDateColumn,
|
||||||
|
PrimaryColumn,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
UpdateDateColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
// Layer 1 — timestamps only, always present
|
||||||
|
export abstract class TimestampedEntity {
|
||||||
|
@CreateDateColumn({ name: 'created_at' })
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@UpdateDateColumn({ name: 'updated_at' })
|
||||||
|
updatedAt: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class TimestampedSortableEntity extends TimestampedEntity {
|
||||||
|
@Column('int', { name: 'sort_order', default: 0 })
|
||||||
|
sortOrder: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Layer 2 — adds soft delete
|
||||||
|
export abstract class SoftDeletableEntity extends TimestampedEntity {
|
||||||
|
@DeleteDateColumn({ name: 'deleted_at', nullable: true })
|
||||||
|
deletedAt: Date | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Layer 3a — adds integer PK to timestamped entity
|
||||||
|
export abstract class IntBaseEntity extends TimestampedEntity {
|
||||||
|
@PrimaryGeneratedColumn()
|
||||||
|
id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class IntSortableBaseEntity extends IntBaseEntity {
|
||||||
|
@Column('int', { name: 'sort_order', default: 0 })
|
||||||
|
sortOrder: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Layer 3b — adds integer PK + soft delete
|
||||||
|
export abstract class SoftDeletableIntBaseEntity extends SoftDeletableEntity {
|
||||||
|
@PrimaryGeneratedColumn()
|
||||||
|
id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Layer 3c — natural/business string PK (e.g. country code, slug)
|
||||||
|
export abstract class StringBaseEntity extends TimestampedEntity {
|
||||||
|
@PrimaryColumn({ name: 'id' })
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class StringSortableBaseEntity extends StringBaseEntity {
|
||||||
|
@Column('int', { name: 'sort_order', default: 0 })
|
||||||
|
sortOrder: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Layer 3d — natural string PK + soft delete
|
||||||
|
export abstract class SoftDeletableStringBaseEntity extends SoftDeletableEntity {
|
||||||
|
@PrimaryColumn({ name: 'id' })
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm';
|
||||||
|
import { IntBaseEntity } from '../base/BaseEntity';
|
||||||
|
import { SqliteUsers } from '../old-entities/SqliteUsers';
|
||||||
|
|
||||||
|
@Index('idx_mcp_tokens_hash', ['tokenHash'], { unique: true })
|
||||||
|
@Entity('mcp_tokens')
|
||||||
|
export class McpTokens extends IntBaseEntity {
|
||||||
|
@Column({ name: 'name' })
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@Column({ name: 'token_hash', unique: true })
|
||||||
|
tokenHash: string;
|
||||||
|
|
||||||
|
@Column({ name: 'token_prefix' })
|
||||||
|
tokenPrefix: string;
|
||||||
|
|
||||||
|
@Column({ name: 'last_used_at', nullable: true })
|
||||||
|
lastUsedAt: Date | null;
|
||||||
|
|
||||||
|
@ManyToOne(() => SqliteUsers, (users) => users.mcpTokens, {
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
})
|
||||||
|
@JoinColumn([{ name: 'user_id', referencedColumnName: 'id' }])
|
||||||
|
user: SqliteUsers;
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import {
|
||||||
|
Column,
|
||||||
|
Entity,
|
||||||
|
Index,
|
||||||
|
JoinColumn,
|
||||||
|
ManyToOne,
|
||||||
|
PrimaryColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { SqliteUsers } from '../old-entities/SqliteUsers';
|
||||||
|
|
||||||
|
@Index('idx_ncp_user', ['userId'], {})
|
||||||
|
@Entity('notification_channel_preferences')
|
||||||
|
export class NotificationChannelPreferences {
|
||||||
|
@PrimaryColumn('int', { name: 'user_id' })
|
||||||
|
userId: number;
|
||||||
|
|
||||||
|
@PrimaryColumn({ name: 'event_type' })
|
||||||
|
eventType: string;
|
||||||
|
|
||||||
|
@PrimaryColumn({ name: 'channel' })
|
||||||
|
channel: string;
|
||||||
|
|
||||||
|
@Column({ name: 'enabled', default: true })
|
||||||
|
enabled: boolean;
|
||||||
|
|
||||||
|
@ManyToOne(
|
||||||
|
() => SqliteUsers,
|
||||||
|
(users) => users.notificationChannelPreferences,
|
||||||
|
{
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
@JoinColumn([{ name: 'user_id', referencedColumnName: 'id' }])
|
||||||
|
user: SqliteUsers;
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm';
|
||||||
|
import { IntBaseEntity } from '../base/BaseEntity';
|
||||||
|
import { SqliteUsers } from '../old-entities/SqliteUsers';
|
||||||
|
|
||||||
|
@Index('idx_notifications_target_scope', ['target', 'scope'], {})
|
||||||
|
@Index('idx_notifications_recipient_created', ['recipientId', 'createdAt'], {})
|
||||||
|
@Index(
|
||||||
|
'idx_notifications_recipient',
|
||||||
|
['recipientId', 'isRead', 'createdAt'],
|
||||||
|
{},
|
||||||
|
)
|
||||||
|
@Entity('notifications')
|
||||||
|
export class Notifications extends IntBaseEntity {
|
||||||
|
@Column({ name: 'type' })
|
||||||
|
type: string;
|
||||||
|
|
||||||
|
@Column({ name: 'scope' })
|
||||||
|
scope: string;
|
||||||
|
|
||||||
|
@Column('int', { name: 'target' })
|
||||||
|
target: number;
|
||||||
|
|
||||||
|
@Column('int', { name: 'recipient_id' })
|
||||||
|
recipientId: number;
|
||||||
|
|
||||||
|
@Column({ name: 'title_key' })
|
||||||
|
titleKey: string;
|
||||||
|
|
||||||
|
@Column('simple-json', {
|
||||||
|
name: 'title_params',
|
||||||
|
nullable: true,
|
||||||
|
default: {},
|
||||||
|
})
|
||||||
|
titleParams: Record<string, unknown> | null;
|
||||||
|
|
||||||
|
@Column({ name: 'text_key' })
|
||||||
|
textKey: string;
|
||||||
|
|
||||||
|
@Column('simple-json', {
|
||||||
|
name: 'text_params',
|
||||||
|
nullable: true,
|
||||||
|
default: {},
|
||||||
|
})
|
||||||
|
textParams: Record<string, unknown> | null;
|
||||||
|
|
||||||
|
@Column({ name: 'positive_text_key', nullable: true })
|
||||||
|
positiveTextKey: string | null;
|
||||||
|
|
||||||
|
@Column({ name: 'negative_text_key', nullable: true })
|
||||||
|
negativeTextKey: string | null;
|
||||||
|
|
||||||
|
@Column({ name: 'positive_callback', nullable: true })
|
||||||
|
positiveCallback: string | null;
|
||||||
|
|
||||||
|
@Column({ name: 'negative_callback', nullable: true })
|
||||||
|
negativeCallback: string | null;
|
||||||
|
|
||||||
|
@Column({ name: 'response', nullable: true })
|
||||||
|
response: string | null;
|
||||||
|
|
||||||
|
@Column({ name: 'navigate_text_key', nullable: true })
|
||||||
|
navigateTextKey: string | null;
|
||||||
|
|
||||||
|
@Column({ name: 'navigate_target', nullable: true })
|
||||||
|
navigateTarget: string | null;
|
||||||
|
|
||||||
|
@Column({ name: 'is_read', nullable: true, default: false })
|
||||||
|
isRead: boolean | null;
|
||||||
|
|
||||||
|
@ManyToOne(() => SqliteUsers, (users) => users.notifications, {
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
})
|
||||||
|
@JoinColumn([{ name: 'recipient_id', referencedColumnName: 'id' }])
|
||||||
|
recipient: SqliteUsers;
|
||||||
|
|
||||||
|
@ManyToOne(() => SqliteUsers, (users) => users.notifications2, {
|
||||||
|
onDelete: 'SET NULL',
|
||||||
|
})
|
||||||
|
@JoinColumn([{ name: 'sender_id', referencedColumnName: 'id' }])
|
||||||
|
sender: SqliteUsers;
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import {
|
||||||
|
Column,
|
||||||
|
Entity,
|
||||||
|
Index,
|
||||||
|
JoinColumn,
|
||||||
|
ManyToOne,
|
||||||
|
OneToMany,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { OldOauthConsents } from './OldOauthConsents';
|
||||||
|
import { OldOauthTokens } from './OldOauthTokens';
|
||||||
|
import { SqliteUsers } from './SqliteUsers';
|
||||||
|
import { StringBaseEntity } from '../base/BaseEntity';
|
||||||
|
|
||||||
|
@Index('idx_oauth_clients_client_id', ['clientId'], { unique: true })
|
||||||
|
@Index('idx_oauth_clients_user', ['userId'], {})
|
||||||
|
@Entity('oauth_clients')
|
||||||
|
export class OldOauthClients extends StringBaseEntity {
|
||||||
|
@Column('int', { name: 'user_id', nullable: true })
|
||||||
|
userId: number | null;
|
||||||
|
|
||||||
|
@Column({ name: 'name' })
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@Column({ name: 'client_id', unique: true })
|
||||||
|
clientId: string;
|
||||||
|
|
||||||
|
@Column({ name: 'client_secret_hash' })
|
||||||
|
clientSecretHash: string;
|
||||||
|
|
||||||
|
@Column('simple-array', { name: 'redirect_uris', default: [] })
|
||||||
|
redirectUris: string[];
|
||||||
|
|
||||||
|
@Column('simple-array', { name: 'allowed_scopes', default: [] })
|
||||||
|
allowedScopes: string[];
|
||||||
|
|
||||||
|
@Column({ name: 'is_public', default: false })
|
||||||
|
isPublic: boolean;
|
||||||
|
|
||||||
|
@Column({ name: 'created_via', default: 'settings_ui' })
|
||||||
|
createdVia: string;
|
||||||
|
|
||||||
|
@OneToMany(() => OldOauthConsents, (oauthConsents) => oauthConsents.client)
|
||||||
|
oauthConsents: OldOauthConsents[];
|
||||||
|
|
||||||
|
@OneToMany(() => OldOauthTokens, (oauthTokens) => oauthTokens.client)
|
||||||
|
oauthTokens: OldOauthTokens[];
|
||||||
|
|
||||||
|
@ManyToOne(() => SqliteUsers, (users) => users.oauthClients, {
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
})
|
||||||
|
@JoinColumn([{ name: 'user_id', referencedColumnName: 'id' }])
|
||||||
|
user: SqliteUsers;
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { Column, Entity, JoinColumn, ManyToOne } from 'typeorm';
|
||||||
|
import { SqliteUsers } from './SqliteUsers';
|
||||||
|
import { OldOauthClients } from './OldOauthClients';
|
||||||
|
import { IntBaseEntity } from '../base/BaseEntity';
|
||||||
|
|
||||||
|
@Entity('oauth_consents')
|
||||||
|
export class OldOauthConsents extends IntBaseEntity {
|
||||||
|
@Column('simple-array', { name: 'scopes', default: [] })
|
||||||
|
scopes: string[];
|
||||||
|
|
||||||
|
@ManyToOne(() => SqliteUsers, (users) => users.oauthConsents, {
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
})
|
||||||
|
@JoinColumn([{ name: 'user_id', referencedColumnName: 'id' }])
|
||||||
|
user: SqliteUsers;
|
||||||
|
|
||||||
|
@ManyToOne(
|
||||||
|
() => OldOauthClients,
|
||||||
|
(oauthClients) => oauthClients.oauthConsents,
|
||||||
|
{
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
@JoinColumn([{ name: 'client_id', referencedColumnName: 'clientId' }])
|
||||||
|
client: OldOauthClients;
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import {
|
||||||
|
Column,
|
||||||
|
Entity,
|
||||||
|
Index,
|
||||||
|
JoinColumn,
|
||||||
|
ManyToOne,
|
||||||
|
OneToMany,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { SqliteUsers } from './SqliteUsers';
|
||||||
|
import { OldOauthClients } from './OldOauthClients';
|
||||||
|
import { IntBaseEntity } from '../base/BaseEntity';
|
||||||
|
|
||||||
|
@Index('idx_oauth_tokens_parent', ['parentTokenId'], {})
|
||||||
|
@Index('idx_oauth_tokens_refresh', ['refreshTokenHash'], { unique: true })
|
||||||
|
@Index('idx_oauth_tokens_access', ['accessTokenHash'], { unique: true })
|
||||||
|
@Index('idx_oauth_tokens_user', ['userId'], {})
|
||||||
|
@Entity('oauth_tokens')
|
||||||
|
export class OldOauthTokens extends IntBaseEntity {
|
||||||
|
@Column('int', { name: 'user_id' })
|
||||||
|
userId: number;
|
||||||
|
|
||||||
|
@Column({ name: 'access_token_hash', unique: true })
|
||||||
|
accessTokenHash: string;
|
||||||
|
|
||||||
|
@Column({ name: 'refresh_token_hash', unique: true })
|
||||||
|
refreshTokenHash: string;
|
||||||
|
|
||||||
|
@Column('simple-array', { name: 'scopes', default: [] })
|
||||||
|
scopes: string[];
|
||||||
|
|
||||||
|
@Column('datetime', { name: 'access_token_expires_at' })
|
||||||
|
accessTokenExpiresAt: Date;
|
||||||
|
|
||||||
|
@Column('datetime', { name: 'refresh_token_expires_at' })
|
||||||
|
refreshTokenExpiresAt: Date;
|
||||||
|
|
||||||
|
@Column('datetime', { name: 'revoked_at', nullable: true })
|
||||||
|
revokedAt: Date | null;
|
||||||
|
|
||||||
|
@Column('int', { name: 'parent_token_id', nullable: true })
|
||||||
|
parentTokenId: number | null;
|
||||||
|
|
||||||
|
@Column({ name: 'audience', nullable: true })
|
||||||
|
audience: string | null;
|
||||||
|
|
||||||
|
@ManyToOne(() => OldOauthTokens, (oauthTokens) => oauthTokens.oauthTokens)
|
||||||
|
@JoinColumn([{ name: 'parent_token_id', referencedColumnName: 'id' }])
|
||||||
|
parentToken: OldOauthTokens;
|
||||||
|
|
||||||
|
@OneToMany(() => OldOauthTokens, (oauthTokens) => oauthTokens.parentToken)
|
||||||
|
oauthTokens: OldOauthTokens[];
|
||||||
|
|
||||||
|
@ManyToOne(() => SqliteUsers, (users) => users.oauthTokens, {
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
})
|
||||||
|
@JoinColumn([{ name: 'user_id', referencedColumnName: 'id' }])
|
||||||
|
user: SqliteUsers;
|
||||||
|
|
||||||
|
@ManyToOne(
|
||||||
|
() => OldOauthClients,
|
||||||
|
(oauthClients) => oauthClients.oauthTokens,
|
||||||
|
{
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
@JoinColumn([{ name: 'client_id', referencedColumnName: 'clientId' }])
|
||||||
|
client: OldOauthClients;
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user