Skip to content

Commit 7a18e8f

Browse files
committed
feat(auth): some progress on profile updating.
1 parent ccdb3fa commit 7a18e8f

3 files changed

Lines changed: 146 additions & 113 deletions

File tree

src/route-tree.gen.ts

Lines changed: 113 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
//// - DO NOT make any changes.
99
//// - Make sure to exclude from linter/formatter.
1010

11-
export { pageRoutes, getRoute, useParams }
12-
export type { PageRoute, UseParamsResult }
11+
export { pageRoutes, getRoute, useParams };
12+
export type { PageRoute, UseParamsResult };
1313

1414
const pageRoutes = [
1515
"/",
@@ -20,142 +20,143 @@ const pageRoutes = [
2020
"/payment/completed",
2121
"/pricing",
2222
"/sign-in",
23-
"/sign-up",
24-
] as const
23+
"/sign-up"
24+
] as const;
2525

26-
type PageRoute = (typeof pageRoutes)[number]
26+
type PageRoute = typeof pageRoutes[number];
2727

2828
/* For regular routes with named parameters. But it has a minor issue, it gets "" as a property, so this is prefixed with '_' */
2929
type _ExtractNamedParams<T extends string> = T extends `${string}@${infer Param}/${infer Rest}`
30-
? { [K in Param | keyof ExtractNamedParams<Rest>]: string }
31-
: T extends `${string}@${infer Param}`
32-
? { [K in Param]: string }
33-
: {}
30+
? { [K in Param | keyof ExtractNamedParams<Rest>]: string }
31+
: T extends `${string}@${infer Param}`
32+
? { [K in Param]: string }
33+
: {};
3434

3535
/** Minor utility to prevent typescript from wrapping types. */
3636
type Prettify<T> = {
37-
[K in keyof T]: T[K]
38-
} & {}
37+
[K in keyof T]: T[K];
38+
} & {};
3939

4040
/* For regular routes with named parameters */
41-
type ExtractNamedParams<T extends string> = Prettify<Omit<_ExtractNamedParams<T>, "">>
41+
type ExtractNamedParams<T extends string> = Prettify<Omit<_ExtractNamedParams<T>, "">>;
4242

4343
/* Helper to determine if a route ends with a catch-all segment */
44-
type EndsWithCatchall<T extends string> = T extends `${string}/@` ? true : false
44+
type EndsWithCatchall<T extends string> = T extends `${string}/@` ? true : false;
4545

4646
/* Conditional type helper for determining if a route is a catchall/splat route */
47-
type IsCatchallRoute<T extends string> = EndsWithCatchall<T>
47+
type IsCatchallRoute<T extends string> = EndsWithCatchall<T>;
4848

4949
/* Return type for UseParams */
50-
type UseParamsResult<T extends PageRoute> = IsCatchallRoute<T> extends true
51-
? ExtractNamedParams<T> & { "@": string[]; "_@": string }
52-
: ExtractNamedParams<T>
50+
type UseParamsResult<T extends PageRoute> =
51+
IsCatchallRoute<T> extends true
52+
? ExtractNamedParams<T> & { '@': string[], '_@': string }
53+
: ExtractNamedParams<T>;
5354

5455
/* Conditional type helper for determining if a route has parameters */
55-
type HasParams<T extends string> = IsCatchallRoute<T> extends true
56-
? true
57-
: T extends `${string}@${string}`
58-
? true
59-
: false
56+
type HasParams<T extends string> =
57+
IsCatchallRoute<T> extends true
58+
? true
59+
: T extends `${string}@${string}`
60+
? true
61+
: false;
6062

6163
/* Type for the options of the getRoute function. */
6264
type GetRouteOptions<T extends PageRoute> = HasParams<T> extends true
63-
? IsCatchallRoute<T> extends true
64-
? {
65-
params: ExtractNamedParams<T> & { "@": string[] | string }
66-
search?: Record<string, string>
67-
}
68-
: {
69-
params: ExtractNamedParams<T>
70-
search?: Record<string, string>
71-
}
72-
: {
73-
params?: never
74-
search?: Record<string, string>
75-
}
65+
? IsCatchallRoute<T> extends true
66+
? {
67+
params: ExtractNamedParams<T> & { '@': string[] | string };
68+
search?: Record<string, string>;
69+
}
70+
: {
71+
params: ExtractNamedParams<T>;
72+
search?: Record<string, string>;
73+
}
74+
: {
75+
params?: never;
76+
search?: Record<string, string>;
77+
};
7678

