Skip to content

Commit

Permalink
Merge branch 'develop' into datacatalog_update_v2
Browse files Browse the repository at this point in the history
  • Loading branch information
mfonsecaOEF authored Mar 12, 2024
2 parents 9dbe962 + b639346 commit ebfe113
Show file tree
Hide file tree
Showing 14 changed files with 627 additions and 76 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/web-actions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,8 @@ jobs:
kubectl set env deployment/cc-web-deploy SMTP_PASSWORD=${{secrets.SMTP_PASSWORD}}
kubectl set env deployment/cc-web-deploy NEXTAUTH_SECRET=${{secrets.NEXTAUTH_SECRET}}
kubectl set env deployment/cc-web-deploy RESET_TOKEN_SECRET=${{secrets.RESET_TOKEN_SECRET}}
kubectl set env deployment/cc-web-deploy VERIFICATION_TOKEN_SECRET=${{secrets.VERIFICATION_TOKEN_SECRET}}
kubectl set env deployment/cc-web-deploy CHAT_PROVIDER=huggingface
kubectl set env deployment/cc-web-deploy OPENAI_API_KEY=${{secrets.OPENAI_API_KEY}}
kubectl set env deployment/cc-web-deploy HUGGINGFACE_API_KEY=${{secrets.HUGGINGFACE_API_KEY}}
kubectl rollout restart deployment cc-web-deploy -n default
53 changes: 53 additions & 0 deletions app/migrations/20240213184421-city-invite.cjs
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');
}
};
1 change: 1 addition & 0 deletions app/src/app/[lng]/settings/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ export default function Settings({
userInfo={userInfo}
cities={cities}
cityUsers={cityUsers}
defaultCityId={cityId}
/>
<MyFilesTab
lng={lng}
Expand Down
22 changes: 12 additions & 10 deletions app/src/app/api/v0/city/[city]/user/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,24 @@ import createHttpError from "http-errors";
import { NextResponse } from "next/server";
import { randomUUID } from "node:crypto";

export const POST = apiHandler(async (_req, { params, session }) => {
const body = createUserRequest.parse(await _req.json());
export const POST = apiHandler(async (req, { params, session }) => {
const body = await req.json();

const city = await UserService.findUserCity(params.city, session);
// check if the user exists

// TODO shouldn't the users sign up themselves? This will probably prevent signup
const user = await db.models.User.create({
userId: randomUUID(),
...body,
const existingUser = await db.models.User.findOne({
where: { email: body.email! },
});
user.addCity(city.cityId);

return NextResponse.json({ data: user });
if (!existingUser) {
// return a message to ui for the flow to continue and not break
return NextResponse.json({ message: "User not found" });
}

return NextResponse.json({ data: existingUser });
});

export const GET = apiHandler(async (_req, { params, session }) => {
export const GET = apiHandler(async (req, { params, session }) => {
const city = await UserService.findUserCity(params.city, session);

const users = await db.models.User.findAll({
Expand Down
49 changes: 49 additions & 0 deletions app/src/app/api/v0/city/invite/[invite]/route.ts
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("/");
});
68 changes: 68 additions & 0 deletions app/src/app/api/v0/city/invite/route.ts
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 });
});
Loading

0 comments on commit ebfe113

Please sign in to comment.