This commit is contained in:
2024-09-02 20:44:47 +02:00
parent c89638262f
commit 5d00a5a090
28 changed files with 160 additions and 2122 deletions

View File

@@ -30,7 +30,7 @@ const { canCreateTabInCategory } = await useUserLimits()
<UDropdown <UDropdown
:items="dropdownItems" :items="dropdownItems"
:popper="{ placement: 'bottom-end', arrow: true }" :popper="{ placement: 'bottom-end', arrow: true }"
:ui="{ width: 'w-40', shadow: 'shadow-xl' }" :ui="{ container: 'group z-50', width: 'w-40', shadow: 'shadow-xl' }"
> >
<UButton <UButton
color="white" color="white"

View File

@@ -69,7 +69,7 @@ function visitLink() {
<span class="animate-ping absolute inline-flex h-full w-full rounded-full opacity-75" :class="`bg-${tab.color}-400`" /> <span class="animate-ping absolute inline-flex h-full w-full rounded-full opacity-75" :class="`bg-${tab.color}-400`" />
<span class="relative inline-flex rounded-full h-3 w-3" :class="`bg-${tab.color}-400`" /> <span class="relative inline-flex rounded-full h-3 w-3" :class="`bg-${tab.color}-400`" />
</div> </div>
<div class="flex items-center justify-between h-full"> <div class="flex items-center justify-between h-full z-20">
<div class="flex gap-4 items-center h-full"> <div class="flex gap-4 items-center h-full">
<UBadge :color="tab.color" class="p-2" variant="soft"> <UBadge :color="tab.color" class="p-2" variant="soft">
<UIcon :name="tab.icon" size="32" /> <UIcon :name="tab.icon" size="32" />
@@ -83,7 +83,7 @@ function visitLink() {
<UDropdown <UDropdown
:items="items" :items="items"
:popper="{ placement: 'bottom-end', arrow: true }" :popper="{ placement: 'bottom-end', arrow: true }"
:ui="{ container: 'z-50 group', width: 'w-40', shadow: 'shadow-2xl', wrapper: 'absolute inline-flex -top-3 -right-3' }" :ui="{ container: 'z-40 group', width: 'w-40', shadow: 'shadow-2xl', wrapper: 'absolute inline-flex -top-3 -right-3' }"
> >
<UButton <UButton
v-show="editMode" v-show="editMode"

View File

@@ -28,27 +28,18 @@ watchEffect(() => {
state.email = props.user.email state.email = props.user.email
}) })
async function handleUpdate(event: FormSubmitEvent<UpdateUserSchemaType>) { const { deleteAvatar, uploadAvatar, updateUser } = await useUser()
try {
await useRequestFetch()(`/api/users/me`, {
method: 'PUT',
body: JSON.stringify({
username: event.data.username,
name: event.data.name,
description: event.data.description,
location: event.data.location,
language: event.data.language,
private: event.data.private,
}),
})
useSuccessToast('Profile successfully updated!')
}
catch (error) {
useErrorToast('Profile update failed!', error as string)
}
}
const { deleteAvatar, uploadAvatar } = await useUser() async function handleUpdate(event: FormSubmitEvent<UpdateUserSchemaType>) {
await updateUser({
username: event.data.username,
name: event.data.name,
description: event.data.description,
location: event.data.location,
language: event.data.language,
private: event.data.private,
})
}
</script> </script>
<template> <template>

View File

@@ -1,45 +0,0 @@
<script lang="ts" setup>
const config = useRuntimeConfig()
const coordinates = ref<[number, number]>([2.179040, 48.877419])
const zoom = ref(11)
</script>
<template>
<ClientOnly>
<UCard :ui="{ base: 'h-72 md:col-span-2', body: { padding: '' } }">
<div class="relative">
<MapboxMap
:options="{
accessToken: config.public.mapbox.accessToken,
style: config.public.mapbox.style,
center: coordinates,
zoom,
projection: 'globe',
}"
class="absolute h-72"
map-id="map"
>
<MapboxDefaultMarker
:lnglat="coordinates"
:options="{
color: '#808080',
size: 1.5,
}"
marker-id="marker"
/>
</MapboxMap>
</div>
</UCard>
</ClientOnly>
</template>
<style>
.mapboxgl-control-container {
display: none !important;
}
.mapboxgl-canvas {
border-radius: 1rem;
}
</style>