7779
/* Typesafe helper to generate a route URL based on Vike pages folder. */
7880
function getRoute<T extends PageRoute>(
79-
route: T,
80-
...args: HasParams<T> extends true
81-
? [options: GetRouteOptions<T>]
82-
: [options?: GetRouteOptions<T>]
81+
route: T,
82+
...args: HasParams<T> extends true
83+
? [options: GetRouteOptions<T>]
84+
: [options?: GetRouteOptions<T>]
8385
): string {
84-
const options = args[0] || {}
85-
86-
// Handle both regular named parameters and catchall routes
87-
let result: string = route
88-
89-
if (options.params) {
90-
// Handle named parameters first
91-
const params = { ...options.params }
92-
Object.entries(params).forEach(([key, value]) => {
93-
if (key !== "@") {
94-
result = result.replace(`@${key}`, String(value))
95-
}
96-
})
97-
98-
// Then handle catchall if present
99-
if (route.endsWith("/@") && "@" in params) {
100-
const catchallValue = params["@"]
101-
// Remove the trailing /@ from the result
102-
result = result.substring(0, result.length - 2)
103-
104-
if (Array.isArray(catchallValue)) {
105-
// If array, join with slashes
106-
result += `/${catchallValue.join("/")}`
107-
} else if (typeof catchallValue === "string") {
108-
// If string, add directly (with leading slash if needed)
109-
const prefix = catchallValue.startsWith("/") ? "" : "/"
110-
result += `${prefix}${catchallValue}`
111-
}
112-
}
113-
}
114-
115-
if (options.search) {
116-
const searchParams = new URLSearchParams(options.search)
117-
result += `?${searchParams.toString()}`
118-
}
119-
120-
return result
86+
const options = args[0] || {};
87+
88+
// Handle both regular named parameters and catchall routes
89+
let result: string = route;
90+
91+
if (options.params) {
92+
// Handle named parameters first
93+
const params = { ...options.params };
94+
Object.entries(params).forEach(([key, value]) => {
95+
if (key !== '@') {
96+
result = result.replace(`@${key}`, String(value));
97+
}
98+
});
99+
100+
// Then handle catchall if present
101+
if (route.endsWith('/@') && '@' in params) {
102+
const catchallValue = params['@'];
103+
// Remove the trailing /@ from the result
104+
result = result.substring(0, result.length - 2);
105+
106+
if (Array.isArray(catchallValue)) {
107+
// If array, join with slashes
108+
result += `/${catchallValue.join('/')}`;
109+
} else if (typeof catchallValue === 'string') {
110+
// If string, add directly (with leading slash if needed)
111+
const prefix = catchallValue.startsWith('/') ? '' : '/';
112+
result += `${prefix}${catchallValue}`;
113+
}
114+
}
121115
}
122116

123-
import { createMemo } from "solid-js"
124-
import { usePageContext } from "vike-solid/usePageContext"
117+
if (options.search) {
118+
const searchParams = new URLSearchParams(options.search);
119+
result += `?${searchParams.toString()}`;
120+
}
121+
122+
return result;
123+
}
125124

