Skip to content
Open
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
1 change: 1 addition & 0 deletions src/common/utils/api-id.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export const APIID = {
FIELDVALUES_CREATE: 'api.fieldValues.create',
FIELDVALUES_SEARCH: 'api.fieldValues.search',
FIELDVALUES_DELETE: 'api.fieldValues.delete',
FIELDVALUES_DOWNLOAD: 'api.fieldValues.download',
FIELD_OPTIONS_DELETE: 'api.fields.options.delete',
FIELD_DELETE: 'api.fields.delete',
LOGIN: 'api.login',
Expand Down
87 changes: 87 additions & 0 deletions src/fields/fields.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import {
ApiBasicAuth,
ApiHeader,
ApiQuery,
ApiOperation,
ApiParam,
} from '@nestjs/swagger';
import {
Controller,
Expand Down Expand Up @@ -769,4 +771,89 @@ export class FieldsController {
);
}
}

@Get('download-file/:fieldId')
@UseGuards(JwtAuthGuard)
@UseFilters(new AllExceptionsFilter(APIID.FIELDVALUES_DOWNLOAD))
@ApiOperation({
summary: 'Download a file from a field',
description: 'Downloads a file associated with a specific field. Only the file owner or admin users can download files.'
})
@ApiParam({
name: 'fieldId',
type: String,
description: 'The ID of the field containing the file to download'
})
@ApiHeader({
name: 'Authorization',
description: 'Bearer token for authentication'
})
@ApiForbiddenResponse({
description: 'User is not authorized to download this file'
})
async downloadFile(
@Param('fieldId') fieldId: string,
@Req() request: RequestWithUser,
@Res() response: Response
) {
try {
// Extract userId and role from bearer token
const userId = request.user?.userId || request.user?.sub;
const userRole = request.user?.role || request.user?.realm_access?.roles?.[0];

if (!userId) {
return APIResponse.error(
response,
APIID.FIELDVALUES_DOWNLOAD,
'User ID is required from authentication token.',
'USER_ID_REQUIRED',
HttpStatus.BAD_REQUEST
);
}

const downloadResult = await this.fileUploadService.downloadFile(
fieldId,
userId,
userRole
);

// Set response headers for file download
response.setHeader('Content-Type', downloadResult.contentType);
response.setHeader('Content-Disposition', `attachment; filename="${downloadResult.originalName}"`);
response.setHeader('Content-Length', downloadResult.size.toString());
response.setHeader('X-Field-Id', fieldId);
response.setHeader('X-File-Name', downloadResult.originalName);
response.setHeader('X-File-Size', downloadResult.size.toString());
response.setHeader('X-Status', 'downloaded');

// Send the file buffer and end the response
response.send(downloadResult.buffer);

Comment thread
vaibhavsTekdi marked this conversation as resolved.
} catch (error) {
// Only log unexpected errors, not validation errors
if (!(error instanceof FileValidationException)) {
console.log('Error in FieldsController downloadFile:', error);
}

if (error instanceof FileValidationException) {
// Extract the error message directly from the exception
const errorMsg = error.message;
return APIResponse.error(
response,
APIID.FIELDVALUES_DOWNLOAD,
errorMsg,
'File Download Error',
HttpStatus.BAD_REQUEST
);
}

return APIResponse.error(
response,
APIID.FIELDVALUES_DOWNLOAD,
'Failed to download file: ' + error.message,
API_RESPONSES.SERVER_ERROR,
HttpStatus.INTERNAL_SERVER_ERROR
);
}
}
}
77 changes: 73 additions & 4 deletions src/fields/fields.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import APIResponse from 'src/utils/response';
import { log } from 'util';
import { ErrorResponseTypeOrm } from 'src/error-response-typeorm';
import { FieldValueConverter } from 'src/utils/field-value-converter';
import { FieldValue, Field } from 'src/storage/interfaces/field-operations.interface';

@Injectable()
export class FieldsService {
Expand Down Expand Up @@ -442,14 +443,82 @@ export class FieldsService {
return fieldResponse;
}

async getField(fieldId: string): Promise<Fields> {
return this.fieldsRepository.findOne({ where: { fieldId } });
async getField(fieldId: string): Promise<Field | null> {
const field = await this.fieldsRepository.findOne({ where: { fieldId } });

if (!field) {
return null;
}

// Convert Fields entity to Field interface
return {
fieldId: field.fieldId,
name: field.name,
label: field.label,
type: field.type,
context: field.context,
state: field.state,
contextType: field.contextType,
fieldParams: field.fieldParams,
required: field.required,
metadata: field.metadata
};
}

async getFieldValue(fieldId: string, itemId: string): Promise<FieldValues> {
return this.fieldsValuesRepository.findOne({
async getFieldValue(fieldId: string, itemId: string): Promise<FieldValue | null> {
const fieldValue = await this.fieldsValuesRepository.findOne({
where: { fieldId: fieldId, itemId: itemId }
});

if (!fieldValue) {
return null;
}

// Convert FieldValues entity to FieldValue interface
return {
fieldValuesId: fieldValue.fieldValuesId,
value: fieldValue.value,
itemId: fieldValue.itemId,
fieldId: fieldValue.fieldId,
fileValue: fieldValue.fileValue,
textValue: fieldValue.textValue,
numberValue: fieldValue.numberValue,
calendarValue: fieldValue.calendarValue,
dropdownValue: fieldValue.dropdownValue,
radioValue: fieldValue.radioValue,
checkboxValue: fieldValue.checkboxValue,
textareaValue: fieldValue.textareaValue,
createdAt: fieldValue.createdAt,
updatedAt: fieldValue.updatedAt,
createdBy: fieldValue.createdBy,
updatedBy: fieldValue.updatedBy
};
}

async getFieldValuesByFieldId(fieldId: string): Promise<FieldValue[]> {
const fieldValues = await this.fieldsValuesRepository.find({
where: { fieldId: fieldId }
});

// Convert FieldValues entities to FieldValue interface
return fieldValues.map(fv => ({
fieldValuesId: fv.fieldValuesId,
value: fv.value,
itemId: fv.itemId,
fieldId: fv.fieldId,
fileValue: fv.fileValue,
textValue: fv.textValue,
numberValue: fv.numberValue,
calendarValue: fv.calendarValue,
dropdownValue: fv.dropdownValue,
radioValue: fv.radioValue,
checkboxValue: fv.checkboxValue,
textareaValue: fv.textareaValue,
createdAt: fv.createdAt,
updatedAt: fv.updatedAt,
createdBy: fv.createdBy,
updatedBy: fv.updatedBy
}));
}

async updateFieldValue(data: {
Expand Down
Loading