View File

@@ -1,34 +0,0 @@
<script lang="ts" setup>
import type { WeatherType } from '~~/types/types'
const { data: weather } = await useFetch<WeatherType>('/api/weather')
</script>
<template>
<section>
<UCard v-if="weather" :ui="{ base: 'h-full' }">
<template #header>
<div class="flex items-center gap-2 text-blue-600 dark:text-blue-300">
<UIcon name="i-ph:cloud-duotone" size="24" />
<h3 class="font-bold text-lg">
Météo
</h3>
</div>
</template>
<template #default>
<div class="flex items-center h-full">
<p class="text-lg h-full">
Il fait actuellement
<span class="text-blue-600 dark:text-blue-300">{{ weather.weather }}</span>
à
<span>{{ weather.city }}</span>,
avec une température de
<span class="text-blue-600 dark:text-blue-300">{{ weather.temp }}
</span>
°C
</p>
</div>
</template>
</UCard>
</section>
</template>

View File

@@ -14,7 +14,7 @@ export async function useCategories() {
await useSuccessToast('Category successfully created!', category.color) await useSuccessToast('Category successfully created!', category.color)
} }
catch (error) { catch (error) {
useErrorToast('Category creation failed!', error as string) useErrorToast('Category creation failed!', String(error))
} }
} }
@@ -28,7 +28,7 @@ export async function useCategories() {
await useSuccessToast('Category successfully updated!') await useSuccessToast('Category successfully updated!')
} }
catch (error) { catch (error) {
useErrorToast('Category update failed!', error as string) useErrorToast('Category update failed!', String(error))
} }
} }
@@ -41,7 +41,7 @@ export async function useCategories() {
await useSuccessToast('Category successfully deleted!') await useSuccessToast('Category successfully deleted!')
} }
catch (error) { catch (error) {
useErrorToast('Category deletion failed!', error as string) useErrorToast('Category deletion failed!', String(error))
} }
} }

View File

@@ -18,7 +18,7 @@ export async function useTabs() {
useSuccessToast('Tab successfully created!', tab.color) useSuccessToast('Tab successfully created!', tab.color)
} }
catch (error) { catch (error) {
useErrorToast('Tab creation failed!', error as string) useErrorToast('Tab creation failed!', String(error))
} }
} }
@@ -32,7 +32,7 @@ export async function useTabs() {
useSuccessToast('Tab successfully updated!') useSuccessToast('Tab successfully updated!')
} }
catch (error) { catch (error) {
useErrorToast('Tab update failed!', error as string) useErrorToast('Tab update failed!', String(error))
} }
} }
@@ -49,7 +49,7 @@ export async function useTabs() {
useSuccessToast(`Tab ${tab.name} ${primary ? 'set as favorite' : 'unset as favorite'}!`, 'yellow') useSuccessToast(`Tab ${tab.name} ${primary ? 'set as favorite' : 'unset as favorite'}!`, 'yellow')
} }
catch (error) { catch (error) {
useErrorToast('Cannot toggle favorite state for tab!', error as string) useErrorToast('Cannot toggle favorite state for tab!', String(error))
} }
} }
@@ -62,7 +62,7 @@ export async function useTabs() {
useSuccessToast('Tab successfully deleted!') useSuccessToast('Tab successfully deleted!')
} }
catch (error) { catch (error) {
useErrorToast('Tab deletion failed!', error as string) useErrorToast('Tab deletion failed!', String(error))
} }
} }

View File

