mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-19 21:31:46 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 249ab217f8 | |||
| 499097fa3c | |||
| 002ea91be8 |
@@ -23,4 +23,4 @@ jobs:
|
||||
- name: Publish to GitHub wiki
|
||||
uses: Andrew-Chen-Wang/github-wiki-action@v5
|
||||
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` |
|
||||
| `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` |
|
||||
| `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 |
|
||||
| `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` |
|
||||
|
||||
+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
|
||||
name: trek
|
||||
version: 2.9.14
|
||||
version: 3.0.9
|
||||
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 }}
|
||||
FORCE_HTTPS: {{ .Values.env.FORCE_HTTPS | quote }}
|
||||
{{- end }}
|
||||
{{- if .Values.env.HSTS_INCLUDE_SUBDOMAINS }}
|
||||
HSTS_INCLUDE_SUBDOMAINS: {{ .Values.env.HSTS_INCLUDE_SUBDOMAINS | quote }}
|
||||
{{- end }}
|
||||
{{- if .Values.env.COOKIE_SECURE }}
|
||||
COOKIE_SECURE: {{ .Values.env.COOKIE_SECURE | quote }}
|
||||
{{- end }}
|
||||
|
||||
@@ -30,6 +30,8 @@ env:
|
||||
# Also used as the base URL for links in email notifications and other external links.
|
||||
# FORCE_HTTPS: "false"
|
||||
# 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"
|
||||
# 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"
|
||||
|
||||
Generated
+5
-5
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "trek-client",
|
||||
"version": "2.9.14",
|
||||
"version": "3.0.9",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "trek-client",
|
||||
"version": "2.9.14",
|
||||
"version": "3.0.9",
|
||||
"dependencies": {
|
||||
"@react-pdf/renderer": "^4.3.2",
|
||||
"axios": "^1.6.7",
|
||||
@@ -8907,9 +8907,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.9",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.9.tgz",
|
||||
"integrity": "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==",
|
||||
"version": "8.5.10",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz",
|
||||
"integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "trek-client",
|
||||
"version": "2.9.14",
|
||||
"version": "3.0.9",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -634,7 +634,7 @@ export default function BudgetPanel({ tripId, tripMembers = [] }: BudgetPanelPro
|
||||
}
|
||||
const handleRenameCategory = async (oldName, newName) => {
|
||||
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() })
|
||||
}
|
||||
const handleAddCategory = () => {
|
||||
|
||||
@@ -266,17 +266,22 @@ export default function DemoBanner(): React.ReactElement | null {
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
position: 'fixed', inset: 0, zIndex: 9999,
|
||||
position: 'fixed', inset: 0, zIndex: 99999,
|
||||
background: 'rgba(0,0,0,0.6)', backdropFilter: 'blur(8px)',
|
||||
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",
|
||||
}} onClick={() => setDismissed(true)}>
|
||||
<div style={{
|
||||
background: 'white', borderRadius: 20, padding: '28px 24px 20px',
|
||||
background: 'white', borderRadius: 20, padding: '28px 24px 0',
|
||||
maxWidth: 480, width: '100%',
|
||||
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()}>
|
||||
|
||||
{/* Header */}
|
||||
@@ -367,8 +372,10 @@ export default function DemoBanner(): React.ReactElement | null {
|
||||
|
||||
{/* Footer */}
|
||||
<div style={{
|
||||
paddingTop: 14, borderTop: '1px solid #e5e7eb',
|
||||
padding: '14px 0 20px', borderTop: '1px solid #e5e7eb',
|
||||
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' }}>
|
||||
<Github size={13} />
|
||||
|
||||
@@ -78,6 +78,7 @@ const transportReservation = {
|
||||
id: 400,
|
||||
title: 'Flight to Rome',
|
||||
type: 'flight',
|
||||
day_id: 10,
|
||||
reservation_time: '2025-06-01T14:30:00',
|
||||
confirmation_number: 'ABC123',
|
||||
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 || {})
|
||||
.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
|
||||
const daysHtml = sorted.map((day, di) => {
|
||||
const assigned = assignments[String(day.id)] || []
|
||||
const notes = (dayNotes || []).filter(n => n.day_id === day.id)
|
||||
const cost = dayCost(assignments, day.id, loc)
|
||||
|
||||
// Reservations for this day (hotel rendered via accommodations block)
|
||||
const dayReservations = (reservations || []).filter(r => {
|
||||
if (!r.reservation_time || r.type === 'hotel') return false
|
||||
return day.date && r.reservation_time.split('T')[0] === day.date
|
||||
})
|
||||
// Reservations for this day (hotel rendered via accommodations block; car middle-phase rendered in sidebar header only)
|
||||
const dayReservations = pdfGetTransportForDay(day.id)
|
||||
.filter(r => !(r.type === 'car' && pdfGetSpanPhase(r, day.id) === 'middle'))
|
||||
|
||||
const merged = []
|
||||
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 }))
|
||||
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.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 === 'tour') subtitle = [meta.operator].filter(Boolean).join(' · ')
|
||||
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 `
|
||||
<div class="note-card" style="border-left: 3px solid ${color};">
|
||||
<div class="note-line" style="background: ${color};"></div>
|
||||
<span class="note-icon">${icon}</span>
|
||||
<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>` : ''}
|
||||
${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>` : ''}
|
||||
|
||||
@@ -66,7 +66,11 @@ export default function DayDetailPanel({ day, days, places, categories = [], tri
|
||||
const isFahrenheit = useSettingsStore(s => s.settings.temperature_unit) === 'fahrenheit'
|
||||
const is12h = useSettingsStore(s => s.settings.time_format) === '12h'
|
||||
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 collapsed = collapsedProp
|
||||
const toggleCollapse = () => onToggleCollapse?.()
|
||||
|
||||
@@ -1576,7 +1576,10 @@ const DayPlanSidebar = React.memo(function DayPlanSidebar({
|
||||
{res.reservation_time?.includes('T') && (
|
||||
<span style={{ fontWeight: 400 }}>
|
||||
{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>
|
||||
)}
|
||||
{(() => {
|
||||
|
||||
@@ -182,6 +182,8 @@ export function ReservationModal({ isOpen, onClose, onSave, reservation, days, p
|
||||
let combinedEndTime = form.reservation_end_time
|
||||
if (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 (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={{ ...fieldValueStyle, textAlign: 'center' }}>
|
||||
{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)}</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -41,7 +41,7 @@ export default function ConfirmDialog({
|
||||
return (
|
||||
<div
|
||||
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}
|
||||
>
|
||||
<div
|
||||
|
||||
@@ -42,7 +42,7 @@ export default function CopyTripDialog({ isOpen, tripTitle, onClose, onConfirm }
|
||||
return (
|
||||
<div
|
||||
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}
|
||||
>
|
||||
<div
|
||||
|
||||
@@ -343,7 +343,10 @@ export default function TripPlannerPage(): React.ReactElement | null {
|
||||
}, [tripId])
|
||||
|
||||
useEffect(() => {
|
||||
if (tripId) tripActions.loadReservations(tripId)
|
||||
if (tripId) {
|
||||
tripActions.loadReservations(tripId)
|
||||
tripActions.loadBudgetItems?.(tripId)
|
||||
}
|
||||
}, [tripId])
|
||||
|
||||
useTripWebSocket(tripId)
|
||||
@@ -1106,7 +1109,7 @@ export default function TripPlannerPage(): React.ReactElement | null {
|
||||
</div>
|
||||
<div style={{ flex: 1, overflow: 'auto' }}>
|
||||
{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} />
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -355,6 +355,37 @@ describe('journeyStore', () => {
|
||||
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 ────────────────────────────────────────────────────────────────
|
||||
|
||||
it('FE-STORE-JOURNEY-015: clear resets state', () => {
|
||||
|
||||
@@ -223,10 +223,8 @@ export const useJourneyStore = create<JourneyState>((set, get) => ({
|
||||
)
|
||||
entries.sort((a, b) => {
|
||||
if (a.entry_date !== b.entry_date) return a.entry_date.localeCompare(b.entry_date)
|
||||
const atime = a.entry_time || ''
|
||||
const btime = b.entry_time || ''
|
||||
if (atime !== btime) return atime.localeCompare(btime)
|
||||
return (a.sort_order || 0) - (b.sort_order || 0)
|
||||
if (a.sort_order !== b.sort_order) return (a.sort_order || 0) - (b.sort_order || 0)
|
||||
return a.id - b.id
|
||||
})
|
||||
return { current: { ...s.current, entries } }
|
||||
})
|
||||
|
||||
@@ -32,6 +32,13 @@ function triggerAnchorDownload(blobUrl: string, filename?: string): void {
|
||||
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
|
||||
* 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
|
||||
* 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> {
|
||||
assertRelativeUrl(url)
|
||||
@@ -71,11 +84,19 @@ export async function openFile(url: string, filename?: string): Promise<void> {
|
||||
return
|
||||
}
|
||||
|
||||
const win = window.open(blobUrl, '_blank', 'noreferrer')
|
||||
if (win) {
|
||||
setTimeout(() => URL.revokeObjectURL(blobUrl), 30_000)
|
||||
} else {
|
||||
// Popup blocked — fall back to download
|
||||
// iOS PWA: target="_blank" would open Safari, which can't access the blob
|
||||
if (isIosStandalone()) {
|
||||
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', () => {
|
||||
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))
|
||||
const mockWin = { closed: false }
|
||||
const openSpy = vi.spyOn(window, 'open').mockReturnValue(mockWin as Window)
|
||||
const openSpy = vi.spyOn(window, 'open').mockReturnValue(null)
|
||||
const clickSpy = vi.spyOn(HTMLAnchorElement.prototype, 'click').mockImplementation(() => {})
|
||||
|
||||
await openFile('/uploads/files/doc.pdf')
|
||||
|
||||
expect(window.fetch).toHaveBeenCalledWith('/uploads/files/doc.pdf', { credentials: 'include' })
|
||||
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
|
||||
vi.runAllTimers()
|
||||
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.spyOn(window, 'open').mockReturnValue(null)
|
||||
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()
|
||||
vi.runAllTimers()
|
||||
expect(URL.revokeObjectURL).toHaveBeenCalledWith('blob:mock-url')
|
||||
// Exactly ONE anchor click — opening the new tab. No fallback download.
|
||||
expect(clickSpy).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('throws on 401 response', async () => {
|
||||
@@ -108,28 +118,55 @@ describe('openFile', () => {
|
||||
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' })
|
||||
vi.stubGlobal('fetch', makeFetchMock(200, htmlBlob))
|
||||
const openSpy = vi.spyOn(window, 'open').mockReturnValue({} as Window)
|
||||
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
|
||||
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 () => {
|
||||
const svgBlob = new Blob(['<svg><script>alert(1)</script></svg>'], { type: 'image/svg+xml' })
|
||||
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(() => {})
|
||||
|
||||
await openFile('/uploads/files/malicious.svg')
|
||||
|
||||
expect(window.open).not.toHaveBeenCalled()
|
||||
expect(clickSpy).toHaveBeenCalled()
|
||||
expect(openSpy).not.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
|
||||
- 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
|
||||
# - 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.
|
||||
# - 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.
|
||||
|
||||
@@ -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