Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
# =============================================================================

APP_NAME=GRIT
APP_BASE_URL=https://grit.social
APP_BASE_URL=http://localhost:5173

# =====================
# Node environment
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "User" ADD COLUMN "displayName" TEXT;
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-- Lowercase any names that weren't stored correctly before normalization was enforced
UPDATE "User" SET "name" = LOWER("name") WHERE "name" != LOWER("name");

-- Backfill displayName for users who existed before the displayName column was added
-- (or were created via the broken Google OAuth path that generated mismatched nanoids)
UPDATE "User" SET "displayName" = "name" WHERE "displayName" IS NULL;
1 change: 1 addition & 0 deletions apps/backend/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ model User {
email String @unique
password String?
name String @unique
displayName String?
avatarKey String?
bio String?
city String?
Expand Down
38 changes: 30 additions & 8 deletions apps/backend/prisma/seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,8 @@ async function uploadToBucket(bucketName: string, localFilePath: string, origina
async function main() {
console.log('--- Seeding database...');

const TEST_RECORD_COUNT = 30;
const DEFAULT_TEST_PASSWORD = 'password123';
const TEST_RECORD_COUNT = 1000;
const DEFAULT_TEST_PASSWORD = 'Password123';
const AVATAR_BUCKET = 'user-avatars';
const EVENT_BUCKET = 'event-images';

Expand All @@ -123,31 +123,52 @@ async function main() {
console.log('--- Seeding Users...');

const coreUsers = [
{ email: 'admin@example.com', name: 'Admin', password: 'admin123', image: null, isAdmin: true },
{
email: 'admin@example.com',
name: 'admin',
displayName: 'Admin',
password: 'admin123',
image: null,
isAdmin: true,
},
{
email: 'alice@example.com',
name: 'Alice',
name: 'alice',
displayName: 'Alice',
password: DEFAULT_TEST_PASSWORD,
image: 'avatar-1.jpg',
},
{
email: 'bob@example.com',
name: 'Bob',
name: 'bob',
displayName: 'Bob',
password: DEFAULT_TEST_PASSWORD,
image: 'avatar-2.jpg',
},
{ email: 'cindy@example.com', name: 'Cindy', password: DEFAULT_TEST_PASSWORD, image: null },
{
email: 'cindy@example.com',
name: 'cindy',
displayName: 'Cindy',
password: DEFAULT_TEST_PASSWORD,
image: null,
},
];

// Process Core Users (Since they need avatars and specific upserts)
for (const u of coreUsers) {
const hashedPassword = await bcrypt.hash(u.password, 10);
const user = await prisma.user.upsert({
where: { email: u.email },
update: { name: u.name, password: hashedPassword, isConfirmed: true },
update: {
name: u.name,
displayName: u.displayName,
password: hashedPassword,
isConfirmed: true,
},
create: {
email: u.email,
name: u.name,
displayName: u.displayName,
password: hashedPassword,
isConfirmed: true,
isAdmin: u.isAdmin ?? false,
Expand Down Expand Up @@ -178,7 +199,8 @@ async function main() {
for (let i = 1; i <= TEST_RECORD_COUNT; i++) {
testUsersData.push({
email: `test${String(i)}@example.com`,
name: `TestUser${String(i)}`,
name: `testuser${String(i)}`,
displayName: `TestUser${String(i)}`,
password: testHashedPassword, // use pre-calculated hash to save time
isConfirmed: true,
isAdmin: false,
Expand Down
1 change: 1 addition & 0 deletions apps/backend/src/auth/auth.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export const ResAuthMeSchema = z.object({
id: z.number().int().positive(),
email: z.email(),
name: z.string().nullable(),
displayName: z.string().nullable().optional(),
avatarKey: z.string().nullable().optional(),
isConfirmed: z.boolean().default(false),
isAdmin: z.boolean().default(false),
Expand Down
15 changes: 13 additions & 2 deletions apps/backend/src/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,13 @@ export class AuthService {

// Logic for verifying user credentials
async validateUser(loginDto: LoginInput): Promise<ResAuthMeDto> {
const user = await this.userService.userGetByEmail(loginDto.email);
// Determine if the input is an email or username
const isEmail = loginDto.emailOrUsername.includes('@');

// Query by email or username accordingly
const user = isEmail
? await this.userService.userGetByEmail(loginDto.emailOrUsername)
: await this.userService.userGetByName(loginDto.emailOrUsername);

if (!user?.password) {
throw new UnauthorizedException('Invalid email or password');
Expand All @@ -45,6 +51,7 @@ export class AuthService {
id: user.id,
email: user.email,
name: user.name,
displayName: user.displayName,
avatarKey: user.avatarKey,
isConfirmed: user.isConfirmed,
isAdmin: user.isAdmin,
Expand All @@ -69,6 +76,7 @@ export class AuthService {
id: user.id,
email: user.email,
name: user.name,
displayName: user.displayName,
avatarKey: user.avatarKey,
isConfirmed: user.isConfirmed,
isAdmin: user.isAdmin,
Expand All @@ -92,6 +100,8 @@ export class AuthService {

while (attempts < maxAttempts) {
try {
const nanoId = this.generateNanoId();
const baseName = `${firstName}-${nanoId}`;
const user = await this.prisma.user.upsert({
where: { email },
update: {
Expand All @@ -100,7 +110,8 @@ export class AuthService {
},
create: {
email,
name: `${firstName}-${this.generateNanoId()}`,
name: baseName.toLowerCase(),
displayName: baseName,
googleId: providerId,
isConfirmed: true,
password: null,
Expand Down
7 changes: 4 additions & 3 deletions apps/backend/src/auth/tests/auth.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ describe('Auth E2E', () => {
it('returns user info and accessToken upon successful login', async () => {
const res = await request(app.getHttpServer())
.post('/auth/login')
.send({ email: user.email, password: 'Password123' })
.send({ emailOrUsername: user.email, password: 'Password123' })
.expect(201);

expect(res.body).toHaveProperty('accessToken');
Expand All @@ -100,15 +100,15 @@ describe('Auth E2E', () => {
it('returns 401 for unauthorized access (wrong password)', async () => {
const res = await request(app.getHttpServer())
.post('/auth/login')
.send({ email: user.email, password: 'WrongPassword123' })
.send({ emailOrUsername: user.email, password: 'WrongPassword123' })
.expect(401);
expect(res.body.message).toBe('Invalid email or password');
});

it('returns 401 for unauthorized access (wrong email)', async () => {
const res = await request(app.getHttpServer())
.post('/auth/login')
.send({ email: 'wrong@email.com', password: 'Password123' })
.send({ emailOrUsername: 'wrong@email.com', password: 'Password123' })
.expect(401);

expect(res.body.message).toBe('Invalid email or password');
Expand All @@ -127,6 +127,7 @@ describe('Auth E2E', () => {

expect(res.body).toStrictEqual({
avatarKey: user.avatarKey,
displayName: user.displayName ?? null,
email: user.email,
id: user.id,
isAdmin: user.isAdmin,
Expand Down
1 change: 1 addition & 0 deletions apps/backend/src/chat/chat.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export class ChatService {
select: {
id: true,
name: true,
displayName: true,
avatarKey: true,
},
},
Expand Down
9 changes: 8 additions & 1 deletion apps/backend/src/event/event.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ export class EventService {
select: {
id: true,
name: true,
displayName: true,
avatarKey: true,
},
},
Expand Down Expand Up @@ -182,7 +183,9 @@ export class EventService {
location: true,
files: true,
attendees: {
select: { user: { select: { id: true, name: true, avatarKey: true } } },
select: {
user: { select: { id: true, name: true, displayName: true, avatarKey: true } },
},
},
conversation: { select: { id: true } },
invites: {
Expand Down Expand Up @@ -301,6 +304,7 @@ export class EventService {
select: {
id: true,
name: true,
displayName: true,
avatarKey: true,
},
},
Expand Down Expand Up @@ -356,6 +360,7 @@ export class EventService {
select: {
id: true,
name: true,
displayName: true,
avatarKey: true,
},
},
Expand Down Expand Up @@ -415,6 +420,7 @@ export class EventService {
select: {
id: true,
name: true,
displayName: true,
avatarKey: true,
},
},
Expand Down Expand Up @@ -468,6 +474,7 @@ export class EventService {
select: {
id: true,
name: true,
displayName: true,
avatarKey: true,
},
},
Expand Down
40 changes: 21 additions & 19 deletions apps/backend/src/friends/friends.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ export class FriendsService {
receiverId,
},
include: {
requester: { select: { id: true, name: true, avatarKey: true } },
receiver: { select: { id: true, name: true, avatarKey: true } },
requester: { select: { id: true, name: true, displayName: true, avatarKey: true } },
receiver: { select: { id: true, name: true, displayName: true, avatarKey: true } },
},
});

Expand All @@ -84,8 +84,8 @@ export class FriendsService {
orderBy: [{ createdAt: 'desc' }, { id: 'asc' }],
take: limit + 1,
include: {
requester: { select: { id: true, name: true, avatarKey: true } },
receiver: { select: { id: true, name: true, avatarKey: true } },
requester: { select: { id: true, name: true, displayName: true, avatarKey: true } },
receiver: { select: { id: true, name: true, displayName: true, avatarKey: true } },
},
});

Expand Down Expand Up @@ -115,8 +115,8 @@ export class FriendsService {
orderBy: [{ createdAt: 'desc' }, { id: 'asc' }],
take: limit + 1,
include: {
requester: { select: { id: true, name: true, avatarKey: true } },
receiver: { select: { id: true, name: true, avatarKey: true } },
requester: { select: { id: true, name: true, displayName: true, avatarKey: true } },
receiver: { select: { id: true, name: true, displayName: true, avatarKey: true } },
},
});

Expand Down Expand Up @@ -148,7 +148,7 @@ export class FriendsService {
orderBy: [{ friend: { name: 'asc' } }, { id: 'asc' }],
take: limit + 1,
include: {
friend: { select: { id: true, name: true, avatarKey: true } },
friend: { select: { id: true, name: true, displayName: true, avatarKey: true } },
},
}),
// Count all friends for this user (ignoring the pagination cursor)
Expand Down Expand Up @@ -188,8 +188,8 @@ export class FriendsService {
const friendRequest = await this.prisma.friendRequest.findFirst({
where: { id: id },
include: {
requester: { select: { id: true, name: true, avatarKey: true } },
receiver: { select: { id: true, name: true, avatarKey: true } },
requester: { select: { id: true, name: true, displayName: true, avatarKey: true } },
receiver: { select: { id: true, name: true, displayName: true, avatarKey: true } },
},
});

Expand All @@ -208,7 +208,9 @@ export class FriendsService {
userId: userId,
friendId: friendRequest.requesterId,
},
include: { friend: { select: { id: true, name: true, avatarKey: true } } },
include: {
friend: { select: { id: true, name: true, displayName: true, avatarKey: true } },
},
}),
this.prisma.friends.create({
data: {
Expand All @@ -230,8 +232,8 @@ export class FriendsService {
const friendRequest = await this.prisma.friendRequest.findFirst({
where: { id: id },
include: {
requester: { select: { id: true, name: true, avatarKey: true } },
receiver: { select: { id: true, name: true, avatarKey: true } },
requester: { select: { id: true, name: true, displayName: true, avatarKey: true } },
receiver: { select: { id: true, name: true, displayName: true, avatarKey: true } },
},
});

Expand All @@ -247,8 +249,8 @@ export class FriendsService {
return await this.prisma.friendRequest.delete({
where: { id },
include: {
requester: { select: { id: true, name: true, avatarKey: true } },
receiver: { select: { id: true, name: true, avatarKey: true } },
requester: { select: { id: true, name: true, displayName: true, avatarKey: true } },
receiver: { select: { id: true, name: true, displayName: true, avatarKey: true } },
},
});
}
Expand All @@ -258,8 +260,8 @@ export class FriendsService {
const friendRequest = await this.prisma.friendRequest.findFirst({
where: { id: id },
include: {
requester: { select: { id: true, name: true, avatarKey: true } },
receiver: { select: { id: true, name: true, avatarKey: true } },
requester: { select: { id: true, name: true, displayName: true, avatarKey: true } },
receiver: { select: { id: true, name: true, displayName: true, avatarKey: true } },
},
});

Expand All @@ -275,8 +277,8 @@ export class FriendsService {
return await this.prisma.friendRequest.delete({
where: { id },
include: {
requester: { select: { id: true, name: true, avatarKey: true } },
receiver: { select: { id: true, name: true, avatarKey: true } },
requester: { select: { id: true, name: true, displayName: true, avatarKey: true } },
receiver: { select: { id: true, name: true, displayName: true, avatarKey: true } },
},
});
}
Expand All @@ -292,7 +294,7 @@ export class FriendsService {
userId: userId,
friendId: friendId,
},
include: { friend: { select: { id: true, name: true, avatarKey: true } } },
include: { friend: { select: { id: true, name: true, displayName: true, avatarKey: true } } },
});
if (!friendship) throw new BadRequestException('Friendship does not exist.');

Expand Down
Loading
Loading