@@ -10,12 +10,16 @@ export async function useUserLimits() {
function canCreateCategory() { function canCreateCategory() {
if (hasPaidPlan.value) if (hasPaidPlan.value)
return true return true
if (!userLimits.value.categories)
return false
return userLimits.value.categories.length < MAX_CATEGORIES return userLimits.value.categories.length < MAX_CATEGORIES
} }
function canCreateTabInCategory(categoryId: number): boolean { function canCreateTabInCategory(categoryId: number): boolean {
if (hasPaidPlan.value) if (hasPaidPlan.value)
return true return true
if (!userLimits.value.categories || !userLimits.value.categories.find(category => category.id === categoryId))
return false
return userLimits.value.categories.find(category => category.id === categoryId).tabs.length < MAX_TABS_PER_CATEGORY return userLimits.value.categories.find(category => category.id === categoryId).tabs.length < MAX_TABS_PER_CATEGORY
} }

View File

@@ -1,16 +1,16 @@
export async function useUser() { export async function useUser() {
const { fetch } = useUserSession() const { fetch, session } = useUserSession()
async function deleteAvatar() { async function deleteAvatar() {
try { try {
await useRequestFetch()('/api/users/avatars', { await $fetch('/api/users/avatars', {
method: 'DELETE', method: 'DELETE',
}) })
useSuccessToast('Avatar successfully deleted!') useSuccessToast('Avatar successfully deleted!')
await fetch() await fetch()
} }
catch (error) { catch (error) {
useErrorToast('An error occurred while deleting your avatar', error as string) useErrorToast('An error occurred while deleting your avatar', String(error))
} }
} }
@@ -24,7 +24,7 @@ export async function useUser() {
formData.append('file', file) formData.append('file', file)
try { try {
await useRequestFetch()('/api/users/avatars', { await $fetch('/api/users/avatars', {
method: 'POST', method: 'POST',
body: formData, body: formData,
}) })
@@ -32,12 +32,29 @@ export async function useUser() {
useSuccessToast('Avatar successfully uploaded!') useSuccessToast('Avatar successfully uploaded!')
} }
catch (error) { catch (error) {
useErrorToast('An error occurred while uploading your avatar', error as string) useErrorToast('An error occurred while uploading your avatar', String(error))
}
}
async function updateUser(user: Partial<UserInsert>) {
try {
await $fetch('/api/users/me', {
method: 'PATCH',
body: JSON.stringify(user),
})
console.log(session.value)
await fetch()
console.log(session.value)
useSuccessToast('User successfully updated!')
}
catch (error) {
useErrorToast('An error occurred while updating your user', String(error))
} }
} }
return { return {
deleteAvatar, deleteAvatar,
uploadAvatar, uploadAvatar,
updateUser,
} }
} }

View File

@@ -10,7 +10,7 @@ onMounted(() => {
setInterval(() => date.value = new Date(), 1000) setInterval(() => date.value = new Date(), 1000)
}) })
const { user } = await useUserSession() const { user, loggedIn } = await useUserSession()
const { categories } = await useCategories() const { categories } = await useCategories()
const { getTabsForCategory } = await useTabs() const { getTabsForCategory } = await useTabs()
const { canCreateCategory } = await useUserLimits() const { canCreateCategory } = await useUserLimits()
@@ -94,7 +94,7 @@ defineShortcuts({
</script> </script>
<template> <template>
<main v-if="user" class="my-12"> <main v-if="user && loggedIn" class="my-12">
<div v-if="date" class="flex flex-col items-center mb-12"> <div v-if="date" class="flex flex-col items-center mb-12">
<h1 class="text-6xl md:text-9xl font-bold"> <h1 class="text-6xl md:text-9xl font-bold">
{{ useDateFormat(date, 'HH') }} {{ useDateFormat(date, 'HH') }}

View File

@@ -24,10 +24,7 @@ export default defineNuxtConfig({
'@vueuse/nuxt', '@vueuse/nuxt',
'@nuxtjs/google-fonts', '@nuxtjs/google-fonts',
'nuxt-auth-utils', 'nuxt-auth-utils',
'@nuxt/content',
'@nuxthq/studio',
'@nuxt/image', '@nuxt/image',
'nuxt-mapbox',
], ],
// Nuxt UI // Nuxt UI
@@ -77,22 +74,9 @@ export default defineNuxtConfig({
// Nuxt Env // Nuxt Env
runtimeConfig: { runtimeConfig: {
openWeather: {
apiKey: '',
lat: '',
lon: '',
lang: '',
units: '',
},
postgres: { postgres: {
url: '', url: '',
dir: './server/db', dir: './server/db',
}, },
public: {
mapbox: {
style: '',
accessToken: '',
},
},
}, },
}) })

