Skip to content

Commit

Permalink
Store and fetch a font file in the backend
Browse files Browse the repository at this point in the history
  • Loading branch information
imnasnainaec committed Jul 27, 2023
1 parent cd36b2a commit 3a32e39
Show file tree
Hide file tree
Showing 8 changed files with 202 additions and 31 deletions.
34 changes: 34 additions & 0 deletions Backend/Controllers/ProjectController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -247,5 +247,39 @@ public async Task<IActionResult> GetProjectFonts(string projectId)

return Ok(await Font.GetFonts(fonts));
}

/// <summary> Gets the font file in the form of a stream from disk. </summary>
/// <returns> Font file stream. </returns>
[AllowAnonymous]
[HttpGet("fonts/{fontId}/{fileName}", Name = "DownloadFont")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(FileContentResult))]
public IActionResult DownloadFont(string fontId, string fileName)
{
// SECURITY: Omitting authentication so the frontend can use the API endpoint directly as a URL.
// if (!await _permissionService.HasProjectPermission(HttpContext, Permission.WordEntry))
// {
// return Forbid();
// }

// Sanitize user input
try
{
fontId = Sanitization.SanitizeId(fontId);
fileName = Sanitization.SanitizeFileName(fileName);
}
catch
{
return new UnsupportedMediaTypeResult();
}

var filePath = FileStorage.GenerateFontFilePath(fontId, fileName);
if (!System.IO.File.Exists(filePath))
{
return BadRequest("Font file does not exist.");
}

var file = System.IO.File.OpenRead(filePath);
return File(file, "application/octet-stream");
}
}
}
35 changes: 27 additions & 8 deletions Backend/Helper/FileStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ public static class FileStorage
{
private const string CombineFilesDir = ".CombineFiles";
private const string AvatarsDir = "Avatars";
private const string FontsDir = "Fonts";
private const string publicEnvName = "PUBLIC_URL";
private const string FontsDir = "fonts";
private const string publicEnvName = "FRONTEND_PUBLIC_URL";
private static readonly string ImportExtractedLocation = Path.Combine("Import", "ExtractedLocation");
private static readonly string LiftImportSuffix = Path.Combine(ImportExtractedLocation, "Lift");
private static readonly string AudioPathSuffix = Path.Combine(LiftImportSuffix, "audio");
Expand Down Expand Up @@ -103,20 +103,39 @@ public static string GenerateAvatarFilePath(string userId)
return GenerateFilePath(AvatarsDir, userId, FileType.Avatar);
}

/// <summary> Generate the path to where fonts are stored. </summary>
/// <summary>
/// Generate the path to where Avatar images are stored.
/// </summary>
/// <exception cref="InvalidIdException"> Throws when id invalid. </exception>
public static string GenerateFontDirPath(string fontId)
public static string GenerateFontFilePath(string fontId, string fileName, bool relative = false)
{
fontId = Sanitization.SanitizeId(fontId);

if (fontId == "assets")
if (relative)
{
throw new InvalidIdException();
return Path.Combine(GetFontsDir(fontId, true), fileName);
}

var publicDirPath = Environment.GetEnvironmentVariable(publicEnvName)!;
return GenerateFilePath(GetFontsDir(fontId), fileName);
}

public static string GetFrontendPublicDirPath()
{
return Environment.GetEnvironmentVariable(publicEnvName) ?? "";
}

/// <summary> Generate the path to where fonts are stored. </summary>
/// <exception cref="InvalidIdException"> Throws when id invalid. </exception>
public static string GetFontsDir(string fontId, bool relative = false)
{
fontId = Sanitization.SanitizeId(fontId);

if (relative)
{
return Path.Combine(FontsDir, fontId);
}

return Path.Combine(publicDirPath, FontsDir, fontId);
return GenerateDirPath(Path.Combine(FontsDir, fontId), true);
}

/// <summary>
Expand Down
12 changes: 4 additions & 8 deletions Backend/Helper/Font.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,11 @@ public static async Task<List<string>> GetFonts(List<string> fonts)
sortedFonts.Sort();

var fontId = "notoseriftangut";
var dir = FileStorage.GenerateFontDirPath(fontId);
if (!Directory.Exists(dir))
{
Directory.CreateDirectory(dir);
}

var family = "Noto Serif Tangut";
var variant = Variant.Regular;
var filePath = Path.Combine(dir, "NotoSerifTangut-Regular.ttf");
var fileName = "NotoSerifTangut-Regular.ttf";
var filePath = FileStorage.GenerateFontFilePath(fontId, fileName);
var format = "ttf";
//var mlpFamily = "Noto Sans Tangut";

Expand All @@ -47,8 +43,8 @@ public static async Task<List<string>> GetFonts(List<string> fonts)
await stream.CopyToAsync(fs);
}

var path = "%PUBLIC_URL%/NotoSerifTangut-Regular.ttf";
return new List<string> { GenerateFontCss(family, variant, path, format) };
var rel_path = FileStorage.GenerateFontFilePath(fontId, fileName, true);
return new List<string> { GenerateFontCss(family, variant, $"%BASE_PATH%/{rel_path}", format) };
}

