-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'develop' into datacatalog_update_v2
- Loading branch information
Showing
14 changed files
with
627 additions
and
76 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
'use strict'; | ||
|
||
/** @type {import('sequelize-cli').Migration} */ | ||
module.exports = { | ||
async up (queryInterface, Sequelize) { | ||
await queryInterface.createTable('CityInvite', { | ||
id: { | ||
type: Sequelize.UUID, | ||
primaryKey:true, | ||
}, | ||
city_id: { | ||
type: Sequelize.UUID, | ||
allowNull: true, | ||
references: { | ||
model:'City', | ||
key: 'city_id' | ||
} | ||
}, | ||
user_id: { | ||
type: Sequelize.STRING, | ||
allowNull:true, | ||
}, | ||
inviting_user_id: { | ||
type: Sequelize.UUID, | ||
allowNull: true, | ||
references: { | ||
model:'User', | ||
key: 'user_id' | ||
} | ||
}, | ||
status: { | ||
type: Sequelize.STRING, | ||
allowNull:false, | ||
defaultValue: 'pending' | ||
}, | ||
created: { | ||
type: Sequelize.DATE, | ||
allowNull: true, | ||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'), | ||
}, | ||
last_updated: { | ||
type: Sequelize.DATE, | ||
allowNull: true, | ||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'), | ||
onUpdate: Sequelize.literal('CURRENT_TIMESTAMP'), | ||
} | ||
}) | ||
}, | ||
|
||
async down (queryInterface, Sequelize) { | ||
await queryInterface.dropTable('CityInvite'); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import { db } from "@/models"; | ||
import { apiHandler } from "@/util/api"; | ||
import createHttpError from "http-errors"; | ||
import { Session } from "next-auth"; | ||
import { NextRequest, NextResponse } from "next/server"; | ||
import jwt from "jsonwebtoken"; | ||
|
||
export const GET = apiHandler(async (req, { params, session }) => { | ||
const invite = await db.models.CityInvite.findOne({ | ||
where: { | ||
id: params.invite, | ||
}, | ||
}); | ||
|
||
if (!invite) { | ||
throw new createHttpError.NotFound("Not found"); | ||
} | ||
|
||
const token = req.nextUrl.searchParams.get("token"); | ||
const email = req.nextUrl.searchParams.get("email"); | ||
|
||
const isVerified = jwt.verify(token!, process.env.VERIFICATION_TOKEN_SECRET!); | ||
|
||
if (!isVerified) { | ||
throw new createHttpError.BadRequest("Invalid token"); | ||
} | ||
|
||
await invite.update({ | ||
status: "accepted", | ||
}); | ||
|
||
const user = await db.models.User.findOne({ | ||
where: { | ||
email: email!, | ||
}, | ||
}); | ||
|
||
if (!user) { | ||
return NextResponse.redirect("/"); | ||
} | ||
|
||
const city = await db.models.City.findOne({ | ||
where: { cityId: invite.cityId }, | ||
}); | ||
|
||
await user?.addCity(city?.cityId); | ||
|
||
return NextResponse.redirect("/"); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import { db } from "@/models"; | ||
import { apiHandler } from "@/util/api"; | ||
import { createUserInvite } from "@/util/validation"; | ||
import { randomUUID } from "crypto"; | ||
import createHttpError from "http-errors"; | ||
import { NextResponse } from "next/server"; | ||
import jwt from "jsonwebtoken"; | ||
import { sendEmail } from "@/lib/email"; | ||
import { render } from "@react-email/components"; | ||
import InviteUserTemplate from "@/lib/emails/InviteUserTemplate"; | ||
import UserService from "@/backend/UserService"; | ||
|
||
export const POST = apiHandler(async (req, { params, session }) => { | ||
const body = createUserInvite.parse(await req.json()); | ||
const city = await UserService.findUserCity(body.cityId, session); | ||
|
||
const cityData = await db.models.City.findOne({ | ||
where: { cityId: city.cityId }, | ||
}); | ||
|
||
if (!cityData) { | ||
throw new createHttpError.NotFound("City not found"); | ||
} | ||
|
||
if (!process.env.VERIFICATION_TOKEN_SECRET) { | ||
console.error("Need to assign RESET_TOKEN_SECRET in env!"); | ||
throw createHttpError.InternalServerError("Configuration error"); | ||
} | ||
|
||
const invitationCode = jwt.sign( | ||
{ email: body.email, reason: "invite", city: body.cityId }, | ||
process.env.VERIFICATION_TOKEN_SECRET, | ||
{ | ||
expiresIn: "24h", | ||
}, | ||
); | ||
|
||
const invite = await db.models.CityInvite.create({ | ||
id: randomUUID(), | ||
...body, | ||
}); | ||
|
||
if (!invite) { | ||
throw new createHttpError.BadRequest("Something went wrong"); | ||
} | ||
const host = process.env.HOST ?? "http://localhost:3000"; | ||
const sendInvite = await sendEmail({ | ||
to: body.email!, | ||
subject: "City Catalyst - City Invitation", | ||
html: render( | ||
InviteUserTemplate({ | ||
url: `${host}/api/v0/city/invite/${invite.id}?token=${invitationCode}&email=${body.email}`, | ||
user: { email: body.email, name: body.name }, | ||
city, | ||
invitingUser: { | ||
name: session?.user.name!, | ||
email: session?.user.email!, | ||
}, | ||
members: city.users, | ||
}), | ||
), | ||
}); | ||
|
||
if (!sendInvite) | ||
throw new createHttpError.BadRequest("Email could not be sent"); | ||
|
||
return NextResponse.json({ data: invite }); | ||
}); |
Oops, something went wrong.