View File

@@ -6,7 +6,7 @@
"scripts": { "scripts": {
"dev": "nuxt dev --host", "dev": "nuxt dev --host",
"remote": "nuxt dev --remote --host", "remote": "nuxt dev --remote --host",
"postinstall": "nuxt prepare", "postinstall": "nuxt prepare && node script.cjs",
"lint:fix": "eslint . --fix", "lint:fix": "eslint . --fix",
"db:generate": "drizzle-kit generate", "db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate", "db:migrate": "drizzle-kit migrate",
@@ -14,9 +14,7 @@
"db:pull": "drizzle-kit pull" "db:pull": "drizzle-kit pull"
}, },
"dependencies": { "dependencies": {
"@nuxt/content": "^2.13.2",
"@nuxt/image": "^1.7.1", "@nuxt/image": "^1.7.1",
"@nuxthq/studio": "^2.0.3",
"@nuxthub/core": "^0.7.7", "@nuxthub/core": "^0.7.7",
"@nuxtjs/google-fonts": "^3.2.0", "@nuxtjs/google-fonts": "^3.2.0",
"drizzle-orm": "^0.33.0", "drizzle-orm": "^0.33.0",
@@ -37,9 +35,7 @@
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"drizzle-kit": "^0.24.2", "drizzle-kit": "^0.24.2",
"eslint": "^9.9.1", "eslint": "^9.9.1",
"mapbox-gl": "^3.6.0",
"nuxt": "^3.13.0", "nuxt": "^3.13.0",
"nuxt-mapbox": "^1.6.0",
"typescript": "^5.5.4", "typescript": "^5.5.4",
"vue-tsc": "^2.0.29", "vue-tsc": "^2.0.29",
"wrangler": "^3.72.3" "wrangler": "^3.72.3"

1938
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

25
script.cjs Normal file
View File

@@ -0,0 +1,25 @@
const fs = require('node:fs')
const path = require('node:path')
const filesToModify = [
'node_modules/drizzle-orm/pg-core/columns/timestamp.js',
'node_modules/drizzle-orm/pg-core/columns/timestamp.cjs',
]
filesToModify.forEach((file) => {
const filePath = path.join(__dirname, file)
console.log(`Checking path: ${filePath}`)
if (fs.existsSync(filePath)) {
let fileContent = fs.readFileSync(filePath, 'utf8')
fileContent = fileContent.replace(
'return value.toISOString()',
'return value',
)
fs.writeFileSync(filePath, fileContent, 'utf8')
console.log(`Modified: ${file}`)
}
else {
console.error(`File not found: ${filePath}`)
}
})

View File

@@ -7,7 +7,7 @@ export default defineEventHandler(async (event) => {
.where( .where(
and( and(
eq(tables.categories.id, id), eq(tables.categories.id, id),
eq(tables.categories.userId, user.id), eq(tables.categories.userId, user.user.id),
), ),
) )
return { statusCode: 200 } return { statusCode: 200 }

View File

@@ -12,7 +12,7 @@ export default defineEventHandler(async (event) => {
.where( .where(
and( and(
eq(tables.categories.id, id), eq(tables.categories.id, id),
eq(tables.categories.userId, user.id), eq(tables.categories.userId, user.user.id),
), ),
) )
return { statusCode: 200 } return { statusCode: 200 }

View File

@@ -6,7 +6,7 @@ export default defineEventHandler(async (event) => {
const user = await getUserSession(event) const user = await getUserSession(event)
const body = await useValidatedBody(event, CreateCategorySchema) const body = await useValidatedBody(event, CreateCategorySchema)
await useDrizzle().insert(tables.categories).values({ await useDrizzle().insert(tables.categories).values({
userId: user.id, userId: user.user.id,
...body, ...body,
}) })
return { statusCode: 200 } return { statusCode: 200 }

View File

@@ -1,5 +1,5 @@
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
const { user, session } = await requireUserSession(event) const { user } = await requireUserSession(event)
if (!user.avatar) { if (!user.avatar) {
return sendNoContent(event, 204) return sendNoContent(event, 204)

View File

@@ -23,7 +23,7 @@ export default defineEventHandler(async (event) => {
const avatar = await hubBlob().put(filename, file, { const avatar = await hubBlob().put(filename, file, {
addRandomSuffix: false, addRandomSuffix: false,
prefix: 'avatars/', prefix: 'avatars',
}) })
const updatedUser = { const updatedUser = {

View File

@@ -1,7 +1,7 @@
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
const user = await requireUserSession(event) const user = await requireUserSession(event)
return useDrizzle().query.users.findFirst({ return useDrizzle().query.users.findFirst({
where: eq(tables.users.id, user.id), where: eq(tables.users.id, user.user.id),
with: { with: {
categories: { categories: {
with: { with: {

View File

@@ -11,7 +11,10 @@ export default defineEventHandler(async (event) => {
} }
await updateUser(user.id, updatedUser) await updateUser(user.id, updatedUser)
await replaceUserSession(event, updatedUser) await setUserSession(event, {
id: user.id,
user: updatedUser,
})
return sendNoContent(event, 204) return sendNoContent(event, 204)
}) })

View File

@@ -11,8 +11,8 @@ CREATE TABLE IF NOT EXISTS "categories" (
"icon" text DEFAULT 'i-ph:circle-wavy-question-duotone', "icon" text DEFAULT 'i-ph:circle-wavy-question-duotone',
"color" text DEFAULT 'gray', "color" text DEFAULT 'gray',
"user_id" integer NOT NULL, "user_id" integer NOT NULL,
"created_at" timestamp (3) DEFAULT now(), "created_at" timestamp(0) with time zone DEFAULT now(),
"updated_at" timestamp (3) "updated_at" timestamp(0) with time zone DEFAULT now()
); );
--> statement-breakpoint --> statement-breakpoint
CREATE TABLE IF NOT EXISTS "tabs" ( CREATE TABLE IF NOT EXISTS "tabs" (
@@ -23,8 +23,8 @@ CREATE TABLE IF NOT EXISTS "tabs" (
"color" text DEFAULT 'gray', "color" text DEFAULT 'gray',
"link" text DEFAULT '', "link" text DEFAULT '',
"category_id" integer NOT NULL, "category_id" integer NOT NULL,
"created_at" timestamp (3) DEFAULT now(), "created_at" timestamp(0) with time zone DEFAULT now(),
"updated_at" timestamp (3) "updated_at" timestamp(0) with time zone DEFAULT now()
); );
--> statement-breakpoint --> statement-breakpoint
CREATE TABLE IF NOT EXISTS "users" ( CREATE TABLE IF NOT EXISTS "users" (
@@ -42,8 +42,8 @@ CREATE TABLE IF NOT EXISTS "users" (
"language" text DEFAULT 'en-EN', "language" text DEFAULT 'en-EN',
"location" text DEFAULT 'unknown', "location" text DEFAULT 'unknown',
"subscription" "subscription" DEFAULT 'free', "subscription" "subscription" DEFAULT 'free',
"created_at" timestamp (3) DEFAULT now(), "created_at" timestamp(0) with time zone DEFAULT now(),
"updated_at" timestamp (3), "updated_at" timestamp(0) with time zone DEFAULT now(),
CONSTRAINT "users_username_unique" UNIQUE("username"), CONSTRAINT "users_username_unique" UNIQUE("username"),
CONSTRAINT "users_email_unique" UNIQUE("email"), CONSTRAINT "users_email_unique" UNIQUE("email"),
CONSTRAINT "users_github_id_unique" UNIQUE("github_id"), CONSTRAINT "users_github_id_unique" UNIQUE("github_id"),

View File

@@ -1,5 +1,5 @@
{ {
"id": "37cfa6b0-d0e9-4999-9ead-06b419388528", "id": "21470761-4c33-4588-be9b-4927bbcbfe2c",
"prevId": "00000000-0000-0000-0000-000000000000", "prevId": "00000000-0000-0000-0000-000000000000",
"version": "7", "version": "7",
"dialect": "postgresql", "dialect": "postgresql",
@@ -50,16 +50,17 @@
}, },
"created_at": { "created_at": {
"name": "created_at", "name": "created_at",
"type": "timestamp (3)", "type": "timestamp(0) with time zone",
"primaryKey": false, "primaryKey": false,
"notNull": false, "notNull": false,
"default": "now()" "default": "now()"
}, },
"updated_at": { "updated_at": {
"name": "updated_at", "name": "updated_at",
"type": "timestamp (3)", "type": "timestamp(0) with time zone",
"primaryKey": false, "primaryKey": false,
"notNull": false "notNull": false,
"default": "now()"
} }
}, },
"indexes": {}, "indexes": {},
@@ -134,16 +135,17 @@
}, },
"created_at": { "created_at": {
"name": "created_at", "name": "created_at",
"type": "timestamp (3)", "type": "timestamp(0) with time zone",
"primaryKey": false, "primaryKey": false,
"notNull": false, "notNull": false,
"default": "now()" "default": "now()"
}, },
"updated_at": { "updated_at": {
"name": "updated_at", "name": "updated_at",
"type": "timestamp (3)", "type": "timestamp(0) with time zone",
"primaryKey": false, "primaryKey": false,
"notNull": false "notNull": false,
"default": "now()"
} }
}, },
"indexes": {}, "indexes": {},
@@ -262,16 +264,17 @@
}, },
"created_at": { "created_at": {
"name": "created_at", "name": "created_at",
"type": "timestamp (3)", "type": "timestamp(0) with time zone",
"primaryKey": false, "primaryKey": false,
"notNull": false, "notNull": false,
"default": "now()" "default": "now()"
}, },
"updated_at": { "updated_at": {
"name": "updated_at", "name": "updated_at",
"type": "timestamp (3)", "type": "timestamp(0) with time zone",
"primaryKey": false, "primaryKey": false,
"notNull": false "notNull": false,
"default": "now()"
} }
}, },
"indexes": {}, "indexes": {},

View File

@@ -5,8 +5,8 @@
{ {
"idx": 0, "idx": 0,
"version": "7", "version": "7",
"when": 1725282814515, "when": 1725302227098,
"tag": "0000_cloudy_lifeguard", "tag": "0000_noisy_randall_flagg",
"breakpoints": true "breakpoints": true
} }
] ]

View File

@@ -3,28 +3,6 @@ export default oauthGitHubEventHandler({
emailRequired: true, emailRequired: true,
}, },
async onSuccess(event, { user: oauthUser, tokens }) { async onSuccess(event, { user: oauthUser, tokens }) {
const userSession = await getUserSession(event)
// If the user is already signed in, link the account
if (userSession?.id) {
const user = await findUserById(userSession.id)
if (user) {
await updateUser(userSession.id, {
githubId: oauthUser.id,
githubToken: tokens.access_token,
})
await setUserSession(event, {
id: userSession.id,
user: userSession,
githubId: oauthUser.id,
})
return sendRedirect(event, '/')
}
}
// If the user is not signed in, search for an existing user with that GitHub ID // If the user is not signed in, search for an existing user with that GitHub ID
// If it exists, sign in as that user and refresh the token // If it exists, sign in as that user and refresh the token
let user = await findUserByGitHubId(oauthUser.id) let user = await findUserByGitHubId(oauthUser.id)
@@ -35,7 +13,7 @@ export default oauthGitHubEventHandler({
githubToken: tokens.access_token, githubToken: tokens.access_token,
}) })
await setUserSession(event, { await replaceUserSession(event, {
id: user.id, id: user.id,
user, user,
}) })
@@ -76,9 +54,9 @@ export default oauthGitHubEventHandler({
subscription: 'free', subscription: 'free',
}) })
await setUserSession(event, { await replaceUserSession(event, {
id: createdUser.id, id: createdUser.id,
user: createdUser, user: createdUser[0],
}) })
return sendRedirect(event, '/') return sendRedirect(event, '/')

View File

@@ -4,28 +4,6 @@ export default oauthGoogleEventHandler({
scope: ['email', 'profile'], scope: ['email', 'profile'],
}, },
async onSuccess(event, { user: oauthUser, tokens }) { async onSuccess(event, { user: oauthUser, tokens }) {
const userSession = await getUserSession(event)
// If the user is already signed in, link the account
if (userSession?.id) {
const user = await findUserById(userSession.id)
if (user) {
await updateUser(userSession.id, {
googleId: oauthUser.sub,
googleToken: tokens.access_token,
})
await setUserSession(event, {
id: userSession.id,
user: userSession,
googleId: oauthUser.sub,
})
return sendRedirect(event, '/')
}
}
// If the user is not signed in, search for an existing user with that Google ID // If the user is not signed in, search for an existing user with that Google ID
// If it exists, sign in as that user and refresh the token // If it exists, sign in as that user and refresh the token
let user = await findUserByGoogleId(oauthUser.sub) let user = await findUserByGoogleId(oauthUser.sub)
@@ -36,7 +14,7 @@ export default oauthGoogleEventHandler({
googleToken: tokens.access_token, googleToken: tokens.access_token,
}) })
await setUserSession(event, { await replaceUserSession(event, {
id: user.id, id: user.id,
user, user,
}) })
@@ -77,9 +55,9 @@ export default oauthGoogleEventHandler({
subscription: 'free', subscription: 'free',
}) })
await setUserSession(event, { await replaceUserSession(event, {
id: createdUser.id, id: createdUser.id,
user: createdUser, user: createdUser[0],
}) })
return sendRedirect(event, '/') return sendRedirect(event, '/')

View File

@@ -3,8 +3,8 @@ import { serial, timestamp } from 'drizzle-orm/pg-core'
* A centralized list of standardized Drizzle ORM schema field definitions to prevent duplication errors * A centralized list of standardized Drizzle ORM schema field definitions to prevent duplication errors
*/ */
export const createdAt = timestamp('created_at', { mode: 'date', precision: 3 }).defaultNow() export const createdAt = timestamp('created_at', { mode: 'string', withTimezone: true, precision: 0 }).defaultNow()
export const updatedAt = timestamp('updated_at', { mode: 'date', precision: 3 }).$onUpdate(() => new Date()) export const updatedAt = timestamp('updated_at', { mode: 'string', withTimezone: true, precision: 0 }).defaultNow().$onUpdateFn(() => sql`(current_timestamp)`)
export const id = serial('id').primaryKey() export const id = serial('id').primaryKey()
export const timestamps = { export const timestamps = {

View File

@@ -1,15 +1,6 @@
import type { SQL } from 'drizzle-orm' import type { SQL } from 'drizzle-orm'
import type { UserInsert } from '~~/server/utils/db' import type { UserInsert } from '~~/server/utils/db'
export async function findUserById(userId: number) {
return useDrizzle()
.query
.users
.findFirst({
where: eq(tables.users.id, userId),
})
}
export async function findUserByGitHubId(githubId: number) { export async function findUserByGitHubId(githubId: number) {
return useDrizzle() return useDrizzle()
.query .query
@@ -41,18 +32,21 @@ export async function createUser(user: UserInsert) {
return useDrizzle() return useDrizzle()
.insert(tables.users) .insert(tables.users)
.values(user) .values(user)
.onConflictDoNothing()
.returning() .returning()
} }
export async function updateUser(userId: number, user: Partial<UserInsert>) { export async function updateUser(userId: number, user: Partial<UserInsert>) {
await useDrizzle() await useDrizzle()
.update(tables.users) .update(tables.users)
.set(user) .set({
...user,
})
.where(eq(tables.users.id, userId)) .where(eq(tables.users.id, userId))
} }
export async function deleteProfilePicture(avatar: string) { export async function deleteProfilePicture(avatar: string) {
if (avatar.startsWith('avatars/')) { if (avatar.startsWith('avatars')) {
await hubBlob().del(avatar) await hubBlob().del(avatar)
} }
} }