125+
import { usePageContext } from 'vike-solid/usePageContext'
126+
import { createMemo } from 'solid-js'
126127
function useParams<T extends PageRoute>(params: { from: T }): () => UseParamsResult<T> {
127-
const pageContext = usePageContext()
128-
129-
return createMemo(() => {
130-
const routeParams = pageContext.routeParams as Record<string, string>
131-
132-
// Check if this is a catch-all route
133-
if (params.from.endsWith("/@") && routeParams && "*" in routeParams) {
134-
const catchAllPath = routeParams["*"] as string
135-
const segments = catchAllPath.split("/").filter(Boolean)
136-
137-
// Extract named parameters from the route
138-
const namedParams: Record<string, string> = {}
139-
const routeParts = params.from.split("/")
140-
const urlParts = pageContext.urlPathname.split("/")
141-
142-
for (let i = 0; i < routeParts.length - 1; i++) {
143-
const routePart = routeParts[i]
144-
if (routePart.startsWith("@")) {
145-
const paramName = routePart.slice(1)
146-
namedParams[paramName] = urlParts[i]
147-
}
148-
}
149-
150-
// Combine named parameters with catch-all segments
151-
return {
152-
...namedParams,
153-
"@": segments,
154-
"_@": catchAllPath.startsWith("/") ? catchAllPath : `/${catchAllPath}`,
155-
} as unknown as UseParamsResult<T>
156-
}
157-
158-
// Handle regular dynamic routes without catch-all
159-
return routeParams as UseParamsResult<T>
160-
})
128+
const pageContext = usePageContext();
129+
130+
return createMemo(() => {
131+
const routeParams = pageContext.routeParams as Record<string, string>;
132+
133+
// Check if this is a catch-all route
134+
if (params.from.endsWith("/@") && routeParams && "*" in routeParams) {
135+
const catchAllPath = routeParams["*"] as string;
136+
const segments = catchAllPath.split("/").filter(Boolean);
137+
138+
// Extract named parameters from the route
139+
const namedParams: Record<string, string> = {};
140+
const routeParts = params.from.split("/");
141+
const urlParts = pageContext.urlPathname.split("/");
142+
143+
for (let i = 0; i < routeParts.length - 1; i++) {
144+
const routePart = routeParts[i];
145+
if (routePart.startsWith("@")) {
146+
const paramName = routePart.slice(1);
147+
namedParams[paramName] = urlParts[i];
148+
}
161149
}
150+
151+
// Combine named parameters with catch-all segments
152+
return {
153+
...namedParams,
154+
"@": segments,
155+
"_@": catchAllPath.startsWith("/") ? catchAllPath : `/${catchAllPath}`,
156+
} as unknown as UseParamsResult<T>;
157+
}
158+
159+
// Handle regular dynamic routes without catch-all
160+
return routeParams as UseParamsResult<T>;
161+
});
162+
}

src/server/modules/auth/auth.dao.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,31 @@ export class AuthDAO {
241241
}
242242
}
243243

244+
async updateUserProfile(params: { userId: string; metadata?: Partial<UserMetaDTO> }) {
245+
const updates: Partial<{ metadata: string }> = {}
246+
247+
if (params.metadata !== undefined) {
248+
// To update a JSON field we need the old row, merge, and overwrite
249+
const current = await db
250+
.selectFrom("user")
251+
.select("metadata")
252+
.where("user.id", "=", params.userId)
253+
.executeTakeFirst()
254+
255+
const existingMeta = current?.metadata
256+
? assertDTO(JSON.parse(current.metadata as string), userMetaDTO)
257+
: ({} as UserMetaDTO)
258+
259+
const mergedMeta: UserMetaDTO = { ...existingMeta, ...params.metadata }
260+
261+
updates.metadata = JSON.stringify(mergedMeta)
262+
}
263+
264+
await db.updateTable("user").set(updates).where("user.id", "=", params.userId).execute()
265+
266+
return { success: true }
267+
}
268+
244269
// --- Auth Strategies ---
245270
// - Email and Password
246271
async createUserFromEmailAndPassword(params: {

src/server/modules/auth/auth.dto.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,19 @@ import { assertDTO } from "@/server/utils/assert-dto"
77
// SERVER ONLY
88
// ===========================================================================
99
// The actual DB schema for user meta (Only defined in application layer. Not defined in DB. Just a json in the db)
10+
// In my opinion, it's good practice to keep everything optional or with a default.
1011
export const userMetaDTO = z
1112
.object({
1213
username: z.string().optional(),
1314
name: z.string().optional(),
1415
// Avatar url from oauth if possible
16+
/** Public avatar url from oauth if possible. */
1517
avatar_url: z.string().optional(),
18+
/**
19+
* Object id from own bucket. Higher priority to show over avatar_url, if uploadable images are a thing.
20+
* This is separate because S3 buckets are generally "private", and only send signed urls.
21+
*/
22+
avatar_object_id: z.string().optional(),
1623
})
1724
.optional()
1825
.nullable()
@@ -23,7 +30,7 @@ export type InternalUserDTO = Selectable<User>
2330
export type InternalSessionDTO = Selectable<Session>
2431

2532
// ===========================================================================
26-
// CLIENT AND SERVER: Make sure to edit as needed! ✍️
33+
// CLIENT AND SERVER: Make sure to edit this and the userMetaDTO as needed! ✍️
2734
// ===========================================================================
2835
// Frontend & Server: User passed around in client context.
2936
export function getUserResponseDTO(user: InternalUserDTO) {

0 commit comments

Comments
 (0)