Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/allow citywide file access #371

Merged
merged 9 commits into from
Mar 19, 2024
22 changes: 22 additions & 0 deletions app/migrations/20240308152708-add-cityId-to-userFile.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use strict';

/** @type {import('sequelize-cli').Migration} */

module.exports = {
async up (queryInterface, Sequelize) {
await queryInterface.addColumn('UserFile', 'city_id', {
type: Sequelize.UUID,
allowNull: true,
references: {
model: 'City',
key: 'city_id',
},
onUpdate: 'CASCADE',
onDelete: 'SET NULL',
});
},

async down (queryInterface, Sequelize) {
await queryInterface.removeColumn('UserFile', 'city_id');
}
};
11 changes: 10 additions & 1 deletion app/src/app/[lng]/data/review/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,15 @@ export default function ReviewPage({

const [isConfirming, setIsConfirming] = useState<boolean>(false);

const { data: userInfo, isLoading: isUserInfoLoading } =
api.useGetUserInfoQuery();

const { data: inventory } = api.useGetInventoryQuery(
userInfo?.defaultInventoryId!,
{ skip: !userInfo },
);
const cityId = inventory?.city.cityId;

const onConfirm = async () => {
setIsConfirming(true);
try {
Expand All @@ -79,7 +88,7 @@ export default function ReviewPage({
formData.append("data", file, file.name);
}

await addUserFile(formData).then(() => {
await addUserFile({ formData, cityId }).then(() => {
// TODO
// Trigger notification to user
});
Expand Down
2 changes: 1 addition & 1 deletion app/src/app/[lng]/settings/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export default function Settings({
{ skip: !cityId },
);

const { data: userFiles } = api.useGetUserFilesQuery({
const { data: userFiles } = api.useGetUserFilesQuery(cityId!, {
skip: !userInfo,
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,14 @@ import createHttpError from "http-errors";
import { NextResponse } from "next/server";

export const GET = apiHandler(async (_req, context) => {
const userId = context.session?.user.id;
if (!context.session) {
throw new createHttpError.Unauthorized("Unauthorized");
}

const userFile = await db.models.UserFile.findOne({
where: {
id: context.params.file,
userId,
cityId: context.params.city,
},
});

Expand Down
46 changes: 46 additions & 0 deletions app/src/app/api/v0/city/[city]/file/[file]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { db } from "@/models";
import { apiHandler } from "@/util/api";
import createHttpError from "http-errors";
import { NextResponse } from "next/server";

export const GET = apiHandler(async (_req: Request, context) => {
const userId = context.session?.user.id;
if (!context.session) {
throw new createHttpError.Unauthorized("Unauthorized");
}

const userFile = await db.models.UserFile.findOne({
where: {
id: context.params.file,
cityId: context.params.city,
},
});

if (!userFile) {
throw new createHttpError.NotFound("User file not found");
}

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

export const DELETE = apiHandler(async (_req: Request, context) => {
const userId = context.session?.user.id;
if (!context.session) {
throw new createHttpError.Unauthorized("Unauthorized");
}

const userFile = await db.models.UserFile.findOne({
where: {
id: context.params.file,
cityId: context.params.city,
},
});

if (!userFile) {
throw new createHttpError.NotFound("User file not found");
}

await userFile.destroy();

return NextResponse.json({ data: userFile, deleted: true });
});
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,13 @@ const MAX_FILE_SIZE = 5000000;
const ACCEPTED_FILE_FORMATS = []; // file formats types to be parsed and refined later

export const GET = apiHandler(async (_req: Request, context) => {
const userId = context.session?.user.id;
if (!context.session) {
throw new createHttpError.Unauthorized("Unauthorized");
}

const userFiles = await db.models.UserFile.findAll({
where: {
userId: userId,
cityId: context.params.city,
},
});

Expand All @@ -38,7 +37,8 @@ export const GET = apiHandler(async (_req: Request, context) => {
});
return {
id: userFile.id,
userId: userFile.id,
userId: userFile.userId,
cityId: userFile.cityId,
fileReference: userFile.fileReference,
url: userFile.url,
sector: userFile.sector,
Expand Down Expand Up @@ -78,6 +78,7 @@ export const POST = apiHandler(async (req: NextRequest, context) => {

const fileData = {
userId: userId,
cityId: context.params.city,
fileReference: formData.get("fileReference"),
url: formData.get("url"),
data: buffer,
Expand All @@ -103,6 +104,7 @@ export const POST = apiHandler(async (req: NextRequest, context) => {
data: {
id: userFile.id,
userId: userFile.userId,
cityId: userFile.cityId,
fileReference: userFile.fileReference,
url: userFile.url,
sector: userFile.sector,
Expand Down
56 changes: 0 additions & 56 deletions app/src/app/api/v0/user/file/[file]/route.ts

This file was deleted.

2 changes: 1 addition & 1 deletion app/src/components/Modals/delete-file-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const DeleteFileModal: FC<DeleteFileModalProps> = ({
const [deleteUserFile] = api.useDeleteUserFileMutation();
const onDeleteFile = async () => {
try {
await deleteUserFile({ fileId: fileData?.id });
await deleteUserFile({ fileId: fileData?.id, cityId: fileData?.cityId });
} catch (error) {
console.error(error);
} finally {
Expand Down
2 changes: 1 addition & 1 deletion app/src/components/Tabs/my-files-tab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,7 @@ const MyFilesTab: FC<MyFilesTabProps> = ({
}}
>
<Link
href={`/api/v0/user/file/${file.id}/download-file`}
href={`/api/v0/city/${file.cityId}/file/${file.id}/download-file`}
download
className="flex gap-4"
>
Expand Down
12 changes: 12 additions & 0 deletions app/src/models/UserFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { User, UserId } from "./User";
export interface UserFileAttributes {
id: string;
userId?: string;
cityId?: string;
fileReference?: string;
data?: Buffer | any;
fileType?: string;
Expand All @@ -21,6 +22,7 @@ export type UserFilePk = "id";
export type UserFileId = UserFile[UserFilePk];
export type UserFileOptionalAttributes =
| "userId"
| "cityId"
| "fileReference"
| "data"
| "fileType"
Expand All @@ -42,6 +44,7 @@ export class UserFile
{
id!: string;
userId?: string;
cityId?: string;
fileReference?: string;
data?: Buffer;
fileType?: string;
Expand Down Expand Up @@ -77,6 +80,15 @@ export class UserFile
},
field: "user_id",
},
cityId: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: "City",
key: "city_id",
},
field: "city_id",
},
fileReference: {
type: DataTypes.STRING(255),
allowNull: true,
Expand Down
2 changes: 2 additions & 0 deletions app/src/models/init-models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,8 @@ export function initModels(sequelize: Sequelize) {
});
User.hasMany(UserFile, { foreignKey: "userId", as: "user" });
UserFile.belongsTo(User, { as: "userFiles", foreignKey: "userId" });
UserFile.belongsTo(City, { foreignKey: "cityId", as: "city" });
City.hasMany(UserFile, { foreignKey: "cityId", as: "userFiles" });
City.hasMany(CityInvite, { as: "cityInvite", foreignKey: "cityId" });
CityInvite.belongsTo(City, { as: "cityInvites", foreignKey: "cityId" });
GasValue.belongsTo(InventoryValue, {
Expand Down
10 changes: 5 additions & 5 deletions app/src/services/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -319,10 +319,10 @@ export const api = createApi({
transformResponse: (response: { data: any }) => response.data,
}),
addUserFile: builder.mutation<UserFileResponse, any>({
query: (formData) => {
query: ({ formData, cityId }) => {
return {
method: "POST",
url: `/user/file`,
url: `city/${cityId}/file`,
body: formData,
};
},
Expand All @@ -331,9 +331,9 @@ export const api = createApi({
invalidatesTags: ["FileData"],
}),
getUserFiles: builder.query({
query: () => ({
query: (cityId: string) => ({
method: "GET",
url: `/user/file`,
url: `/city/${cityId}/file`,
}),
transformResponse: (response: { data: UserFileResponse }) => {
return response.data;
Expand All @@ -344,7 +344,7 @@ export const api = createApi({
deleteUserFile: builder.mutation({
query: (params) => ({
method: "DELETE",
url: `/user/file/${params.fileId}`,
url: `/city/${params.cityId}/file/${params.fileId}`,
}),
transformResponse: (response: { data: UserFileResponse }) =>
response.data,
Expand Down
3 changes: 2 additions & 1 deletion app/src/util/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,8 @@ export type CreatePopulationRequest = z.infer<typeof createPopulationRequest>;

// user file schema validation
export const createUserFileRequset = z.object({
userId: z.string().uuid().optional(),
userId: z.string().uuid(),
cityId: z.string().uuid(),
fileReference: z.string().optional(),
data: z.any(),
fileType: z.string().optional(),
Expand Down
4 changes: 2 additions & 2 deletions app/tests/api/userfile.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import {
POST as createUserFile,
GET as findUserFiles,
} from "@/app/api/v0/user/file/route";
} from "@/app/api/v0/city/[city]/file/route";

import {
DELETE as deleteUserfile,
GET as findUserFile,
} from "@/app/api/v0/user/file/[file]/route";
} from "@/app/api/v0/city/[city]/file/[file]/route";

import { db } from "@/models";
import assert from "node:assert";
Expand Down
Loading