public static string GenerateFontCss(
Expand Down
131 changes: 131 additions & 0 deletions src/api/api/project-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,54 @@ export const ProjectApiAxiosParamCreator = function (
options: localVarRequestOptions,
};
},
/**
*
* @param {string} fontId
* @param {string} fileName
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
downloadFont: async (
fontId: string,
fileName: string,
options: any = {}
): Promise<RequestArgs> => {
// verify required parameter 'fontId' is not null or undefined
assertParamExists("downloadFont", "fontId", fontId);
// verify required parameter 'fileName' is not null or undefined
assertParamExists("downloadFont", "fileName", fileName);
const localVarPath = `/v1/projects/font/{fontId}/{fileName}`
.replace(`{${"fontId"}}`, encodeURIComponent(String(fontId)))
.replace(`{${"fileName"}}`, encodeURIComponent(String(fileName)));
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}

const localVarRequestOptions = {
method: "GET",
...baseOptions,
...options,
};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;

setSearchParams(localVarUrlObj, localVarQueryParameter, options.query);
let headersFromBaseOptions =
baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {
...localVarHeaderParameter,
...headersFromBaseOptions,
...options.headers,
};

return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
*
* @param {string} projectId
Expand Down Expand Up @@ -586,6 +634,32 @@ export const ProjectApiFp = function (configuration?: Configuration) {
configuration
);
},
/**
*
* @param {string} fontId
* @param {string} fileName
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async downloadFont(
fontId: string,
fileName: string,
options?: any
): Promise<
(axios?: AxiosInstance, basePath?: string) => AxiosPromise<any>
> {
const localVarAxiosArgs = await localVarAxiosParamCreator.downloadFont(
fontId,
fileName,
options
);
return createRequestFunction(
localVarAxiosArgs,
globalAxios,
BASE_PATH,
configuration
);
},
/**
*
* @param {string} projectId
Expand Down Expand Up @@ -798,6 +872,22 @@ export const ProjectApiFactory = function (
.deleteProject(projectId, options)
.then((request) => request(axios, basePath));
},
/**
*
* @param {string} fontId
* @param {string} fileName
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
downloadFont(
fontId: string,
fileName: string,
options?: any
): AxiosPromise<any> {
return localVarFp
.downloadFont(fontId, fileName, options)
.then((request) => request(axios, basePath));
},
/**
*
* @param {string} projectId
Expand Down Expand Up @@ -924,6 +1014,27 @@ export interface ProjectApiDeleteProjectRequest {
readonly projectId: string;
}

/**
* Request parameters for downloadFont operation in ProjectApi.
* @export
* @interface ProjectApiDownloadFontRequest
*/
export interface ProjectApiDownloadFontRequest {
/**
*
* @type {string}
* @memberof ProjectApiDownloadFont
*/
readonly fontId: string;

/**
*
* @type {string}
* @memberof ProjectApiDownloadFont
*/
readonly fileName: string;
}

/**
* Request parameters for getAllProjectUsers operation in ProjectApi.
* @export
Expand Down Expand Up @@ -1073,6 +1184,26 @@ export class ProjectApi extends BaseAPI {
.then((request) => request(this.axios, this.basePath));
}

/**
*
* @param {ProjectApiDownloadFontRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof ProjectApi
*/
public downloadFont(
requestParameters: ProjectApiDownloadFontRequest,
options?: any
) {
return ProjectApiFp(this.configuration)
.downloadFont(
requestParameters.fontId,
requestParameters.fileName,
options
)
.then((request) => request(this.axios, this.basePath));
}

/**
*
* @param {ProjectApiGetAllProjectUsersRequest} requestParameters Request parameters.
Expand Down
2 changes: 1 addition & 1 deletion src/backend/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import { Bcp47Code } from "types/writingSystem";
import { convertGoalToEdit } from "utilities/goalUtilities";

export const baseURL = `${RuntimeConfig.getInstance().baseUrl()}`;
const apiBaseURL = `${baseURL}/v1`;
export const apiBaseURL = `${baseURL}/v1`;
const config_parameters: Api.ConfigurationParameters = { basePath: baseURL };
const config = new Api.Configuration(config_parameters);

Expand Down
12 changes: 4 additions & 8 deletions src/components/App/AppLoggedIn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Theme, ThemeProvider, createTheme } from "@mui/material/styles";
import { ReactElement, useEffect, useState } from "react";
import { Route, Routes } from "react-router-dom";

import { getFonts } from "backend";
import { apiBaseURL, getFonts } from "backend";
import SignalRHub from "components/App/SignalRHub";
import AppBar from "components/AppBar/AppBarComponent";
import PageNotFound from "components/PageNotFound/component";
Expand Down Expand Up @@ -38,11 +38,11 @@ export default function AppWithBar(): ReactElement {
useEffect(() => {
if (projId) {
getFonts(projId).then((fontCss) => {
console.info(process.env);
setStyleOverrides(
fontCss[0]
.replace("\r", "")
.replace("%PUBLIC_URL%", process.env.PUBLIC_URL)
.replaceAll("\r", "")
.replaceAll("\\", "/")
.replace("%BASE_PATH%", `${apiBaseURL}/projects`)
);
});
}
Expand All @@ -63,10 +63,6 @@ export default function AppWithBar(): ReactElement {
styleOverrides,
},
},
typography: {
...theme.typography,
fontFamily: "'Noto Serif Tangut'",
},
})
: theme;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,6 @@ interface GlossListProps {

function GlossList(props: GlossListProps): ReactElement {
const langs = props.glosses.map((g) => g.language);
console.info(props.glosses);
const glosses = langs.includes(props.defaultLang.bcp47)
? props.glosses
: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,6 @@ export class ReviewEntriesGloss implements Gloss {
constructor(def = "", language = "", writingSystems?: WritingSystem[]) {
this.def = def;
this.language = language;
const writingSystem = writingSystems?.find((ws) => ws.bcp47 === language);
if (writingSystem) {
console.info(writingSystems);
this.font = writingSystem.font;
}
this.font = writingSystems?.find((ws) => ws.bcp47 === language)?.font;
}
}

0 comments on commit 3a32e39

Please sign in to comment.