mirror of
https://github.com/wasp-lang/wasp.git
synced 2024-09-11 07:16:09 +03:00
Fix the auth helpers to only require fields they need from AuthIdentity
(#2057)
This commit is contained in:
parent
720ef76a7a
commit
b869eab352
@ -1,12 +1,14 @@
|
||||
{{={= =}=}}
|
||||
import { type {= authIdentityEntityName =} } from '../entities/index.js'
|
||||
import { type ProviderName } from '../server/_types/index.js'
|
||||
import type { AuthUserData, AuthUser } from '../server/auth/user.js'
|
||||
import type {
|
||||
AuthUserData,
|
||||
AuthUser,
|
||||
UserEntityWithAuth,
|
||||
} from '../server/auth/user.js'
|
||||
|
||||
/**
|
||||
* We split the user.ts code into two files to avoid some server-only
|
||||
* code (Oslo's hashing functions) being imported on the client.
|
||||
*/
|
||||
import { type UserEntityWithAuth } from '../server/auth/user.js'
|
||||
|
||||
// PUBLIC API
|
||||
export function getEmail(user: UserEntityWithAuth): string | null {
|
||||
@ -49,7 +51,7 @@ function makeAuthUser(data: AuthUserData): AuthUser {
|
||||
};
|
||||
}
|
||||
|
||||
function findUserIdentity(user: UserEntityWithAuth, providerName: ProviderName): {= authIdentityEntityName =} | null {
|
||||
function findUserIdentity(user: UserEntityWithAuth, providerName: ProviderName): UserEntityWithAuth['auth']['identities'][number] | null {
|
||||
if (!user.auth) {
|
||||
return null;
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ export type AuthUser = AuthUserData & {
|
||||
*
|
||||
* TODO: Change this once/if we switch to strict mode. https://github.com/wasp-lang/wasp/issues/1938
|
||||
*/
|
||||
export type AuthUserData = Omit<UserEntityWithAuth, '{= authFieldOnUserEntityName =}'> & {
|
||||
export type AuthUserData = Omit<CompleteUserEntityWithAuth, '{= authFieldOnUserEntityName =}'> & {
|
||||
identities: {
|
||||
{=# enabledProviders.isEmailAuthEnabled =}
|
||||
email: Expand<UserFacingProviderData<'email'>> | null
|
||||
@ -54,17 +54,36 @@ type UserFacingProviderData<PN extends ProviderName> = {
|
||||
} & Omit<PossibleProviderData[PN], 'hashedPassword'>
|
||||
|
||||
// PRIVATE API
|
||||
export type UserEntityWithAuth = {= userEntityName =} & {
|
||||
{= authFieldOnUserEntityName =}: AuthEntityWithIdentities | null
|
||||
export type CompleteUserEntityWithAuth =
|
||||
MakeUserEntityWithAuth<CompleteAuthEntityWithIdentities>
|
||||
|
||||
// PRIVATE API
|
||||
export type CompleteAuthEntityWithIdentities =
|
||||
MakeAuthEntityWithIdentities<{= authIdentityEntityName =}>
|
||||
|
||||
// PRIVATE API
|
||||
/**
|
||||
* User entity with all of the auth related data that's needed for the user facing
|
||||
* helper functions like `getUsername` and `getEmail`.
|
||||
*/
|
||||
export type UserEntityWithAuth = MakeUserEntityWithAuth<
|
||||
MakeAuthEntityWithIdentities<
|
||||
// It's constructed like the Complete* types, but only with the fields needed
|
||||
// for the user facing functions.
|
||||
Pick<{= authIdentityEntityName =}, 'providerName' | 'providerUserId'>
|
||||
>
|
||||
>
|
||||
|
||||
type MakeUserEntityWithAuth<AuthType> = {= userEntityName =} & {
|
||||
{= authFieldOnUserEntityName =}: AuthType | null
|
||||
}
|
||||
|
||||
type MakeAuthEntityWithIdentities<IdentityType> = {= authEntityName =} & {
|
||||
{= identitiesFieldOnAuthEntityName =}: IdentityType[]
|
||||
}
|
||||
|
||||
// PRIVATE API
|
||||
export type AuthEntityWithIdentities = {= authEntityName =} & {
|
||||
{= identitiesFieldOnAuthEntityName =}: {= authIdentityEntityName =}[]
|
||||
}
|
||||
|
||||
// PRIVATE API
|
||||
export function createAuthUserData(user: UserEntityWithAuth): AuthUserData {
|
||||
export function createAuthUserData(user: CompleteUserEntityWithAuth): AuthUserData {
|
||||
const { {= authFieldOnUserEntityName =}, ...rest } = user
|
||||
if (!{= authFieldOnUserEntityName =}) {
|
||||
throw new Error(`🐝 Error: trying to create a user without auth data.
|
||||
@ -94,7 +113,7 @@ This should never happen, but it did which means there is a bug in the code.`)
|
||||
}
|
||||
|
||||
function getProviderInfo<PN extends ProviderName>(
|
||||
auth: AuthEntityWithIdentities,
|
||||
auth: CompleteAuthEntityWithIdentities,
|
||||
providerName: PN
|
||||
):
|
||||
| UserFacingProviderData<PN>
|
||||
@ -112,7 +131,7 @@ function getProviderInfo<PN extends ProviderName>(
|
||||
}
|
||||
|
||||
function getIdentity(
|
||||
auth: AuthEntityWithIdentities,
|
||||
auth: CompleteAuthEntityWithIdentities,
|
||||
providerName: ProviderName
|
||||
): {= authIdentityEntityName =} | null {
|
||||
return auth.{= identitiesFieldOnAuthEntityName =}.find((i) => i.providerName === providerName) ?? null
|
||||
|
@ -158,7 +158,7 @@
|
||||
"file",
|
||||
"../out/sdk/wasp/auth/user.ts"
|
||||
],
|
||||
"43743b68e46a4bed06983596968b8d9cd6536d0eb8fb0cbd17100d7f82fc54cd"
|
||||
"19b44865cb0a36e86b8ddcd7779ca33d517e08da2b25f66003a66bda543f1c44"
|
||||
],
|
||||
[
|
||||
[
|
||||
@ -536,7 +536,7 @@
|
||||
"file",
|
||||
"../out/sdk/wasp/server/auth/user.ts"
|
||||
],
|
||||
"eb5754af49265911b6423004068cd829cf2e4b1b6a25e2477318f7e07e3029c9"
|
||||
"bc415fd61fe7333ffe30d850bd07b4eae823a6e7b70321f03d0d881808d1a412"
|
||||
],
|
||||
[
|
||||
[
|
||||
|
@ -1,11 +1,14 @@
|
||||
import { type AuthIdentity } from '../entities/index.js'
|
||||
import { type ProviderName } from '../server/_types/index.js'
|
||||
import type { AuthUserData, AuthUser } from '../server/auth/user.js'
|
||||
import type {
|
||||
AuthUserData,
|
||||
AuthUser,
|
||||
UserEntityWithAuth,
|
||||
} from '../server/auth/user.js'
|
||||
|
||||
/**
|
||||
* We split the user.ts code into two files to avoid some server-only
|
||||
* code (Oslo's hashing functions) being imported on the client.
|
||||
*/
|
||||
import { type UserEntityWithAuth } from '../server/auth/user.js'
|
||||
|
||||
// PUBLIC API
|
||||
export function getEmail(user: UserEntityWithAuth): string | null {
|
||||
@ -48,7 +51,7 @@ function makeAuthUser(data: AuthUserData): AuthUser {
|
||||
};
|
||||
}
|
||||
|
||||
function findUserIdentity(user: UserEntityWithAuth, providerName: ProviderName): AuthIdentity | null {
|
||||
function findUserIdentity(user: UserEntityWithAuth, providerName: ProviderName): UserEntityWithAuth['auth']['identities'][number] | null {
|
||||
if (!user.auth) {
|
||||
return null;
|
||||
}
|
||||
|
@ -1,9 +1,8 @@
|
||||
import type { AuthUserData, AuthUser } from '../server/auth/user.js';
|
||||
import type { AuthUserData, AuthUser, UserEntityWithAuth } from '../server/auth/user.js';
|
||||
/**
|
||||
* We split the user.ts code into two files to avoid some server-only
|
||||
* code (Oslo's hashing functions) being imported on the client.
|
||||
*/
|
||||
import { type UserEntityWithAuth } from '../server/auth/user.js';
|
||||
export declare function getEmail(user: UserEntityWithAuth): string | null;
|
||||
export declare function getUsername(user: UserEntityWithAuth): string | null;
|
||||
export declare function getFirstProviderUserId(user?: UserEntityWithAuth): string | null;
|
||||
|
@ -1,3 +1,7 @@
|
||||
/**
|
||||
* We split the user.ts code into two files to avoid some server-only
|
||||
* code (Oslo's hashing functions) being imported on the client.
|
||||
*/
|
||||
// PUBLIC API
|
||||
export function getEmail(user) {
|
||||
var _a, _b;
|
||||
|
@ -1 +1 @@
|
||||
{"version":3,"file":"user.js","sourceRoot":"","sources":["../../auth/user.ts"],"names":[],"mappings":"AASA,aAAa;AACb,MAAM,UAAU,QAAQ,CAAC,IAAwB;;IAC/C,OAAO,MAAA,MAAA,gBAAgB,CAAC,IAAI,EAAE,OAAO,CAAC,0CAAE,cAAc,mCAAI,IAAI,CAAC;AACjE,CAAC;AAED,aAAa;AACb,MAAM,UAAU,WAAW,CAAC,IAAwB;;IAClD,OAAO,MAAA,MAAA,gBAAgB,CAAC,IAAI,EAAE,UAAU,CAAC,0CAAE,cAAc,mCAAI,IAAI,CAAC;AACpE,CAAC;AAED,aAAa;AACb,MAAM,UAAU,sBAAsB,CAAC,IAAyB;;IAC9D,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtF,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,MAAA,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,cAAc,mCAAI,IAAI,CAAC;AACxD,CAAC;AAQD,MAAM,UAAU,sBAAsB,CACpC,IAAyB;IAEzB,OAAO,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;AACzC,CAAC;AAED,SAAS,YAAY,CAAC,IAAkB;IACtC,uCACK,IAAI,KACP,sBAAsB,EAAE,GAAG,EAAE;YAC3B,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YAClE,OAAO,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;QACzD,CAAC,IACD;AACJ,CAAC;AAED,SAAS,gBAAgB,CAAC,IAAwB,EAAE,YAA0B;;IAC5E,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QACf,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,MAAA,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAC9B,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,YAAY,KAAK,YAAY,CACrD,mCAAI,IAAI,CAAC;AACZ,CAAC"}
|
||||
{"version":3,"file":"user.js","sourceRoot":"","sources":["../../auth/user.ts"],"names":[],"mappings":"AAOA;;;GAGG;AAEH,aAAa;AACb,MAAM,UAAU,QAAQ,CAAC,IAAwB;;IAC/C,OAAO,MAAA,MAAA,gBAAgB,CAAC,IAAI,EAAE,OAAO,CAAC,0CAAE,cAAc,mCAAI,IAAI,CAAC;AACjE,CAAC;AAED,aAAa;AACb,MAAM,UAAU,WAAW,CAAC,IAAwB;;IAClD,OAAO,MAAA,MAAA,gBAAgB,CAAC,IAAI,EAAE,UAAU,CAAC,0CAAE,cAAc,mCAAI,IAAI,CAAC;AACpE,CAAC;AAED,aAAa;AACb,MAAM,UAAU,sBAAsB,CAAC,IAAyB;;IAC9D,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtF,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,MAAA,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,cAAc,mCAAI,IAAI,CAAC;AACxD,CAAC;AAQD,MAAM,UAAU,sBAAsB,CACpC,IAAyB;IAEzB,OAAO,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;AACzC,CAAC;AAED,SAAS,YAAY,CAAC,IAAkB;IACtC,uCACK,IAAI,KACP,sBAAsB,EAAE,GAAG,EAAE;YAC3B,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YAClE,OAAO,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;QACzD,CAAC,IACD;AACJ,CAAC;AAED,SAAS,gBAAgB,CAAC,IAAwB,EAAE,YAA0B;;IAC5E,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QACf,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,MAAA,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAC9B,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,YAAY,KAAK,YAAY,CACrD,mCAAI,IAAI,CAAC;AACZ,CAAC"}
|
@ -5,7 +5,7 @@ import { Expand } from '../../universal/types.js';
|
||||
export type AuthUser = AuthUserData & {
|
||||
getFirstProviderUserId: () => string | null;
|
||||
};
|
||||
export type AuthUserData = Omit<UserEntityWithAuth, 'auth'> & {
|
||||
export type AuthUserData = Omit<CompleteUserEntityWithAuth, 'auth'> & {
|
||||
identities: {
|
||||
google: Expand<UserFacingProviderData<'google'>> | null;
|
||||
};
|
||||
@ -13,11 +13,18 @@ export type AuthUserData = Omit<UserEntityWithAuth, 'auth'> & {
|
||||
type UserFacingProviderData<PN extends ProviderName> = {
|
||||
id: string;
|
||||
} & Omit<PossibleProviderData[PN], 'hashedPassword'>;
|
||||
export type UserEntityWithAuth = User & {
|
||||
auth: AuthEntityWithIdentities | null;
|
||||
export type CompleteUserEntityWithAuth = MakeUserEntityWithAuth<CompleteAuthEntityWithIdentities>;
|
||||
export type CompleteAuthEntityWithIdentities = MakeAuthEntityWithIdentities<AuthIdentity>;
|
||||
/**
|
||||
* User entity with all of the auth related data that's needed for the user facing
|
||||
* helper functions like `getUsername` and `getEmail`.
|
||||
*/
|
||||
export type UserEntityWithAuth = MakeUserEntityWithAuth<MakeAuthEntityWithIdentities<Pick<AuthIdentity, 'providerName' | 'providerUserId'>>>;
|
||||
type MakeUserEntityWithAuth<AuthType> = User & {
|
||||
auth: AuthType | null;
|
||||
};
|
||||
export type AuthEntityWithIdentities = Auth & {
|
||||
identities: AuthIdentity[];
|
||||
type MakeAuthEntityWithIdentities<IdentityType> = Auth & {
|
||||
identities: IdentityType[];
|
||||
};
|
||||
export declare function createAuthUserData(user: UserEntityWithAuth): AuthUserData;
|
||||
export declare function createAuthUserData(user: CompleteUserEntityWithAuth): AuthUserData;
|
||||
export {};
|
||||
|
@ -1 +1 @@
|
||||
{"version":3,"file":"user.js","sourceRoot":"","sources":["../../../server/auth/user.ts"],"names":[],"mappings":";;;;;;;;;;;AAKA,OAAO,EAEL,kCAAkC,EACnC,MAAM,qBAAqB,CAAA;AA0C5B,cAAc;AACd,MAAM,UAAU,kBAAkB,CAAC,IAAwB;IACzD,MAAM,EAAE,IAAI,KAAc,IAAI,EAAb,IAAI,UAAK,IAAI,EAAxB,QAAiB,CAAO,CAAA;IAC9B,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,KAAK,CAAC;6EACyD,CAAC,CAAA;IAC5E,CAAC;IACD,MAAM,UAAU,GAAG;QACjB,MAAM,EAAE,eAAe,CAAW,IAAI,EAAE,QAAQ,CAAC;KAClD,CAAA;IACD,uCACK,IAAI,KACP,UAAU,IACX;AACH,CAAC;AAED,SAAS,eAAe,CACtB,IAA8B,EAC9B,YAAgB;IAIhB,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,EAAE,YAAY,CAAC,CAAA;IAChD,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,IAAI,CAAA;IACb,CAAC;IACD,uCACK,kCAAkC,CAAK,QAAQ,CAAC,YAAY,EAAE;QAC/D,yBAAyB,EAAE,IAAI;KAChC,CAAC,KACF,EAAE,EAAE,QAAQ,CAAC,cAAc,IAC5B;AACH,CAAC;AAED,SAAS,WAAW,CAClB,IAA8B,EAC9B,YAA0B;;IAE1B,OAAO,MAAA,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,KAAK,YAAY,CAAC,mCAAI,IAAI,CAAA;AAC7E,CAAC"}
|
||||
{"version":3,"file":"user.js","sourceRoot":"","sources":["../../../server/auth/user.ts"],"names":[],"mappings":";;;;;;;;;;;AAKA,OAAO,EAEL,kCAAkC,EACnC,MAAM,qBAAqB,CAAA;AA6D5B,cAAc;AACd,MAAM,UAAU,kBAAkB,CAAC,IAAgC;IACjE,MAAM,EAAE,IAAI,KAAc,IAAI,EAAb,IAAI,UAAK,IAAI,EAAxB,QAAiB,CAAO,CAAA;IAC9B,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,KAAK,CAAC;6EACyD,CAAC,CAAA;IAC5E,CAAC;IACD,MAAM,UAAU,GAAG;QACjB,MAAM,EAAE,eAAe,CAAW,IAAI,EAAE,QAAQ,CAAC;KAClD,CAAA;IACD,uCACK,IAAI,KACP,UAAU,IACX;AACH,CAAC;AAED,SAAS,eAAe,CACtB,IAAsC,EACtC,YAAgB;IAIhB,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,EAAE,YAAY,CAAC,CAAA;IAChD,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,IAAI,CAAA;IACb,CAAC;IACD,uCACK,kCAAkC,CAAK,QAAQ,CAAC,YAAY,EAAE;QAC/D,yBAAyB,EAAE,IAAI;KAChC,CAAC,KACF,EAAE,EAAE,QAAQ,CAAC,cAAc,IAC5B;AACH,CAAC;AAED,SAAS,WAAW,CAClB,IAAsC,EACtC,YAA0B;;IAE1B,OAAO,MAAA,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,KAAK,YAAY,CAAC,mCAAI,IAAI,CAAA;AAC7E,CAAC"}
|
@ -28,7 +28,7 @@ export type AuthUser = AuthUserData & {
|
||||
*
|
||||
* TODO: Change this once/if we switch to strict mode. https://github.com/wasp-lang/wasp/issues/1938
|
||||
*/
|
||||
export type AuthUserData = Omit<UserEntityWithAuth, 'auth'> & {
|
||||
export type AuthUserData = Omit<CompleteUserEntityWithAuth, 'auth'> & {
|
||||
identities: {
|
||||
google: Expand<UserFacingProviderData<'google'>> | null
|
||||
},
|
||||
@ -39,17 +39,36 @@ type UserFacingProviderData<PN extends ProviderName> = {
|
||||
} & Omit<PossibleProviderData[PN], 'hashedPassword'>
|
||||
|
||||
// PRIVATE API
|
||||
export type UserEntityWithAuth = User & {
|
||||
auth: AuthEntityWithIdentities | null
|
||||
export type CompleteUserEntityWithAuth =
|
||||
MakeUserEntityWithAuth<CompleteAuthEntityWithIdentities>
|
||||
|
||||
// PRIVATE API
|
||||
export type CompleteAuthEntityWithIdentities =
|
||||
MakeAuthEntityWithIdentities<AuthIdentity>
|
||||
|
||||
// PRIVATE API
|
||||
/**
|
||||
* User entity with all of the auth related data that's needed for the user facing
|
||||
* helper functions like `getUsername` and `getEmail`.
|
||||
*/
|
||||
export type UserEntityWithAuth = MakeUserEntityWithAuth<
|
||||
MakeAuthEntityWithIdentities<
|
||||
// It's constructed like the Complete* types, but only with the fields needed
|
||||
// for the user facing functions.
|
||||
Pick<AuthIdentity, 'providerName' | 'providerUserId'>
|
||||
>
|
||||
>
|
||||
|
||||
type MakeUserEntityWithAuth<AuthType> = User & {
|
||||
auth: AuthType | null
|
||||
}
|
||||
|
||||
type MakeAuthEntityWithIdentities<IdentityType> = Auth & {
|
||||
identities: IdentityType[]
|
||||
}
|
||||
|
||||
// PRIVATE API
|
||||
export type AuthEntityWithIdentities = Auth & {
|
||||
identities: AuthIdentity[]
|
||||
}
|
||||
|
||||
// PRIVATE API
|
||||
export function createAuthUserData(user: UserEntityWithAuth): AuthUserData {
|
||||
export function createAuthUserData(user: CompleteUserEntityWithAuth): AuthUserData {
|
||||
const { auth, ...rest } = user
|
||||
if (!auth) {
|
||||
throw new Error(`🐝 Error: trying to create a user without auth data.
|
||||
@ -65,7 +84,7 @@ This should never happen, but it did which means there is a bug in the code.`)
|
||||
}
|
||||
|
||||
function getProviderInfo<PN extends ProviderName>(
|
||||
auth: AuthEntityWithIdentities,
|
||||
auth: CompleteAuthEntityWithIdentities,
|
||||
providerName: PN
|
||||
):
|
||||
| UserFacingProviderData<PN>
|
||||
@ -83,7 +102,7 @@ function getProviderInfo<PN extends ProviderName>(
|
||||
}
|
||||
|
||||
function getIdentity(
|
||||
auth: AuthEntityWithIdentities,
|
||||
auth: CompleteAuthEntityWithIdentities,
|
||||
providerName: ProviderName
|
||||
): AuthIdentity | null {
|
||||
return auth.identities.find((i) => i.providerName === providerName) ?? null
|
||||
|
@ -1,6 +1,6 @@
|
||||
app crudTesting {
|
||||
wasp: {
|
||||
version: "^0.13.0"
|
||||
version: "^0.14.0"
|
||||
},
|
||||
head: [
|
||||
"<link rel=\"stylesheet\" href=\"https://unpkg.com/mvp.css@1.12/mvp.css\">"
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { Link } from 'wasp/client/router'
|
||||
import { type Task } from 'wasp/entities'
|
||||
|
||||
import {
|
||||
useAction,
|
||||
@ -13,12 +12,15 @@ import {
|
||||
} from 'wasp/client/operations'
|
||||
|
||||
import React, { useState, FormEventHandler, ChangeEventHandler } from 'react'
|
||||
import { getEmail } from 'wasp/auth'
|
||||
|
||||
type NonEmptyArray<T> = [T, ...T[]]
|
||||
|
||||
type TaskWithUser = Awaited<ReturnType<typeof getTasks>>[number]
|
||||
|
||||
export function areThereAnyTasks(
|
||||
tasks: Task[] | undefined
|
||||
): tasks is NonEmptyArray<Task> {
|
||||
tasks: TaskWithUser[] | undefined
|
||||
): tasks is NonEmptyArray<TaskWithUser> {
|
||||
return !!(tasks && tasks.length > 0)
|
||||
}
|
||||
|
||||
@ -55,7 +57,7 @@ const Todo = () => {
|
||||
)
|
||||
}
|
||||
|
||||
const Footer = ({ tasks }: { tasks: NonEmptyArray<Task> }) => {
|
||||
const Footer = ({ tasks }: { tasks: NonEmptyArray<TaskWithUser> }) => {
|
||||
const numCompletedTasks = tasks.filter((t) => t.isDone).length
|
||||
const numUncompletedTasks = tasks.filter((t) => !t.isDone).length
|
||||
|
||||
@ -83,7 +85,7 @@ const Footer = ({ tasks }: { tasks: NonEmptyArray<Task> }) => {
|
||||
)
|
||||
}
|
||||
|
||||
const Tasks = ({ tasks }: { tasks: NonEmptyArray<Task> }) => {
|
||||
const Tasks = ({ tasks }: { tasks: NonEmptyArray<TaskWithUser> }) => {
|
||||
return (
|
||||
<div>
|
||||
<table className="border-separate border-spacing-2">
|
||||
@ -97,9 +99,9 @@ const Tasks = ({ tasks }: { tasks: NonEmptyArray<Task> }) => {
|
||||
)
|
||||
}
|
||||
|
||||
type UpdateTaskIsDonePayload = Pick<Task, 'id' | 'isDone'>
|
||||
type UpdateTaskIsDonePayload = Pick<TaskWithUser, 'id' | 'isDone'>
|
||||
|
||||
const TaskView = ({ task }: { task: Task }) => {
|
||||
const TaskView = ({ task }: { task: TaskWithUser }) => {
|
||||
const updateTaskIsDoneOptimistically = useAction(updateTaskIsDone, {
|
||||
optimisticUpdates: [
|
||||
{
|
||||
@ -114,7 +116,7 @@ const TaskView = ({ task }: { task: Task }) => {
|
||||
)
|
||||
}
|
||||
},
|
||||
} as OptimisticUpdateDefinition<UpdateTaskIsDonePayload, Task[]>,
|
||||
} as OptimisticUpdateDefinition<UpdateTaskIsDonePayload, TaskWithUser[]>,
|
||||
],
|
||||
})
|
||||
const handleTaskIsDoneChange: ChangeEventHandler<HTMLInputElement> = async (
|
||||
@ -130,6 +132,8 @@ const TaskView = ({ task }: { task: Task }) => {
|
||||
}
|
||||
}
|
||||
|
||||
const email = getEmail(task.user)
|
||||
|
||||
return (
|
||||
<tr>
|
||||
<td>
|
||||
@ -143,7 +147,7 @@ const TaskView = ({ task }: { task: Task }) => {
|
||||
</td>
|
||||
<td>
|
||||
<Link to="/task/:id" params={{ id: task.id }}>
|
||||
{task.description}
|
||||
{task.description} {email && `by ${email}`}
|
||||
</Link>
|
||||
</td>
|
||||
</tr>
|
||||
@ -158,21 +162,26 @@ const NewTaskForm = () => {
|
||||
{
|
||||
getQuerySpecifier: () => [getTasks],
|
||||
updateQuery: (newTask, oldTasks) => {
|
||||
const newTaskWithUser = {
|
||||
...newTask,
|
||||
user: {},
|
||||
} as TaskWithUser
|
||||
|
||||
if (oldTasks === undefined) {
|
||||
// cache is empty
|
||||
return [newTask as Task]
|
||||
return [newTaskWithUser]
|
||||
} else {
|
||||
return [...oldTasks, newTask as Task]
|
||||
return [...oldTasks, newTaskWithUser]
|
||||
}
|
||||
},
|
||||
} as OptimisticUpdateDefinition<
|
||||
Pick<Task, 'isDone' | 'description'>,
|
||||
Task[]
|
||||
Pick<TaskWithUser, 'isDone' | 'description'>,
|
||||
TaskWithUser[]
|
||||
>,
|
||||
],
|
||||
})
|
||||
|
||||
const createNewTask = async (description: Task['description']) => {
|
||||
const createNewTask = async (description: TaskWithUser['description']) => {
|
||||
const task = { isDone: false, description }
|
||||
await createTaskFn(task)
|
||||
}
|
||||
|
@ -1,8 +1,13 @@
|
||||
import { type Task } from "wasp/entities";
|
||||
import { HttpError } from "wasp/server";
|
||||
import { type GetNumTasks, type GetTask, type GetTasks, type GetDate } from "wasp/server/operations";
|
||||
import { type Task } from 'wasp/entities'
|
||||
import { HttpError } from 'wasp/server'
|
||||
import {
|
||||
type GetNumTasks,
|
||||
type GetTask,
|
||||
type GetTasks,
|
||||
type GetDate,
|
||||
} from 'wasp/server/operations'
|
||||
|
||||
export const getTasks: GetTasks<void, Task[]> = async (_args, context) => {
|
||||
export const getTasks = (async (_args, context) => {
|
||||
if (!context.user) {
|
||||
throw new HttpError(401)
|
||||
}
|
||||
@ -10,20 +15,40 @@ export const getTasks: GetTasks<void, Task[]> = async (_args, context) => {
|
||||
console.log('TEST_ENV_VAR', process.env.TEST_ENV_VAR)
|
||||
|
||||
const Task = context.entities.Task
|
||||
const tasks = await Task.findMany(
|
||||
{
|
||||
where: { user: { id: context.user.id } },
|
||||
orderBy: { id: 'asc' },
|
||||
}
|
||||
)
|
||||
const tasks = await Task.findMany({
|
||||
where: { user: { id: context.user.id } },
|
||||
orderBy: { id: 'asc' },
|
||||
include: {
|
||||
user: {
|
||||
include: {
|
||||
auth: {
|
||||
include: {
|
||||
identities: {
|
||||
select: {
|
||||
providerName: true,
|
||||
providerUserId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
return tasks
|
||||
}
|
||||
}) satisfies GetTasks<void>
|
||||
|
||||
export const getNumTasks: GetNumTasks<void, number> = async (_args, context) => {
|
||||
export const getNumTasks: GetNumTasks<void, number> = async (
|
||||
_args,
|
||||
context
|
||||
) => {
|
||||
return context.entities.Task.count()
|
||||
}
|
||||
|
||||
export const getTask: GetTask<Pick<Task, 'id'>, Task> = async (where, context) => {
|
||||
export const getTask: GetTask<Pick<Task, 'id'>, Task> = async (
|
||||
where,
|
||||
context
|
||||
) => {
|
||||
if (!context.user) {
|
||||
throw new HttpError(401)
|
||||
}
|
||||
|
@ -1,62 +1,65 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
import { test, expect } from "@playwright/test";
|
||||
import {
|
||||
generateRandomCredentials,
|
||||
performLogin,
|
||||
performSignup,
|
||||
} from './helpers'
|
||||
} from "./helpers";
|
||||
|
||||
test('has title', async ({ page }) => {
|
||||
await page.goto('/')
|
||||
test("has title", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
|
||||
await expect(page).toHaveTitle(/ToDo App/)
|
||||
})
|
||||
test.describe('signup and login', () => {
|
||||
const { email, password } = generateRandomCredentials()
|
||||
await expect(page).toHaveTitle(/ToDo App/);
|
||||
});
|
||||
test.describe("signup and login", () => {
|
||||
const { email, password } = generateRandomCredentials();
|
||||
|
||||
test.describe.configure({ mode: 'serial' })
|
||||
test.describe.configure({ mode: "serial" });
|
||||
|
||||
test('social button renders', async ({ page }) => {
|
||||
await page.goto('/signup')
|
||||
test("social button renders", async ({ page }) => {
|
||||
await page.goto("/signup");
|
||||
|
||||
await page.waitForSelector('text=Create a new account')
|
||||
await page.waitForSelector("text=Create a new account");
|
||||
|
||||
await expect(
|
||||
page.locator("a[href='http://localhost:3001/auth/google/login']"),
|
||||
).toBeVisible()
|
||||
})
|
||||
page.locator("a[href='http://localhost:3001/auth/google/login']")
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test('can sign up', async ({ page }) => {
|
||||
test("can sign up", async ({ page }) => {
|
||||
await performSignup(page, {
|
||||
email,
|
||||
password,
|
||||
})
|
||||
});
|
||||
|
||||
await expect(page.locator('body')).toContainText(
|
||||
`You've signed up successfully! Check your email for the confirmation link.`,
|
||||
)
|
||||
})
|
||||
await expect(page.locator("body")).toContainText(
|
||||
`You've signed up successfully! Check your email for the confirmation link.`
|
||||
);
|
||||
});
|
||||
|
||||
test('can log in and create a task', async ({ page }) => {
|
||||
test("can log in and create a task", async ({ page }) => {
|
||||
await performLogin(page, {
|
||||
email,
|
||||
password: '12345678xxx',
|
||||
})
|
||||
password: "12345678xxx",
|
||||
});
|
||||
|
||||
await expect(page.locator('body')).toContainText('Invalid credentials')
|
||||
await expect(page.locator("body")).toContainText("Invalid credentials");
|
||||
|
||||
await performLogin(page, {
|
||||
email,
|
||||
password,
|
||||
})
|
||||
});
|
||||
|
||||
await expect(page).toHaveURL('/profile')
|
||||
await expect(page).toHaveURL("/profile");
|
||||
|
||||
await page.goto('/')
|
||||
await page.goto("/");
|
||||
|
||||
const randomTask = `New Task ${Math.random().toString(36).substring(7)}`
|
||||
await page.locator("input[type='text']").fill(randomTask)
|
||||
await page.getByText('Create new task').click()
|
||||
const randomTask = `New Task ${Math.random().toString(36).substring(7)}`;
|
||||
await page.locator("input[type='text']").fill(randomTask);
|
||||
await page.getByText("Create new task").click();
|
||||
|
||||
await expect(page.locator('body')).toContainText(randomTask)
|
||||
})
|
||||
})
|
||||
const fullTaskText = `${randomTask} by ${email}`;
|
||||
await page.waitForSelector(`text=${fullTaskText}`);
|
||||
|
||||
await expect(page.locator("body")).toContainText(fullTaskText);
|
||||
});
|
||||
});
|
||||
|
@ -30,7 +30,7 @@ genAuth spec =
|
||||
Just auth ->
|
||||
-- shared stuff
|
||||
sequence
|
||||
[ genUserTs
|
||||
[ genFileCopy [relfile|auth/user.ts|]
|
||||
]
|
||||
-- client stuff
|
||||
<++> sequence
|
||||
@ -133,8 +133,3 @@ genProvidersTypes auth = return $ C.mkTmplFdWithData [relfile|auth/providers/typ
|
||||
userEntityName = AS.refName $ AS.Auth.userEntity auth
|
||||
|
||||
tmplData = object ["userEntityUpper" .= (userEntityName :: String)]
|
||||
|
||||
genUserTs :: Generator FileDraft
|
||||
genUserTs = return $ C.mkTmplFdWithData [relfile|auth/user.ts|] tmplData
|
||||
where
|
||||
tmplData = object ["authIdentityEntityName" .= DbAuth.authIdentityEntityName]
|
||||
|
Loading…
Reference in New Issue
Block a user