From 92da8e04283a9343a3659408f5b60a3e7ec35120 Mon Sep 17 00:00:00 2001 From: Hsu Zhong Jun <27919917+dcshzj@users.noreply.github.com> Date: Mon, 6 Nov 2023 15:42:41 +0800 Subject: [PATCH] refactor(v1): completely nuke v1 endpoints and associated classes --- docs/openapi.yaml | 1436 ----------------- src/classes/Collection.js | 236 --- src/classes/Config.js | 211 --- src/classes/Directory.js | 136 -- src/classes/File.js | 281 ---- src/classes/MediaFile.js | 218 --- src/classes/MediaSubfolder.js | 98 -- src/classes/NetlifyToml.js | 41 - src/classes/Resource.js | 164 -- src/classes/ResourceRoom.js | 247 --- src/classes/Settings.js | 260 --- src/classes/Subfolder.js | 45 - src/middleware/stats.ts | 4 - src/routes/v1/auth.js | 174 -- src/routes/v1/authenticated/index.js | 33 - src/routes/v1/authenticated/sites.js | 208 --- .../v1/authenticatedSites/collectionPages.js | 298 ---- .../v1/authenticatedSites/collections.js | 214 --- src/routes/v1/authenticatedSites/directory.js | 44 - src/routes/v1/authenticatedSites/documents.js | 223 --- src/routes/v1/authenticatedSites/folders.js | 175 -- src/routes/v1/authenticatedSites/homepage.js | 77 - src/routes/v1/authenticatedSites/images.js | 225 --- src/routes/v1/authenticatedSites/index.js | 60 - .../v1/authenticatedSites/mediaSubfolder.js | 100 -- .../v1/authenticatedSites/navigation.js | 73 - .../v1/authenticatedSites/netlifyToml.js | 43 - src/routes/v1/authenticatedSites/pages.js | 262 --- .../v1/authenticatedSites/resourcePages.js | 211 --- .../v1/authenticatedSites/resourceRoom.js | 93 -- src/routes/v1/authenticatedSites/resources.js | 154 -- src/routes/v1/authenticatedSites/settings.js | 49 - src/server.js | 20 - .../AuthenticationMiddlewareService.ts | 9 +- .../AuthenticationMiddlewareService.spec.ts | 10 +- src/utils/route-utils.js | 46 - 36 files changed, 7 insertions(+), 6171 deletions(-) delete mode 100644 docs/openapi.yaml delete mode 100644 src/classes/Collection.js delete mode 100644 src/classes/Config.js delete mode 100644 src/classes/Directory.js delete mode 100644 src/classes/File.js delete mode 100644 src/classes/MediaFile.js delete mode 100644 src/classes/MediaSubfolder.js delete mode 100644 src/classes/NetlifyToml.js delete mode 100644 src/classes/Resource.js delete mode 100644 src/classes/ResourceRoom.js delete mode 100644 src/classes/Settings.js delete mode 100644 src/classes/Subfolder.js delete mode 100644 src/routes/v1/auth.js delete mode 100644 src/routes/v1/authenticated/index.js delete mode 100644 src/routes/v1/authenticated/sites.js delete mode 100644 src/routes/v1/authenticatedSites/collectionPages.js delete mode 100644 src/routes/v1/authenticatedSites/collections.js delete mode 100644 src/routes/v1/authenticatedSites/directory.js delete mode 100644 src/routes/v1/authenticatedSites/documents.js delete mode 100644 src/routes/v1/authenticatedSites/folders.js delete mode 100644 src/routes/v1/authenticatedSites/homepage.js delete mode 100644 src/routes/v1/authenticatedSites/images.js delete mode 100644 src/routes/v1/authenticatedSites/index.js delete mode 100644 src/routes/v1/authenticatedSites/mediaSubfolder.js delete mode 100644 src/routes/v1/authenticatedSites/navigation.js delete mode 100644 src/routes/v1/authenticatedSites/netlifyToml.js delete mode 100644 src/routes/v1/authenticatedSites/pages.js delete mode 100644 src/routes/v1/authenticatedSites/resourcePages.js delete mode 100644 src/routes/v1/authenticatedSites/resourceRoom.js delete mode 100644 src/routes/v1/authenticatedSites/resources.js delete mode 100644 src/routes/v1/authenticatedSites/settings.js delete mode 100644 src/utils/route-utils.js diff --git a/docs/openapi.yaml b/docs/openapi.yaml deleted file mode 100644 index 592bdf994..000000000 --- a/docs/openapi.yaml +++ /dev/null @@ -1,1436 +0,0 @@ -openapi: 3.0.0 -info: - version: "0.0.1" - title: IsomerCMS API - description: | - # API documentation for IsomerCMS -servers: - - url: "https://cms-api.isomerpages.com" - description: "Production API Server" - - url: "https://staging-cms-api.isomerpages.com" - description: "Staging API Server" -tags: - - name: Authentication - - name: Collections - - name: Collection Pages - - name: Folders - - name: Images - - name: Documents - - name: Pages - - name: Sites - - name: Resource Room - - name: Resources - - name: Resource Pages - - name: Menus -components: - securitySchemes: - ApiKeyAuthentication: - type: apiKey - in: header - name: x-vault-api-key - schemas: - OAuthParams: - properties: - client_id: - type: string - state: - type: string - request_uri: - type: string - ResourcePageListResponse: - properties: - resourcePages: - type: array - items: - type: string - CollectionListResponse: - properties: - collections: - type: array - items: - type: string - CollectionPageListResponse: - properties: - collectionPages: - type: array - items: - type: string - FolderListItem: - type: object - properties: - name: - type: string - description: File or directory name - path: - type: string - description: File or directory path - sha: - type: string - description: SHA of the data object - size: - type: number - description: Size of the file in bytes (this defaults to 0 for directories) - content: - type: string - description: Content of the file in base 64 (this is null for directories) - type: - type: string - description: Either 'dir' or 'file' - FolderListResponse: - properties: - folderPages: - type: array - description: Array of file or directory data types - items: - $ref: "#/components/schemas/FolderListItem" - PageListResponse: - properties: - pages: - type: array - items: - type: string - DocumentListResponse: - properties: - documents: - type: array - items: - type: string - ImageListResponse: - properties: - documents: - type: array - items: - type: string - SiteListResponse: - properties: - siteNames: - type: array - items: - type: string - ResourceListResponse: - properties: - resources: - type: array - items: - type: string - resourceRoomName: - type: string - MenuListResponse: - properties: - menus: - type: array - items: - type: string - ResourceResponse: - properties: - resourceName: - type: string - MenuResponse: - properties: - menuName: - type: string - content: - type: string - sha: - type: string - CollectionNameResponse: - properties: - collectionName: - type: string - ResourcePageResponse: - properties: - resourceName: - type: string - pageName: - type: string - content: - type: string - sha: - type: string - CollectionPageResponse: - properties: - collectionName: - type: string - pageName: - type: string - content: - type: string - sha: - type: string - PageResponse: - properties: - pageName: - type: string - content: - type: string - sha: - type: string - DocumentResponse: - properties: - documentName: - type: string - content: - type: string - sha: - type: string - ImageResponse: - properties: - imageName: - type: string - content: - type: string - sha: - type: string - ShaAndContent: - properties: - sha: - type: string - content: - type: string - ContentAndPageName: - properties: - pageName: - type: string - content: - type: string - ContentAndDocumentName: - properties: - documentName: - type: string - content: - type: string - ContentAndImageName: - properties: - imageName: - type: string - content: - type: string - RenameCollection: - properties: - collectionName: - type: string - newCollectionName: - type: string - RenamePage: - properties: - pageName: - type: string - newPageName: - type: string - RenameDocument: - properties: - documentName: - type: string - newDocumentName: - type: string - RenameImage: - properties: - imageName: - type: string - newDocumentName: - type: string - RenameResource: - properties: - resourceName: - type: string - newResourceName: - type: string - ResourceRoom: - properties: - resourceRoom: - type: string - - -paths: - /v1: - get: - tags: - - Authentication - description: Obtain parameters for the GitHub OAuth link - responses: - 200: - description: Success - content: - application/json: - schema: - $ref: "#/components/schemas/OAuthParams" - /v1/sites/{siteName}/folders: - get: - tags: - - Folders - parameters: - - name: siteName - in: path - required: true - schema: - type: string - - name: path - description: The path of the directory from which we want to retrieve folder contents - in: query - schema: - type: string - responses: - 200: - description: Success - content: - application/json: - schema: - $ref: "#/components/schemas/FolderListResponse" - /v1/sites/{siteName}/collections: - get: - tags: - - Collections - parameters: - - name: siteName - in: path - required: true - schema: - type: string - description: List collections - responses: - 200: - description: Success - content: - application/json: - schema: - $ref: "#/components/schemas/CollectionListResponse" - post: - tags: - - Collections - description: Create new collection - parameters: - - name: siteName - in: path - required: true - schema: - type: string - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/CollectionNameResponse" - responses: - 200: - description: Success - content: - application/json: - schema: - $ref: "#/components/schemas/CollectionNameResponse" - /v1/sites/{siteName}/collections/{collectionName}: - get: - tags: - - Collection Pages - parameters: - - name: siteName - in: path - required: true - schema: - type: string - - name: collectionName - in: path - required: true - schema: - type: string - description: List collection pages - responses: - 200: - description: Success - content: - application/json: - schema: - $ref: "#/components/schemas/CollectionPageListResponse" - delete: - tags: - - Collections - parameters: - - name: siteName - in: path - required: true - schema: - type: string - - name: collectionName - in: path - required: true - schema: - type: string - description: Delete collection - responses: - 200: - description: Success - content: - application/json: - schema: - $ref: "#/components/schemas/CollectionNameResponse" - /v1/sites/{siteName}/collections/{collectionName}/rename/{newCollectionName}: - post: - tags: - - Collections - description: Rename collection - parameters: - - name: siteName - in: path - required: true - schema: - type: string - - name: collectionName - in: path - required: true - schema: - type: string - - name: newCollectionName - in: path - required: true - schema: - type: string - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/RenameCollection" - responses: - 200: - description: Success - content: - application/json: - schema: - $ref: "#/components/schemas/RenameCollection" - /v1/sites/{siteName}/collections/{collectionName}/pages: - post: - tags: - - Collection Pages - parameters: - - name: siteName - in: path - required: true - schema: - type: string - - name: collectionName - in: path - required: true - schema: - type: string - description: Create new collection page - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/ContentAndPageName" - responses: - 200: - description: Success - content: - application/json: - schema: - $ref: "#/components/schemas/CollectionListResponse" - /v1/sites/{siteName}/collections/{collectionName}/pages/{pageName}: - get: - tags: - - Collection Pages - parameters: - - name: siteName - in: path - required: true - schema: - type: string - - name: collectionName - in: path - required: true - schema: - type: string - - name: pageName - in: path - required: true - schema: - type: string - description: Read collection page - responses: - 200: - description: Success - content: - application/json: - schema: - $ref: "#/components/schemas/CollectionPageResponse" - post: - tags: - - Collection Pages - parameters: - - name: siteName - in: path - required: true - schema: - type: string - - name: collectionName - in: path - required: true - schema: - type: string - - name: pageName - in: path - required: true - schema: - type: string - description: Update collection page - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/ShaAndContent" - responses: - 200: - description: Success - content: - application/json: - schema: - $ref: "#/components/schemas/CollectionPageResponse" - delete: - tags: - - Collection Pages - parameters: - - name: siteName - in: path - required: true - schema: - type: string - - name: collectionName - in: path - required: true - schema: - type: string - - name: pageName - in: path - required: true - schema: - type: string - - name: sha - in: body - required: true - schema: - type: string - description: Delete collection page - responses: - 200: - description: Success - /v1/sites/{siteName}/collections/{collectionName}/pages/{pageName}/rename/{newPageName}: - post: - tags: - - Collection Pages - parameters: - - name: siteName - in: path - required: true - schema: - type: string - - name: collectionName - in: path - required: true - schema: - type: string - - name: pageName - in: path - required: true - schema: - type: string - - name: newPageName - in: path - required: true - schema: - type: string - description: Rename collection page - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/ShaAndContent" - responses: - 200: - description: Success - content: - application/json: - schema: - $ref: "#/components/schemas/CollectionPageResponse" - - /v1/sites/{siteName}/pages: - get: - tags: - - Pages - parameters: - - name: siteName - in: path - required: true - schema: - type: string - description: List pages - responses: - 200: - description: Success - content: - application/json: - schema: - $ref: "#/components/schemas/PageListResponse" - post: - tags: - - Pages - parameters: - - name: siteName - in: path - required: true - schema: - type: string - - name: pageName - in: path - required: true - schema: - type: string - description: Create page - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/ContentAndPageName" - responses: - 200: - description: Success - content: - application/json: - schema: - $ref: "#/components/schemas/PageResponse" - /v1/sites/{siteName}/pages/{pageName}: - get: - tags: - - Pages - parameters: - - name: siteName - in: path - required: true - schema: - type: string - - name: pageName - in: path - required: true - schema: - type: string - description: Read page - responses: - 200: - description: Success - content: - application/json: - schema: - $ref: "#/components/schemas/PageResponse" - post: - tags: - - Pages - parameters: - - name: siteName - in: path - required: true - schema: - type: string - - name: pageName - in: path - required: true - schema: - type: string - description: Update page - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/ShaAndContent" - responses: - 200: - description: Success - content: - application/json: - schema: - $ref: "#/components/schemas/PageResponse" - delete: - tags: - - Pages - parameters: - - name: siteName - in: path - required: true - schema: - type: string - - name: pageName - in: path - required: true - schema: - type: string - - name: sha - in: body - required: true - schema: - type: string - description: Delete page - responses: - 200: - description: Success - /v1/sites/{siteName}/pages/{pageName}/rename/{newPageName}: - post: - tags: - - Pages - parameters: - - name: siteName - in: path - required: true - schema: - type: string - - name: pageName - in: path - required: true - schema: - type: string - - name: newPageName - in: path - required: true - schema: - type: string - description: Rename page - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/RenamePage" - responses: - 200: - description: Success - content: - application/json: - schema: - $ref: "#/components/schemas/PageResponse" - - /v1/sites/{siteName}/documents: - get: - tags: - - Documents - parameters: - - name: siteName - in: path - required: true - schema: - type: string - description: List documents - responses: - 200: - description: Success - content: - application/json: - schema: - $ref: "#/components/schemas/DocumentListResponse" - post: - tags: - - Documents - parameters: - - name: siteName - in: path - required: true - schema: - type: string - description: Create document - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/ContentAndDocumentName" - responses: - 200: - description: Success - content: - application/json: - schema: - $ref: "#/components/schemas/DocumentResponse" - /v1/sites/{siteName}/documents/{documentName}: - get: - tags: - - Documents - parameters: - - name: siteName - in: path - required: true - schema: - type: string - - name: documentName - in: path - required: true - schema: - type: string - description: Read document - responses: - 200: - description: Success - content: - application/json: - schema: - $ref: "#/components/schemas/DocumentResponse" - post: - tags: - - Documents - parameters: - - name: siteName - in: path - required: true - schema: - type: string - - name: documentName - in: path - required: true - schema: - type: string - description: Update document - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/ShaAndContent" - responses: - 200: - description: Success - content: - application/json: - schema: - $ref: "#/components/schemas/DocumentResponse" - delete: - tags: - - Documents - parameters: - - name: siteName - in: path - required: true - schema: - type: string - - name: documentName - in: path - required: true - schema: - type: string - - name: sha - in: body - required: true - schema: - type: string - description: Delete document - responses: - 200: - description: Success - /v1/sites/{siteName}/documents/{documentName}/rename/{newDocumentName}: - post: - tags: - - Documents - parameters: - - name: siteName - in: path - required: true - schema: - type: string - - name: documentName - in: path - required: true - schema: - type: string - - name: newDocumentName - in: path - required: true - schema: - type: string - description: Rename document - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/RenameDocument" - responses: - 200: - description: Success - content: - application/json: - schema: - $ref: "#/components/schemas/DocumentResponse" - - /v1/sites/{siteName}/images: - get: - tags: - - Images - parameters: - - name: siteName - in: path - required: true - schema: - type: string - description: List images - responses: - 200: - description: Success - content: - application/json: - schema: - $ref: "#/components/schemas/ImageListResponse" - post: - tags: - - Images - parameters: - - name: siteName - in: path - required: true - schema: - type: string - description: Create image - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/ContentAndImageName" - responses: - 200: - description: Success - content: - application/json: - schema: - $ref: "#/components/schemas/ImageResponse" - /v1/sites/{siteName}/images/{imageName}: - get: - tags: - - Images - parameters: - - name: siteName - in: path - required: true - schema: - type: string - - name: imageName - in: path - required: true - schema: - type: string - description: Read image - responses: - 200: - description: Success - content: - application/json: - schema: - $ref: "#/components/schemas/ImageResponse" - post: - tags: - - Images - parameters: - - name: siteName - in: path - required: true - schema: - type: string - - name: imageName - in: path - required: true - schema: - type: string - description: Update image - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/ShaAndContent" - responses: - 200: - description: Success - content: - application/json: - schema: - $ref: "#/components/schemas/ImageResponse" - delete: - tags: - - Images - parameters: - - name: siteName - in: path - required: true - schema: - type: string - - name: imageName - in: path - required: true - schema: - type: string - - name: sha - in: body - required: true - schema: - type: string - description: Delete image - responses: - 200: - description: Success - /v1/sites/{siteName}/images/{imageName}/rename/{newImageName}: - post: - tags: - - Images - parameters: - - name: siteName - in: path - required: true - schema: - type: string - - name: imageName - in: path - required: true - schema: - type: string - - name: newImageName - in: path - required: true - schema: - type: string - description: Rename image - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/RenameImage" - responses: - 200: - description: Success - content: - application/json: - schema: - $ref: "#/components/schemas/ImageResponse" - /v1/sites: - get: - tags: - - Sites - description: List sites - responses: - 200: - description: Success - content: - application/json: - schema: - $ref: "#/components/schemas/SiteListResponse" - /sites/{siteName}: - get: - tags: - - Sites - parameters: - - name: siteName - in: path - required: true - schema: - type: string - description: Checks if site exists and user has write access - responses: - 200: - description: Success - 404: - description: Not found - - - /v1/sites/{siteName}/resource-room: - get: - tags: - - Resource Room - parameters: - - name: siteName - in: path - required: true - schema: - type: string - description: Get resource room name - responses: - 200: - description: Success - content: - application/json: - schema: - $ref: "#/components/schemas/ResourceRoom" - post: - tags: - - Resource Room - parameters: - - name: siteName - in: path - required: true - schema: - type: string - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/ResourceRoom" - description: Create resource room - responses: - 200: - description: Success - content: - application/json: - schema: - $ref: "#/components/schemas/ResourceRoom" - delete: - tags: - - Resource Room - parameters: - - name: siteName - in: path - required: true - schema: - type: string - description: Delete resource room - responses: - 200: - description: Success - /v1/sites/{siteName}/resource-room/{resourceRoom}: - post: - tags: - - Resource Room - parameters: - - name: siteName - in: path - required: true - schema: - type: string - - name: resourceRoom - in: path - required: true - schema: - type: string - description: Rename resource room - responses: - 200: - description: Success - content: - application/json: - schema: - $ref: "#/components/schemas/ResourceRoom" - /v1/sites/{siteName}/resources: - get: - tags: - - Resources - parameters: - - name: siteName - in: path - required: true - schema: - type: string - description: Get resource list - responses: - 200: - description: Success - content: - application/json: - schema: - $ref: "#/components/schemas/ResourceListResponse" - post: - tags: - - Resources - parameters: - - name: siteName - in: path - required: true - schema: - type: string - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/ResourceResponse" - description: Create resource - responses: - 200: - description: Success - content: - application/json: - schema: - $ref: "#/components/schemas/ResourceResponse" - /v1/sites/{siteName}/resources/{resourceName}: - get: - tags: - - Resource Pages - parameters: - - name: siteName - in: path - required: true - schema: - type: string - - name: resourceName - in: path - required: true - schema: - type: string - description: Get page list in resource - responses: - 200: - description: Success - content: - application/json: - schema: - $ref: "#/components/schemas/ResourcePageListResponse" - delete: - tags: - - Resources - parameters: - - name: siteName - in: path - required: true - schema: - type: string - - name: resourceName - in: path - required: true - schema: - type: string - description: Delete resource - responses: - 200: - description: Success - /v1/sites/{siteName}/resources/{resourceName}/rename/{newResourceName}: - post: - tags: - - Resources - parameters: - - name: siteName - in: path - required: true - schema: - type: string - - name: resourceName - in: path - required: true - schema: - type: string - - name: newResourceName - in: path - required: true - schema: - type: string - description: Create resource - responses: - 200: - description: Success - content: - application/json: - schema: - $ref: "#/components/schemas/RenameResource" - /v1/sites/{siteName}/resources/{resourceName}/pages: - post: - tags: - - Resource Pages - parameters: - - name: siteName - in: path - required: true - schema: - type: string - - name: resourceName - in: path - required: true - schema: - type: string - description: Create resource page - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/ContentAndPageName" - responses: - 200: - description: Success - content: - application/json: - schema: - $ref: "#/components/schemas/ResourcePageResponse" - /v1/sites/{siteName}/resources/{resourceName}/pages/{pageName}: - get: - tags: - - Resource Pages - parameters: - - name: siteName - in: path - required: true - schema: - type: string - - name: resourceName - in: path - required: true - schema: - type: string - - name: pageName - in: path - required: true - schema: - type: string - description: Read resource page - responses: - 200: - description: Success - content: - application/json: - schema: - $ref: "#/components/schemas/ResourcePageListResponse" - post: - tags: - - Resource Pages - parameters: - - name: siteName - in: path - required: true - schema: - type: string - - name: resourceName - in: path - required: true - schema: - type: string - - name: pageName - in: path - required: true - schema: - type: string - description: Update resource page - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/ShaAndContent" - responses: - 200: - description: Success - content: - application/json: - schema: - $ref: "#/components/schemas/ResourcePageResponse" - delete: - tags: - - Resource Pages - parameters: - - name: siteName - in: path - required: true - schema: - type: string - - name: resourceName - in: path - required: true - schema: - type: string - - name: pageName - in: path - required: true - schema: - type: string - - name: sha - in: body - required: true - schema: - type: string - description: Delete resource page - responses: - 200: - description: Success - /v1/sites/{siteName}/resources/{resourceName}/pages/{pageName}/rename/{newPageName}: - post: - tags: - - Resource Pages - parameters: - - name: siteName - in: path - required: true - schema: - type: string - - name: resourceName - in: path - required: true - schema: - type: string - - name: pageName - in: path - required: true - schema: - type: string - - name: newPageName - in: path - required: true - schema: - type: string - description: Rename resource page - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/RenamePage" - responses: - 200: - description: Success - content: - application/json: - schema: - $ref: "#/components/schemas/ResourcePageResponse" - /v1/sites/{siteName}/menus: - get: - tags: - - Menus - parameters: - - name: siteName - in: path - required: true - schema: - type: string - description: Get menu list - responses: - 200: - description: Success - content: - application/json: - schema: - $ref: "#/components/schemas/MenuListResponse" - /v1/sites/{siteName}/menus/{menuName}: - get: - tags: - - Menus - parameters: - - name: siteName - in: path - required: true - schema: - type: string - - name: menuName - in: path - required: true - schema: - type: string - description: Read menu page - responses: - 200: - description: Success - content: - application/json: - schema: - $ref: "#/components/schemas/MenuResponse" - post: - tags: - - Menus - parameters: - - name: siteName - in: path - required: true - schema: - type: string - - name: menuName - in: path - required: true - schema: - type: string - description: Update menu page - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/ShaAndContent" - responses: - 200: - description: Success - content: - application/json: - schema: - $ref: "#/components/schemas/MenuResponse" - -security: - - ApiKeyAuthentication: [] \ No newline at end of file diff --git a/src/classes/Collection.js b/src/classes/Collection.js deleted file mode 100644 index 028219afe..000000000 --- a/src/classes/Collection.js +++ /dev/null @@ -1,236 +0,0 @@ -const { - sanitizedYamlParse, - sanitizedYamlStringify, -} = require("@utils/yaml-utils") - -const { Base64 } = require("js-base64") - -require("bluebird") -require("lodash") -const { - ConflictError, - protectedFolderConflictErrorMsg, -} = require("@errors/ConflictError") - -const { CollectionConfig } = require("@classes/Config.js") -const { Directory, RootType } = require("@classes/Directory.js") -const { File, DataType } = require("@classes/File.js") - -const { - getTree, - sendTree, - deslugifyCollectionName, -} = require("@utils/utils.js") - -const NAV_FILE_NAME = "navigation.yml" -const ISOMER_TEMPLATE_DIRS = ["_data", "_includes", "_site", "_layouts"] -const ISOMER_TEMPLATE_PROTECTED_DIRS = [ - "data", - "includes", - "site", - "layouts", - "files", - "images", - "misc", - "pages", -] - -class Collection { - constructor(accessToken, siteName) { - this.accessToken = accessToken - this.siteName = siteName - } - - async list() { - const IsomerDirectory = new Directory(this.accessToken, this.siteName) - const folderType = new RootType() - IsomerDirectory.setDirType(folderType) - const repoRootContent = await IsomerDirectory.list() - - return repoRootContent.reduce((acc, curr) => { - if ( - curr.type === "dir" && - !ISOMER_TEMPLATE_DIRS.includes(curr.name) && - curr.name.slice(0, 1) === "_" - ) - acc.push(curr.path.slice(1)) - return acc - }, []) - } - - async create(collectionName, orderArray) { - const collectionConfig = new CollectionConfig( - this.accessToken, - this.siteName, - collectionName - ) - const contentObject = { - collections: { - [collectionName]: { - output: true, - order: orderArray || [], - }, - }, - } - if (ISOMER_TEMPLATE_PROTECTED_DIRS.includes(collectionName)) - throw new ConflictError(protectedFolderConflictErrorMsg(collectionName)) - const newContent = Base64.encode(sanitizedYamlStringify(contentObject)) - await collectionConfig.create(newContent) - - const nav = new File(this.accessToken, this.siteName) - const dataType = new DataType() - nav.setFileType(dataType) - const { content: navContent, sha: navSha } = await nav.read(NAV_FILE_NAME) - const navContentObject = sanitizedYamlParse(Base64.decode(navContent)) - - navContentObject.links.push({ - title: deslugifyCollectionName(collectionName), - collection: collectionName, - }) - const newNavContent = Base64.encode( - sanitizedYamlStringify(navContentObject) - ) - - await nav.update(NAV_FILE_NAME, newNavContent, navSha) - } - - async delete(collectionName, currentCommitSha, treeSha) { - const commitMessage = `Delete collection ${collectionName}` - const gitTree = await getTree( - this.siteName, - this.accessToken, - treeSha, - true - ) - const newGitTree = gitTree - .filter( - (item) => - item.type !== "tree" && item.path.startsWith(`_${collectionName}/`) - ) - .map((item) => ({ - ...item, - sha: null, - })) - await sendTree( - newGitTree, - treeSha, - currentCommitSha, - this.siteName, - this.accessToken, - commitMessage - ) - - // Delete collection in nav if it exists - const nav = new File(this.accessToken, this.siteName) - const dataType = new DataType() - nav.setFileType(dataType) - const { content: navContent, sha: navSha } = await nav.read(NAV_FILE_NAME) - const navContentObject = sanitizedYamlParse(Base64.decode(navContent)) - - const newNavLinks = navContentObject.links.filter( - (link) => link.collection !== collectionName - ) - const newNavContentObject = { - ...navContentObject, - links: newNavLinks, - } - const newNavContent = Base64.encode( - sanitizedYamlStringify(newNavContentObject) - ) - await nav.update(NAV_FILE_NAME, newNavContent, navSha) - } - - async rename( - oldCollectionName, - newCollectionName, - currentCommitSha, - treeSha - ) { - const commitMessage = `Rename collection from ${oldCollectionName} to ${newCollectionName}` - - const gitTree = await getTree( - this.siteName, - this.accessToken, - treeSha, - true - ) - const oldCollectionDirectoryName = `_${oldCollectionName}` - const newCollectionDirectoryName = `_${newCollectionName}` - const newGitTree = [] - gitTree.forEach((item) => { - if (item.path === oldCollectionDirectoryName && item.type === "tree") { - // Rename old subdirectory to new name - newGitTree.push({ - ...item, - path: newCollectionDirectoryName, - }) - } else if ( - item.path.startsWith(`${oldCollectionDirectoryName}/`) && - item.type !== "tree" - ) { - // Delete old subdirectory items - newGitTree.push({ - ...item, - sha: null, - }) - } - }) - await sendTree( - newGitTree, - treeSha, - currentCommitSha, - this.siteName, - this.accessToken, - commitMessage - ) - - // Update collection.yml in newCollection with newCollection name - const collectionConfig = new CollectionConfig( - this.accessToken, - this.siteName, - newCollectionName - ) - const { - content: configContentObject, - sha: configSha, - } = await collectionConfig.read() - const newConfigContentObject = { - collections: { - [newCollectionName]: configContentObject.collections[oldCollectionName], - }, - } - const newConfigContent = Base64.encode( - sanitizedYamlStringify(newConfigContentObject) - ) - await collectionConfig.update(newConfigContent, configSha) - - // Rename collection in nav if it exists - const nav = new File(this.accessToken, this.siteName) - const dataType = new DataType() - nav.setFileType(dataType) - const { content: navContent, sha: navSha } = await nav.read(NAV_FILE_NAME) - const navContentObject = sanitizedYamlParse(Base64.decode(navContent)) - - const newNavLinks = navContentObject.links.map((link) => { - if (link.collection === oldCollectionName) { - return { - title: deslugifyCollectionName(newCollectionName), - collection: newCollectionName, - } - } - return link - }) - - const newNavContentObject = { - ...navContentObject, - links: newNavLinks, - } - - const newNavContent = Base64.encode( - sanitizedYamlStringify(newNavContentObject) - ) - await nav.update(NAV_FILE_NAME, newNavContent, navSha) - } -} - -module.exports = { Collection } diff --git a/src/classes/Config.js b/src/classes/Config.js deleted file mode 100644 index 242df6b18..000000000 --- a/src/classes/Config.js +++ /dev/null @@ -1,211 +0,0 @@ -import { config } from "@config/config" - -const { Base64 } = require("js-base64") -const _ = require("lodash") - -const { - ConflictError, - inputNameConflictErrorMsg, -} = require("@errors/ConflictError") -const { NotFoundError } = require("@errors/NotFoundError") - -const { validateStatus } = require("@utils/axios-utils") -const { - sanitizedYamlParse, - sanitizedYamlStringify, -} = require("@utils/yaml-utils") - -const { - genericGitHubAxiosInstance: axios, -} = require("@root/services/api/AxiosInstance") - -// Import error - -const GITHUB_ORG_NAME = config.get("github.orgName") -const BRANCH_REF = config.get("github.branchRef") - -class Config { - constructor(accessToken, siteName) { - this.accessToken = accessToken - this.siteName = siteName - this.endpoint = `https://api.github.com/repos/${GITHUB_ORG_NAME}/${this.siteName}/contents/_config.yml` - } - - async read() { - const params = { - ref: BRANCH_REF, - } - - const resp = await axios.get(this.endpoint, { - validateStatus, - params, - headers: { - Authorization: `token ${this.accessToken}`, - "Content-Type": "application/json", - }, - }) - - if (resp.status === 404) - throw new NotFoundError("Config page does not exist") - - const { content, sha } = resp.data - - return { content, sha } - } - - async update(newContent, sha) { - const params = { - message: "Edit config", - content: newContent, - branch: BRANCH_REF, - sha, - } - - await axios.put(this.endpoint, params, { - headers: { - Authorization: `token ${this.accessToken}`, - "Content-Type": "application/json", - }, - }) - } -} - -class CollectionConfig extends Config { - constructor(accessToken, siteName, collectionName) { - super(accessToken, siteName) - this.collectionName = collectionName - this.endpoint = `https://api.github.com/repos/${GITHUB_ORG_NAME}/${siteName}/contents/_${collectionName}/collection.yml` - } - - async create(content) { - try { - const params = { - message: `Create file: _${this.collectionName}/collection.yml`, - content, - branch: BRANCH_REF, - } - - const resp = await axios.put(this.endpoint, params, { - headers: { - Authorization: `token ${this.accessToken}`, - "Content-Type": "application/json", - }, - }) - - return { sha: resp.data.content.sha } - } catch (err) { - const { status } = err.response - if (status === 422 || status === 409) - throw new ConflictError( - inputNameConflictErrorMsg(`${this.collectionName}/collection.yml`) - ) - throw err.response - } - } - - async delete(sha) { - try { - const params = { - message: `Delete file: _${this.collectionName}/collection.yml`, - branch: BRANCH_REF, - sha, - } - - await axios.delete(this.endpoint, { - params, - headers: { - Authorization: `token ${this.accessToken}`, - "Content-Type": "application/json", - }, - }) - } catch (err) { - const { status } = err.response - if (status === 404) throw new NotFoundError("File does not exist") - throw err - } - } - - async read() { - const { content, sha } = await super.read() - const contentObject = sanitizedYamlParse(Base64.decode(content)) - return { content: contentObject, sha } - } - - async addItemToOrder(item, index) { - const { collectionName } = this - const { content, sha } = await this.read() - - let newIndex = index - if (index === undefined) { - if (item.split("/").length === 2) { - // if file in subfolder, get index of last file in subfolder - newIndex = - _.findLastIndex( - content.collections[collectionName].order, - (f) => f.split("/")[0] === item.split("/")[0] - ) + 1 - } else { - // get index of last file in collection - newIndex = content.collections[collectionName].order.length - } - } - content.collections[collectionName].order.splice(newIndex, 0, item) - const newContent = Base64.encode(sanitizedYamlStringify(content)) - - await this.update(newContent, sha) - } - - async deleteItemFromOrder(item) { - const { collectionName } = this - const { content, sha } = await this.read() - const index = content.collections[collectionName].order.indexOf(item) - content.collections[collectionName].order.splice(index, 1) - const newContent = Base64.encode(sanitizedYamlStringify(content)) - - await this.update(newContent, sha) - return { index, item } - } - - async updateItemInOrder(oldItem, newItem) { - const { collectionName } = this - const { content, sha } = await this.read() - const index = content.collections[collectionName].order.indexOf(oldItem) - content.collections[collectionName].order.splice(index, 1) - content.collections[collectionName].order.splice(index, 0, newItem) - const newContent = Base64.encode(sanitizedYamlStringify(content)) - - await this.update(newContent, sha) - } - - async deleteSubfolderFromOrder(subfolder) { - const { collectionName } = this - const { content, sha } = await this.read() - const filteredOrder = content.collections[collectionName].order.filter( - (item) => !item.includes(`${subfolder}/`) - ) - const newContentObject = _.cloneDeep(content) - newContentObject.collections[collectionName].order = filteredOrder - const newContent = Base64.encode(sanitizedYamlStringify(newContentObject)) - - await this.update(newContent, sha) - } - - async renameSubfolderInOrder(subfolder, newSubfolderName) { - const { collectionName } = this - const { content, sha } = await this.read() - const renamedOrder = content.collections[collectionName].order.map( - (item) => { - if (item.includes(`${subfolder}/`)) - return `${newSubfolderName}/${item.split("/")[1]}` - return item - } - ) - const newContentObject = _.cloneDeep(content) - newContentObject.collections[collectionName].order = renamedOrder - const newContent = Base64.encode(sanitizedYamlStringify(newContentObject)) - - await this.update(newContent, sha) - } -} - -module.exports = { Config, CollectionConfig } diff --git a/src/classes/Directory.js b/src/classes/Directory.js deleted file mode 100644 index 862a7ad93..000000000 --- a/src/classes/Directory.js +++ /dev/null @@ -1,136 +0,0 @@ -import { config } from "@config/config" - -const _ = require("lodash") - -const { BadRequestError } = require("@errors/BadRequestError") -const { NotFoundError } = require("@errors/NotFoundError") - -const { validateStatus } = require("@utils/axios-utils") - -const { - genericGitHubAxiosInstance: axios, -} = require("@root/services/api/AxiosInstance") - -const GITHUB_ORG_NAME = config.get("github.orgName") -const BRANCH_REF = config.get("github.branchRef") - -class RootType { - constructor() { - this.folderName = "" - } - - getFolderName() { - return this.folderName - } -} - -class FolderType { - constructor(folderPath) { - this.folderName = folderPath - } - - getFolderName() { - return this.folderName - } -} - -class ResourceRoomType { - constructor(resourceRoomName) { - this.folderName = `${resourceRoomName}` - } - - getFolderName() { - return this.folderName - } -} - -class Directory { - constructor(accessToken, siteName) { - this.accessToken = accessToken - this.siteName = siteName - this.baseEndpoint = null - this.dirType = null - } - - setDirType(dirType) { - this.dirType = dirType - const folderPath = dirType.getFolderName() - this.baseEndpoint = `https://api.github.com/repos/${GITHUB_ORG_NAME}/${this.siteName}/contents/${folderPath}` - } - - async list() { - const endpoint = `${this.baseEndpoint}` - - const params = { - ref: BRANCH_REF, - } - - const resp = await axios.get(endpoint, { - validateStatus, - params, - headers: { - Authorization: `token ${this.accessToken}`, - "Content-Type": "application/json", - }, - }) - - if (resp.status !== 200) { - if (this.dirType instanceof FolderType) { - if (resp.status === 404) - throw new NotFoundError( - `Path ${this.dirType.getFolderName()} was not found!` - ) - throw new BadRequestError( - `Path ${this.dirType.getFolderName()} was invalid!` - ) - } - return [] - } - - if (this.dirType instanceof FolderType) { - // Validation - if (!Array.isArray(resp.data)) { - throw new BadRequestError( - `The provided path, ${endpoint}, is not a directory` - ) - } - - const filesOrDirs = resp.data.map((fileOrDir) => { - const { name, path, sha, size, content, type } = fileOrDir - return { - name, - path, - sha, - size, - content, - type, - } - }) - - return _.compact(filesOrDirs) - } - - if (this.dirType instanceof ResourceRoomType) { - const directories = resp.data - .filter((object) => object.type === "dir") - .map((object) => { - const pathNameSplit = object.path.split("/") - const dirName = pathNameSplit[pathNameSplit.length - 1] - return { - path: encodeURIComponent(object.path), - dirName, - } - }) - return directories - } - - return resp.data - } -} - -module.exports = { - Directory, - RootType, - FolderType, - ResourceRoomType, -} diff --git a/src/classes/File.js b/src/classes/File.js deleted file mode 100644 index 76c845d3d..000000000 --- a/src/classes/File.js +++ /dev/null @@ -1,281 +0,0 @@ -import { config } from "@config/config" - -const { BaseIsomerError } = require("@errors/BaseError") -const { - ConflictError, - inputNameConflictErrorMsg, -} = require("@errors/ConflictError") -const { NotFoundError } = require("@errors/NotFoundError") - -const { validateStatus } = require("@utils/axios-utils") - -const { - genericGitHubAxiosInstance: axios, -} = require("@root/services/api/AxiosInstance") - -// Import error - -const GITHUB_ORG_NAME = config.get("github.orgName") -const BRANCH_REF = config.get("github.branchRef") - -class File { - constructor(accessToken, siteName) { - this.accessToken = accessToken - this.siteName = siteName - this.baseEndpoint = null - this.folderPath = null - } - - setFileType(fileType) { - this.folderPath = fileType.getFolderName() - this.baseEndpoint = `https://api.github.com/repos/${GITHUB_ORG_NAME}/${ - this.siteName - }/contents${this.folderPath ? "/" : ""}${this.folderPath}` - } - - async list() { - const endpoint = `${this.baseEndpoint}` - - const params = { - ref: BRANCH_REF, - } - - const resp = await axios.get(endpoint, { - validateStatus, - params, - headers: { - Authorization: `token ${this.accessToken}`, - "Content-Type": "application/json", - }, - }) - - if (resp.status !== 200) { - if (resp.status === 404) - throw new NotFoundError("Directory does not exist") - throw new BaseIsomerError({ message: resp }) - } - - const files = resp.data - .filter((object) => object.type === "file") - .map((object) => { - const pathNameSplit = object.path.split("/") - const fileName = pathNameSplit[pathNameSplit.length - 1] - return { - path: encodeURIComponent(object.path), - fileName, - sha: object.sha, - } - }) - - return files - } - - async create(fileName, content) { - try { - const endpoint = `${this.baseEndpoint}/${fileName}` - - const params = { - message: `Create file: ${fileName}`, - content, - branch: BRANCH_REF, - } - - const resp = await axios.put(endpoint, params, { - headers: { - Authorization: `token ${this.accessToken}`, - "Content-Type": "application/json", - }, - }) - - return { sha: resp.data.content.sha } - } catch (err) { - const { status } = err.response - if (status === 422 || status === 409) - throw new ConflictError(inputNameConflictErrorMsg(fileName)) - throw err.response - } - } - - async read(fileName) { - const endpoint = `${this.baseEndpoint}/${fileName}` - - const params = { - ref: BRANCH_REF, - } - - const resp = await axios.get(endpoint, { - validateStatus, - params, - headers: { - Authorization: `token ${this.accessToken}`, - "Content-Type": "application/json", - }, - }) - if (resp.status === 404) throw new NotFoundError("File does not exist") - - const { content, sha } = resp.data - - return { content, sha } - } - - async update(fileName, content, sha) { - try { - const endpoint = `${this.baseEndpoint}/${fileName}` - - const params = { - message: `Update file: ${fileName}`, - content, - branch: BRANCH_REF, - sha, - } - - const resp = await axios.put(endpoint, params, { - headers: { - Authorization: `token ${this.accessToken}`, - "Content-Type": "application/json", - }, - }) - - return { newSha: resp.data.commit.sha } - } catch (err) { - const { status } = err.response - if (status === 404) throw new NotFoundError("File does not exist") - if (status === 409) - throw new ConflictError( - "File has been changed recently, please try again" - ) - throw err - } - } - - async delete(fileName, sha) { - try { - const endpoint = `${this.baseEndpoint}/${fileName}` - - const params = { - message: `Delete file: ${fileName}`, - branch: BRANCH_REF, - sha, - } - - await axios.delete(endpoint, { - params, - headers: { - Authorization: `token ${this.accessToken}`, - "Content-Type": "application/json", - }, - }) - } catch (err) { - const { status } = err.response - if (status === 404) throw new NotFoundError("File does not exist") - if (status === 409) - throw new ConflictError( - "File has been changed recently, please try again" - ) - throw err - } - } -} - -class PageType { - constructor() { - this.folderName = "pages" - } - - getFolderName() { - return this.folderName - } -} - -class CollectionPageType { - constructor(collectionName) { - this.folderName = `_${collectionName}` - } - - getFolderName() { - return this.folderName - } -} - -class ResourcePageType { - constructor(resourceRoomName, resourceName) { - this.folderName = `${resourceRoomName}/${resourceName}/_posts` - } - - getFolderName() { - return this.folderName - } -} - -class ResourceCategoryType { - constructor(resourceRoomName, resourceName) { - this.folderName = `${resourceRoomName}/${resourceName}` - } - - getFolderName() { - return this.folderName - } -} - -class ResourceType { - constructor(resourceRoomName) { - this.folderName = `${resourceRoomName}` - } - - getFolderName() { - return this.folderName - } -} - -class ImageType { - constructor() { - this.folderName = "images" - } - - getFolderName() { - return this.folderName - } -} - -class DocumentType { - constructor() { - this.folderName = "files" - } - - getFolderName() { - return this.folderName - } -} - -class DataType { - constructor() { - this.folderName = "_data" - } - - getFolderName() { - return this.folderName - } -} - -class HomepageType { - constructor() { - this.folderName = "" - } - - getFolderName() { - return this.folderName - } -} - -module.exports = { - File, - PageType, - CollectionPageType, - ResourcePageType, - ResourceCategoryType, - ResourceType, - ImageType, - DocumentType, - DataType, - HomepageType, -} diff --git a/src/classes/MediaFile.js b/src/classes/MediaFile.js deleted file mode 100644 index 88fe3413e..000000000 --- a/src/classes/MediaFile.js +++ /dev/null @@ -1,218 +0,0 @@ -const { BaseIsomerError } = require("@errors/BaseError") -const { - ConflictError, - inputNameConflictErrorMsg, -} = require("@errors/ConflictError") -const { MediaTypeError } = require("@errors/MediaTypeError") -const { NotFoundError } = require("@errors/NotFoundError") - -const { validateStatus } = require("@utils/axios-utils") -const { validateAndSanitizeFileUpload } = require("@utils/file-upload-utils") - -const { - genericGitHubAxiosInstance: axios, -} = require("@root/services/api/AxiosInstance") - -// Import error - -// Constants -const GITHUB_ORG_NAME = "isomerpages" - -class ImageType { - constructor(directory) { - this.folderName = directory || "images" - } - - getFolderName() { - return this.folderName - } -} - -class DocumentType { - constructor(directory) { - this.folderName = directory || "files" - } - - getFolderName() { - return this.folderName - } -} - -class MediaFile { - constructor(accessToken, siteName) { - this.accessToken = accessToken - this.siteName = siteName - this.baseEndpoint = null - this.fileType = null - } - - setFileTypeToImage(directory) { - this.fileType = new ImageType(directory) - this.baseEndpoint = `https://api.github.com/repos/${GITHUB_ORG_NAME}/${ - this.siteName - }/contents/${this.fileType.getFolderName()}` - // Endpoint to retrieve files greater than 1MB - this.baseBlobEndpoint = `https://api.github.com/repos/${GITHUB_ORG_NAME}/${this.siteName}/git/blobs` - } - - setFileTypeToDocument(directory) { - this.fileType = new DocumentType(directory) - this.baseEndpoint = `https://api.github.com/repos/${GITHUB_ORG_NAME}/${ - this.siteName - }/contents/${this.fileType.getFolderName()}` - // Endpoint to retrieve files greater than 1MB - this.baseBlobEndpoint = `https://api.github.com/repos/${GITHUB_ORG_NAME}/${this.siteName}/git/blobs` - } - - async list() { - const endpoint = `${this.baseEndpoint}` - - const resp = await axios.get(endpoint, { - validateStatus, - headers: { - Authorization: `token ${this.accessToken}`, - "Content-Type": "application/json", - }, - }) - - if (resp.status !== 200) { - if (resp.status === 404) - throw new NotFoundError("Media Directory does not exist") - throw new BaseIsomerError({ message: resp }) - } - - return resp.data - .filter((object) => object.type === "file") - .map((object) => { - const pathNameSplit = object.path.split("/") - const fileName = pathNameSplit[pathNameSplit.length - 1] - return { - path: encodeURIComponent(object.path), - fileName, - sha: object.sha, - } - }) - } - - async create(fileName, content) { - const sanitizedContent = await validateAndSanitizeFileUpload(content) - if (!sanitizedContent) { - throw new MediaTypeError(`File extension is not within the approved list`) - } - - const endpoint = `${this.baseEndpoint}/${fileName}` - - const params = { - message: `Create file: ${fileName}`, - content: sanitizedContent, - branch: "staging", - } - - try { - const resp = await axios.put(endpoint, params, { - headers: { - Authorization: `token ${this.accessToken}`, - "Content-Type": "application/json", - }, - }) - - return { sha: resp.data.content.sha } - } catch (err) { - const { status } = err.response - if (status === 422 || status === 409) - throw new ConflictError(inputNameConflictErrorMsg(fileName)) - throw err.response - } - } - - async read(fileName) { - /** - * Images that are bigger than 1 MB needs to be retrieved - * via Github Blob API. The content can only be retrieved through - * the `sha` of the file. - * The code below takes the `fileName`, - * lists all the files in the image directory - * and filters it down to get the sha of the file - */ - const files = await this.list() - const targetfile = files.find((file) => file.fileName === fileName) - if (!targetfile) throw new NotFoundError("Media file does not exist") - const fileSha = targetfile.sha - - const blobEndpoint = `${this.baseBlobEndpoint}/${fileSha}` - - const resp = await axios.get(blobEndpoint, { - validateStatus, - headers: { - Authorization: `token ${this.accessToken}`, - }, - }) - - if (resp.status === 404) - throw new NotFoundError("Media file does not exist") - - const { content, sha } = resp.data - - return { content, sha } - } - - async update(fileName, content, sha) { - const endpoint = `${this.baseEndpoint}/${fileName}` - - const params = { - message: `Update file: ${fileName}`, - content, - branch: "staging", - sha, - } - - try { - const resp = await axios.put(endpoint, params, { - headers: { - Authorization: `token ${this.accessToken}`, - "Content-Type": "application/json", - }, - }) - - return { newSha: resp.data.commit.sha } - } catch (err) { - const { status } = err.response - if (status === 404) throw new NotFoundError("File does not exist") - if (status === 409) - throw new ConflictError( - "File has been changed recently, please try again" - ) - throw err - } - } - - async delete(fileName, sha) { - const endpoint = `${this.baseEndpoint}/${fileName}` - - const params = { - message: `Delete file: ${fileName}`, - branch: "staging", - sha, - } - - try { - await axios.delete(endpoint, { - data: params, - headers: { - Authorization: `token ${this.accessToken}`, - "Content-Type": "application/json", - }, - }) - } catch (err) { - const { status } = err.response - if (status === 404) throw new NotFoundError("File does not exist") - if (status === 409) - throw new ConflictError( - "File has been changed recently, please try again" - ) - throw err - } - } -} - -module.exports = { MediaFile } diff --git a/src/classes/MediaSubfolder.js b/src/classes/MediaSubfolder.js deleted file mode 100644 index 92327d022..000000000 --- a/src/classes/MediaSubfolder.js +++ /dev/null @@ -1,98 +0,0 @@ -const { File, ImageType, DocumentType } = require("@classes/File.js") - -const { getTree, sendTree } = require("@utils/utils.js") - -class MediaSubfolder { - constructor(accessToken, siteName, fileType) { - this.accessToken = accessToken - this.siteName = siteName - switch (fileType) { - case "images": - this.fileType = new ImageType() - this.mediaFolderName = fileType - break - case "documents": - this.fileType = new DocumentType() - this.mediaFolderName = "files" - break - default: - throw new Error("Invalid media type!") - } - } - - async create(subfolderPath) { - const IsomerFile = new File(this.accessToken, this.siteName) - IsomerFile.setFileType(this.fileType) - await IsomerFile.create(`${subfolderPath}/.keep`, "") - } - - async delete(subfolderPath, currentCommitSha, treeSha) { - const commitMessage = `Delete ${this.mediaFolderName} subfolder ${subfolderPath}` - const gitTree = await getTree( - this.siteName, - this.accessToken, - treeSha, - true - ) - const directoryName = `${this.mediaFolderName}/${subfolderPath}` - const newGitTree = gitTree - .filter( - (item) => - item.type !== "tree" && item.path.startsWith(`${directoryName}/`) - ) - .map((item) => ({ - ...item, - sha: null, - })) - await sendTree( - newGitTree, - treeSha, - currentCommitSha, - this.siteName, - this.accessToken, - commitMessage - ) - } - - async rename(oldSubfolderPath, newSubfolderPath, currentCommitSha, treeSha) { - const commitMessage = `Rename ${this.mediaFolderName} subfolder from ${oldSubfolderPath} to ${newSubfolderPath}` - - const gitTree = await getTree( - this.siteName, - this.accessToken, - treeSha, - true - ) - const oldDirectoryName = `${this.mediaFolderName}/${oldSubfolderPath}` - const newDirectoryName = `${this.mediaFolderName}/${newSubfolderPath}` - const newGitTree = [] - gitTree.forEach((item) => { - if (item.path === oldDirectoryName && item.type === "tree") { - // Rename old subdirectory to new name - newGitTree.push({ - ...item, - path: newDirectoryName, - }) - } else if ( - item.path.startsWith(`${oldDirectoryName}/`) && - item.type !== "tree" - ) { - // Delete old subdirectory items - newGitTree.push({ - ...item, - sha: null, - }) - } - }) - await sendTree( - newGitTree, - treeSha, - currentCommitSha, - this.siteName, - this.accessToken, - commitMessage - ) - } -} - -module.exports = { MediaSubfolder } diff --git a/src/classes/NetlifyToml.js b/src/classes/NetlifyToml.js deleted file mode 100644 index 7797da663..000000000 --- a/src/classes/NetlifyToml.js +++ /dev/null @@ -1,41 +0,0 @@ -import { config } from "@config/config" - -const { NotFoundError } = require("@errors/NotFoundError") - -const { validateStatus } = require("@utils/axios-utils") - -const { - genericGitHubAxiosInstance: axios, -} = require("@root/services/api/AxiosInstance") - -// Import error - -const GITHUB_BUILD_ORG_NAME = config.get("github.buildOrgName") -const GITHUB_BUILD_REPO_NAME = config.get("github.buildRepo") - -class NetlifyToml { - constructor(accessToken, siteName) { - this.accessToken = accessToken - this.siteName = siteName - } - - async read() { - const endpoint = `https://api.github.com/repos/${GITHUB_BUILD_ORG_NAME}/${GITHUB_BUILD_REPO_NAME}/contents/overrides/netlify.toml` - - const resp = await axios.get(endpoint, { - validateStatus, - headers: { - Authorization: `token ${this.accessToken}`, - "Content-Type": "application/json", - }, - }) - - if (resp.status === 404) - throw new NotFoundError("netlify.toml file does not exist") - - const { content, sha } = resp.data - return { content, sha } - } -} - -module.exports = { NetlifyToml } diff --git a/src/classes/Resource.js b/src/classes/Resource.js deleted file mode 100644 index 961d288f2..000000000 --- a/src/classes/Resource.js +++ /dev/null @@ -1,164 +0,0 @@ -const Bluebird = require("bluebird") -const { Base64 } = require("js-base64") -const _ = require("lodash") - -// Import classes -const { NotFoundError } = require("@errors/NotFoundError") - -const { Directory, ResourceRoomType } = require("@classes/Directory.js") -const { - File, - ResourceCategoryType, - ResourcePageType, -} = require("@classes/File.js") - -const { - getCommitAndTreeSha, - getTree, - sendTree, - deslugifyCollectionName, -} = require("@utils/utils.js") -const { - sanitizedYamlParse, - sanitizedYamlStringify, -} = require("@utils/yaml-utils") - -// Constants -const RESOURCE_INDEX_PATH = "index.html" - -class Resource { - constructor(accessToken, siteName) { - this.accessToken = accessToken - this.siteName = siteName - } - - async list(resourceRoomName) { - const IsomerDir = new Directory(this.accessToken, this.siteName) - const resourceRoomType = new ResourceRoomType(resourceRoomName) - IsomerDir.setDirType(resourceRoomType) - return IsomerDir.list() - } - - async create(resourceRoomName, resourceName) { - // Create an index file in the resource folder - const IsomerFile = new File(this.accessToken, this.siteName) - const resourceType = new ResourceCategoryType( - resourceRoomName, - resourceName - ) - IsomerFile.setFileType(resourceType) - const resourceObject = { - layout: "resources-alt", - title: deslugifyCollectionName(resourceName), - } - const resourceFrontMatter = sanitizedYamlStringify(resourceObject) - const resourceIndexContent = ["---\n", resourceFrontMatter, "---"].join("") - return IsomerFile.create( - `${RESOURCE_INDEX_PATH}`, - Base64.encode(resourceIndexContent) - ) - } - - async rename(resourceRoomName, resourceName, newResourceName) { - const commitMessage = `Rename resource category from ${resourceName} to ${newResourceName}` - const { currentCommitSha, treeSha } = await getCommitAndTreeSha( - this.siteName, - this.accessToken - ) - const gitTree = await getTree( - this.siteName, - this.accessToken, - treeSha, - true - ) - const newGitTree = [] - gitTree.forEach((item) => { - // We need to append resource room to the file path because the path is relative to the subtree - if ( - item.path === `${resourceRoomName}/${resourceName}` && - item.type === "tree" - ) { - // Rename old subdirectory to new name - newGitTree.push({ - ...item, - path: `${resourceRoomName}/${newResourceName}`, - }) - } else if ( - item.path.startsWith(`${resourceRoomName}/${resourceName}/`) && - item.type !== "tree" - ) { - // Delete old subdirectory items - newGitTree.push({ - ...item, - sha: null, - }) - } - }) - await sendTree( - newGitTree, - treeSha, - currentCommitSha, - this.siteName, - this.accessToken, - commitMessage - ) - - // We also need to update the title in the index.html file - const IsomerFile = new File(this.accessToken, this.siteName) - const resourceType = new ResourceCategoryType( - resourceRoomName, - newResourceName - ) - IsomerFile.setFileType(resourceType) - const { content, sha } = await IsomerFile.read(RESOURCE_INDEX_PATH) - const decodedContent = Base64.decode(content) - const resourceFrontMatterObj = sanitizedYamlParse( - decodedContent.split("---")[1] - ) - resourceFrontMatterObj.title = deslugifyCollectionName(newResourceName) - const resourceFrontMatter = sanitizedYamlStringify(resourceFrontMatterObj) - const resourceIndexContent = ["---\n", resourceFrontMatter, "---"].join("") - await IsomerFile.update( - RESOURCE_INDEX_PATH, - Base64.encode(resourceIndexContent), - sha - ) - } - - async delete(resourceRoomName, resourceName) { - // Delete index file in resource - const IsomerIndexFile = new File(this.accessToken, this.siteName) - const resourceType = new ResourceCategoryType( - resourceRoomName, - resourceName - ) - IsomerIndexFile.setFileType(resourceType) - const { sha } = await IsomerIndexFile.read(`${RESOURCE_INDEX_PATH}`) - await IsomerIndexFile.delete(`${RESOURCE_INDEX_PATH}`, sha) - - // Delete all resourcePages in resource - // 1. List all resourcePages in resource - const IsomerFile = new File(this.accessToken, this.siteName) - const resourcePageType = new ResourcePageType( - resourceRoomName, - resourceName - ) - IsomerFile.setFileType(resourcePageType) - let resourcePages = [] - try { - resourcePages = await IsomerFile.list() - } catch (error) { - if (!(error instanceof NotFoundError)) throw error - } - - if (_.isEmpty(resourcePages)) return - - // 2. Delete all resourcePages in resource - await Bluebird.each(resourcePages, async (resourcePage) => { - const { sha: pageSha } = await IsomerFile.read(resourcePage.fileName) - return IsomerFile.delete(resourcePage.fileName, pageSha) - }) - } -} - -module.exports = { Resource } diff --git a/src/classes/ResourceRoom.js b/src/classes/ResourceRoom.js deleted file mode 100644 index 29eb758f9..000000000 --- a/src/classes/ResourceRoom.js +++ /dev/null @@ -1,247 +0,0 @@ -const Bluebird = require("bluebird") -const _ = require("lodash") - -// Import Classes -const { Config } = require("@classes/Config.js") -const { File, ResourceType, DataType } = require("@classes/File.js") -const { Resource } = require("@classes/Resource.js") - -const { - getCommitAndTreeSha, - getTree, - sendTree, - deslugifyCollectionName, -} = require("@utils/utils.js") -const { - sanitizedYamlParse, - sanitizedYamlStringify, -} = require("@utils/yaml-utils") - -// Constants -const RESOURCE_ROOM_INDEX_PATH = "index.html" -const NAV_FILE_NAME = "navigation.yml" - -const { Base64 } = require("js-base64") - -class ResourceRoom { - constructor(accessToken, siteName) { - this.accessToken = accessToken - this.siteName = siteName - } - - async get() { - const config = new Config(this.accessToken, this.siteName) - const { content } = await config.read() - const contentObject = sanitizedYamlParse(Base64.decode(content)) - - return contentObject.resources_name - } - - async create(resourceRoom) { - const config = new Config(this.accessToken, this.siteName) - const { content, sha } = await config.read() - const contentObject = sanitizedYamlParse(Base64.decode(content)) - - contentObject.resources_name = resourceRoom - - const newContent = Base64.encode(sanitizedYamlStringify(contentObject)) - - // Create index file in resourceRoom - const IsomerIndexFile = new File(this.accessToken, this.siteName) - const resourceType = new ResourceType(resourceRoom) - IsomerIndexFile.setFileType(resourceType) - const resourceRoomObject = { - layout: "resources", - title: deslugifyCollectionName(resourceRoom), - } - const resourceRoomFrontMatter = sanitizedYamlStringify(resourceRoomObject) - const resourceRoomIndexContent = [ - "---\n", - resourceRoomFrontMatter, - "---", - ].join("") - await IsomerIndexFile.create( - RESOURCE_ROOM_INDEX_PATH, - Base64.encode(resourceRoomIndexContent) - ) - - await config.update(newContent, sha) - - const nav = new File(this.accessToken, this.siteName) - const dataType = new DataType() - nav.setFileType(dataType) - const { content: navContent, sha: navSha } = await nav.read(NAV_FILE_NAME) - const navContentObject = sanitizedYamlParse(Base64.decode(navContent)) - - navContentObject.links.push({ - title: deslugifyCollectionName(resourceRoom), - resource_room: true, - }) - const newNavContent = Base64.encode( - sanitizedYamlStringify(navContentObject) - ) - - await nav.update(NAV_FILE_NAME, newNavContent, navSha) - - return resourceRoom - } - - async rename(newResourceRoom) { - // Add resource room to config - const config = new Config(this.accessToken, this.siteName) - const { content: configContent, sha: configSha } = await config.read() - const contentObject = sanitizedYamlParse(Base64.decode(configContent)) - - // Obtain existing resourceRoomName - const resourceRoomName = contentObject.resources_name - contentObject.resources_name = newResourceRoom - const newContent = Base64.encode(sanitizedYamlStringify(contentObject)) - - const commitMessage = `Rename resource room from ${resourceRoomName} to ${newResourceRoom}` - - await config.update(newContent, configSha) - - // Rename resource room in nav if it exists - const nav = new File(this.accessToken, this.siteName) - const dataType = new DataType() - nav.setFileType(dataType) - const { content: navContent, sha: navSha } = await nav.read(NAV_FILE_NAME) - const navContentObject = sanitizedYamlParse(Base64.decode(navContent)) - - const newNavLinks = navContentObject.links.map((link) => { - if (link.resource_room === true) { - return { - title: deslugifyCollectionName(newResourceRoom), - resource_room: true, - } - } - return link - }) - const newNavContentObject = { - ...navContentObject, - links: newNavLinks, - } - const newNavContent = Base64.encode( - sanitizedYamlStringify(newNavContentObject) - ) - await nav.update(NAV_FILE_NAME, newNavContent, navSha) - - const { currentCommitSha, treeSha } = await getCommitAndTreeSha( - this.siteName, - this.accessToken - ) - const gitTree = await getTree( - this.siteName, - this.accessToken, - treeSha, - true - ) - const newGitTree = [] - gitTree.forEach((item) => { - if (item.path === resourceRoomName && item.type === "tree") { - // Rename old subdirectory to new name - newGitTree.push({ - ...item, - path: newResourceRoom, - }) - } else if ( - item.path.startsWith(`${resourceRoomName}/`) && - item.type !== "tree" - ) { - // Delete old subdirectory items - newGitTree.push({ - ...item, - sha: null, - }) - } - }) - await sendTree( - newGitTree, - treeSha, - currentCommitSha, - this.siteName, - this.accessToken, - commitMessage - ) - - // We also need to update the title in the index.html file - const IsomerFile = new File(this.accessToken, this.siteName) - const resourceType = new ResourceType(resourceRoomName) - IsomerFile.setFileType(resourceType) - const { - content: resourceFileContent, - sha: resourceFileSha, - } = await IsomerFile.read(RESOURCE_ROOM_INDEX_PATH) - const decodedContent = Base64.decode(resourceFileContent) - const resourceFrontMatterObj = sanitizedYamlParse( - decodedContent.split("---")[1] - ) - resourceFrontMatterObj.title = deslugifyCollectionName(newResourceRoom) - const resourceFrontMatter = sanitizedYamlStringify(resourceFrontMatterObj) - const resourceIndexContent = ["---\n", resourceFrontMatter, "---"].join("") - await IsomerFile.update( - RESOURCE_ROOM_INDEX_PATH, - Base64.encode(resourceIndexContent), - resourceFileSha - ) - - return newResourceRoom - } - - async delete() { - // Delete resource in config - const config = new Config(this.accessToken, this.siteName) - const { content, sha } = await config.read() - const contentObject = sanitizedYamlParse(Base64.decode(content)) - - // Obtain resourceRoomName - const resourceRoomName = contentObject.resources_name - - // Delete resourcses_name from Config - delete contentObject.resources_name - const newContent = Base64.encode(sanitizedYamlStringify(contentObject)) - - // Delete resource room in nav if it exists - const nav = new File(this.accessToken, this.siteName) - const dataType = new DataType() - nav.setFileType(dataType) - const { content: navContent, sha: navSha } = await nav.read(NAV_FILE_NAME) - const navContentObject = sanitizedYamlParse(Base64.decode(navContent)) - - // Assumption: only a single resource room exists - const newNavLinks = navContentObject.links.filter( - (link) => link.resource_room !== true - ) - const newNavContentObject = { - ...navContentObject, - links: newNavLinks, - } - const newNavContent = Base64.encode( - sanitizedYamlStringify(newNavContentObject) - ) - await nav.update(NAV_FILE_NAME, newNavContent, navSha) - - // Delete all resources and resourcePages - const IsomerResource = new Resource(this.accessToken, this.siteName) - const resources = await IsomerResource.list(resourceRoomName) - - if (!_.isEmpty(resources)) { - await Bluebird.map(resources, async (resource) => - IsomerResource.delete(resourceRoomName, resource.dirName) - ) - } - - // Delete index file in resourceRoom - const IsomerIndexFile = new File(this.accessToken, this.siteName) - const resourceType = new ResourceType(resourceRoomName) - IsomerIndexFile.setFileType(resourceType) - const { sha: deleteSha } = await IsomerIndexFile.read( - RESOURCE_ROOM_INDEX_PATH - ) - await IsomerIndexFile.delete(RESOURCE_ROOM_INDEX_PATH, deleteSha) - - await config.update(newContent, sha) - } -} - -module.exports = { ResourceRoom } diff --git a/src/classes/Settings.js b/src/classes/Settings.js deleted file mode 100644 index 0dda106ba..000000000 --- a/src/classes/Settings.js +++ /dev/null @@ -1,260 +0,0 @@ -const Bluebird = require("bluebird") -const { Base64 } = require("js-base64") -const _ = require("lodash") - -// import classes -const { Config } = require("@classes/Config.js") -const { File, DataType, HomepageType } = require("@classes/File.js") - -const { - sanitizedYamlParse, - sanitizedYamlStringify, -} = require("@utils/yaml-utils") - -// Constants -const FOOTER_PATH = "footer.yml" -const NAVIGATION_PATH = "navigation.yml" -const { HOMEPAGE_FILENAME } = require("@root/constants") - -const retrieveSettingsFiles = async ( - accessToken, - siteName, - shouldRetrieveHomepage -) => { - const configResp = new Config(accessToken, siteName) - - const FooterFile = new File(accessToken, siteName) - const dataType = new DataType() - FooterFile.setFileType(dataType) - - const NavigationFile = new File(accessToken, siteName) - NavigationFile.setFileType(dataType) - - const HomepageFile = new File(accessToken, siteName) - const homepageType = new HomepageType() - HomepageFile.setFileType(homepageType) - - const fileRetrievalObj = { - config: configResp.read(), - footer: FooterFile.read(FOOTER_PATH), - navigation: NavigationFile.read(NAVIGATION_PATH), - } - - // Retrieve homepage only if flag is set to true - if (shouldRetrieveHomepage) { - fileRetrievalObj.homepage = HomepageFile.read(HOMEPAGE_FILENAME) - } - - const fileContentsArr = await Bluebird.map( - Object.keys(fileRetrievalObj), - async (fileOpKey) => { - const { content, sha } = await fileRetrievalObj[fileOpKey] - - // homepage requires special extraction as the content is wrapped in front matter - if (fileOpKey === "homepage") { - const homepageContent = Base64.decode(content) - const homepageFrontMatterObj = sanitizedYamlParse( - homepageContent.split("---")[1] - ) - return { type: fileOpKey, content: homepageFrontMatterObj, sha } - } - - return { - type: fileOpKey, - content: sanitizedYamlParse(Base64.decode(content)), - sha, - } - } - ) - - // Convert to an object so that data is accessible by key - const fileContentsObj = {} - fileContentsArr.forEach((fileObj) => { - const { type, content, sha } = fileObj - fileContentsObj[type] = { content, sha } - }) - - return { - configResp, - FooterFile, - NavigationFile, - HomepageFile, - fileContentsObj, - } -} - -class Settings { - constructor(accessToken, siteName) { - this.accessToken = accessToken - this.siteName = siteName - } - - async get() { - const { - fileContentsObj: { config, footer, navigation }, - } = await retrieveSettingsFiles(this.accessToken, this.siteName) - - // convert data to object form - const configContent = config.content - const footerContent = footer.content - const navigationContent = navigation.content - - // retrieve only the relevant config and index fields - const configFieldsRequired = { - url: configContent.url, - title: configContent.title, - description: configContent.description, - favicon: configContent.favicon, - shareicon: configContent.shareicon, - is_government: configContent.is_government, - facebook_pixel: configContent["facebook-pixel"], - google_analytics_ga4: configContent.google_analytics_ga4, - linkedin_insights: configContent["linkedin-insights"], - resources_name: configContent.resources_name, - colors: configContent.colors, - } - - // retrieve footer sha since we are sending the footer object wholesale - const footerSha = footer.sha - - return { - configFieldsRequired, - footerContent, - navigationContent: { logo: navigationContent.logo }, - footerSha, - } - } - - async post(payload) { - const { - configResp, - FooterFile, - NavigationFile, - HomepageFile, - fileContentsObj: { config, footer, navigation, homepage }, - } = await retrieveSettingsFiles(this.accessToken, this.siteName, true) - - // extract data - const { footerSettings, configSettings, navigationSettings } = payload - - // update settings objects - const configContent = config.content - const footerContent = footer.content - const navigationContent = navigation.content - - const settingsObj = {} - - if (!_.isEmpty(configSettings)) { - settingsObj.config = { - payload: configSettings, - currentData: configContent, - } - } - - if (!_.isEmpty(footerSettings)) { - // We want to remove empty footer settings entirely so they don't show up in the actual site - const clonedFooterSettings = _.cloneDeep(footerSettings) - const clonedFooterContent = _.cloneDeep(footerContent) - Object.keys(footerSettings).forEach((setting) => { - if (setting === "social_media") { - const socials = footerSettings[setting] - Object.keys(socials).forEach((social) => { - if (!socials[social]) { - delete clonedFooterSettings[setting][social] - delete clonedFooterContent[setting][social] - } - }) - } else if (footerSettings[setting] === "") { - // Check for empty string because false value exists - delete clonedFooterSettings[setting] - delete clonedFooterContent[setting] - } - }) - settingsObj.footer = { - payload: clonedFooterSettings, - currentData: clonedFooterContent, - } - } - - if (!_.isEmpty(navigationSettings)) { - settingsObj.navigation = { - payload: navigationSettings, - currentData: navigationContent, - } - } - - const updatedSettingsObjArr = Object.keys(settingsObj).map( - (settingsObjKey) => { - const { payload: retrievedPayload, currentData } = settingsObj[ - settingsObjKey - ] - const clonedSettingsObj = _.cloneDeep(currentData) - Object.keys(retrievedPayload).forEach((setting) => { - clonedSettingsObj[setting] = retrievedPayload[setting] - }) - return { - type: settingsObjKey, - settingsObj: clonedSettingsObj, - } - } - ) - - const updatedSettingsObj = {} - updatedSettingsObjArr.forEach((setting) => { - const { type, settingsObj: retrievedSettingsObj } = setting - updatedSettingsObj[`${type}SettingsObj`] = retrievedSettingsObj - }) - - const { - configSettingsObj, - footerSettingsObj, - navigationSettingsObj, - } = updatedSettingsObj - - // To-do: use Git Tree to speed up operations - if (!_.isEmpty(configSettings)) { - const newConfigContent = Base64.encode( - sanitizedYamlStringify(configSettingsObj) - ) - await configResp.update(newConfigContent, config.sha) - - // Update title and description in homepage as well if it's changed - const hasTitleChanged = configContent.title !== configSettingsObj.title - const hasDescriptionChanged = - configContent.description !== configSettingsObj.description - if (hasTitleChanged || hasDescriptionChanged) { - const { content: homepageContentObj, sha } = homepage - - if (hasTitleChanged) homepageContentObj.title = configSettings.title - if (hasDescriptionChanged) - homepageContentObj.description = configSettings.description - const homepageFrontMatter = sanitizedYamlStringify(homepageContentObj) - - const homepageContent = ["---\n", homepageFrontMatter, "---"].join("") - const newHomepageContent = Base64.encode(homepageContent) - - await HomepageFile.update(HOMEPAGE_FILENAME, newHomepageContent, sha) - } - } - - if (!_.isEmpty(footerSettings)) { - const newFooterContent = Base64.encode( - sanitizedYamlStringify(footerSettingsObj) - ) - await FooterFile.update(FOOTER_PATH, newFooterContent, footer.sha) - } - - if (!_.isEmpty(navigationSettings)) { - const newNavigationContent = Base64.encode( - sanitizedYamlStringify(navigationSettingsObj) - ) - await NavigationFile.update( - NAVIGATION_PATH, - newNavigationContent, - navigation.sha - ) - } - } -} - -module.exports = { Settings } diff --git a/src/classes/Subfolder.js b/src/classes/Subfolder.js deleted file mode 100644 index 60a3ffee7..000000000 --- a/src/classes/Subfolder.js +++ /dev/null @@ -1,45 +0,0 @@ -const { CollectionConfig } = require("@classes/Config.js") -const { Directory, FolderType } = require("@classes/Directory.js") -const { File, CollectionPageType } = require("@classes/File.js") - -class Subfolder { - constructor(accessToken, siteName, collectionName) { - this.accessToken = accessToken - this.siteName = siteName - this.collectionName = collectionName - } - - async list() { - const IsomerDirectory = new Directory(this.accessToken, this.siteName) - const folderType = new FolderType(`_${this.collectionName}`) - IsomerDirectory.setDirType(folderType) - const repoRootContent = await IsomerDirectory.list() - - const allSubfolders = repoRootContent.reduce((acc, curr) => { - if (curr.type === "dir") { - const pathTokens = curr.path.split("/") - acc.push(pathTokens.slice(1).join("/")) - } - return acc - }, []) - return allSubfolders - } - - async create(subfolderName) { - // Update collection.yml - const collectionConfig = new CollectionConfig( - this.accessToken, - this.siteName, - this.collectionName - ) - await collectionConfig.addItemToOrder(`${subfolderName}/.keep`) - - // Create placeholder file - const IsomerFile = new File(this.accessToken, this.siteName) - const dataType = new CollectionPageType(this.collectionName) - IsomerFile.setFileType(dataType) - await IsomerFile.create(`${subfolderName}/.keep`, "") - } -} - -module.exports = { Subfolder } diff --git a/src/middleware/stats.ts b/src/middleware/stats.ts index a941b60e7..1cd889b95 100644 --- a/src/middleware/stats.ts +++ b/src/middleware/stats.ts @@ -41,10 +41,6 @@ export class StatsMiddleware { this.statsService.countMigratedSites() ) - trackV1GithubLogins = wrapAsRequestHandler(async () => - this.statsService.trackGithubLogins(Versions.V1) - ) - trackV2GithubLogins = wrapAsRequestHandler(async () => this.statsService.trackGithubLogins(Versions.V2) ) diff --git a/src/routes/v1/auth.js b/src/routes/v1/auth.js deleted file mode 100644 index fc80449f9..000000000 --- a/src/routes/v1/auth.js +++ /dev/null @@ -1,174 +0,0 @@ -import { config } from "@config/config" - -import { isSecure } from "@root/utils/auth-utils" - -const axios = require("axios") -const express = require("express") -const queryString = require("query-string") -const uuid = require("uuid/v4") - -const logger = require("@logger/logger").default - -// Import error -const { AuthError } = require("@errors/AuthError") -const { ForbiddenError } = require("@errors/ForbiddenError") - -// Import middleware -const { attachReadRouteHandlerWrapper } = require("@middleware/routeHandler") - -const { validateStatus } = require("@utils/axios-utils") -const jwtUtils = require("@utils/jwt-utils") - -const { authenticationMiddleware } = require("@root/middleware") -const { statsMiddleware } = require("@root/middleware/stats") -// Import services -const identityServices = require("@services/identity") - -const router = express.Router() - -const CLIENT_ID = config.get("github.clientId") -const CLIENT_SECRET = config.get("github.clientSecret") -const REDIRECT_URI = config.get("github.redirectUri") -const CSRF_TOKEN_EXPIRY_MS = 600000 -const FRONTEND_URL = config.get("app.frontendUrl") - -const CSRF_COOKIE_NAME = "isomer-csrf" -const COOKIE_NAME = "isomercms" - -async function clearAllCookies(res) { - const cookieSettings = { - path: "/", - } - - res.clearCookie(COOKIE_NAME, cookieSettings) - res.clearCookie(CSRF_COOKIE_NAME, cookieSettings) -} - -async function authRedirect(req, res) { - const state = uuid() - const githubAuthUrl = `https://github.com/login/oauth/authorize?client_id=${CLIENT_ID}&state=${state}&scope=repo` - - const csrfTokenExpiry = new Date() - csrfTokenExpiry.setTime(csrfTokenExpiry.getTime() + CSRF_TOKEN_EXPIRY_MS) - - const cookieSettings = { - expires: csrfTokenExpiry, - httpOnly: true, - secure: isSecure, - } - - const token = jwtUtils.signToken({ state }) - - res.cookie(CSRF_COOKIE_NAME, token, cookieSettings) - return res.redirect(githubAuthUrl) -} - -async function githubAuth(req, res) { - const csrfState = req.cookies[CSRF_COOKIE_NAME] - const { code, state } = req.query - - try { - const decoded = jwtUtils.verifyToken(csrfState) - if (decoded.state !== state) throw new Error("State does not match") - } catch (err) { - throw new ForbiddenError() - } - - const params = { - code, - redirect_uri: REDIRECT_URI, - state, - } - - const resp = await axios.post( - "https://github.com/login/oauth/access_token", - params, - { - auth: { - username: CLIENT_ID, - password: CLIENT_SECRET, - }, - } - ) - - const { access_token: accessToken } = queryString.parse(resp.data) - if (!accessToken) throw new AuthError("Access token not found") - - // Retrieve user information to put into access token - const endpoint = `https://api.github.com/user` - const userResp = await axios.get(endpoint, { - validateStatus, - headers: { - Authorization: `token ${accessToken}`, - Accept: "application/vnd.github.v3+json", - "Content-Type": "application/json", - }, - }) - - const githubId = userResp.data && userResp.data.login - - // Create user if does not exists. Set last logged in to current time. - const user = await identityServices.usersService.login(githubId) - if (!user) throw Error("Failed to create user") - - const userInfo = { - accessToken: jwtUtils.encryptToken(accessToken), - githubId, - isomerUserId: user.id, - } - Object.assign(req.session, { userInfo }) - logger.info(`User ${userInfo.email} successfully logged in`) - return res.redirect(`${FRONTEND_URL}/sites`) -} - -async function logout(req, res) { - clearAllCookies(res) - const { email } = req.session.userInfo - req.session.destroy() - logger.info(`User ${email} successfully logged out`) - return res.sendStatus(200) -} - -async function whoami(req, res) { - const { userSessionData } = res.locals - const { accessToken } = userSessionData - - // Make a call to github - const endpoint = "https://api.github.com/user" - - try { - const resp = await axios.get(endpoint, { - headers: { - Authorization: `token ${accessToken}`, - "Content-Type": "application/json", - }, - }) - const userId = resp.data.login - - const { - email, - contactNumber, - } = await identityServices.usersService.findByGitHubId(userId) - return res.status(200).json({ userId, email, contactNumber }) - } catch (err) { - clearAllCookies(res) - // Return a 401 so that user will be redirected to logout - return res.sendStatus(401) - } -} - -router.get("/github-redirect", attachReadRouteHandlerWrapper(authRedirect)) -router.get( - "/", - statsMiddleware.trackV1GithubLogins, - attachReadRouteHandlerWrapper(githubAuth) -) -router.delete("/logout", attachReadRouteHandlerWrapper(logout)) -router.get( - "/whoami", - authenticationMiddleware.verifyAccess, - statsMiddleware.countDbUsers, - attachReadRouteHandlerWrapper(whoami) -) - -module.exports = router diff --git a/src/routes/v1/authenticated/index.js b/src/routes/v1/authenticated/index.js deleted file mode 100644 index 278783239..000000000 --- a/src/routes/v1/authenticated/index.js +++ /dev/null @@ -1,33 +0,0 @@ -import { Versions } from "@root/constants" - -const express = require("express") - -const sitesRouter = require("@routes/v1/authenticated/sites") -const { UsersRouter } = require("@routes/v2/authenticated/users") - -const getAuthenticatedSubrouter = ({ - authenticationMiddleware, - usersService, - statsMiddleware, - apiLogger, -}) => { - // Workaround - no v1 users router exists - const usersRouter = new UsersRouter({ usersService }) - - const authenticatedSubrouter = express.Router({ mergeParams: true }) - - authenticatedSubrouter.use(authenticationMiddleware.verifyAccess) - // NOTE: apiLogger needs to be after `verifyAccess` as it logs the github username - // which is only available after verifying that the jwt is valid - authenticatedSubrouter.use(apiLogger) - authenticatedSubrouter.use("/sites", sitesRouter) - authenticatedSubrouter.use( - "/user", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "user"), - usersRouter.getRouter() - ) - - return authenticatedSubrouter -} - -export default getAuthenticatedSubrouter diff --git a/src/routes/v1/authenticated/sites.js b/src/routes/v1/authenticated/sites.js deleted file mode 100644 index 95f162c55..000000000 --- a/src/routes/v1/authenticated/sites.js +++ /dev/null @@ -1,208 +0,0 @@ -import { Versions } from "@constants" - -import { config } from "@config/config" - -import { GITHUB_ORG_REPOS_ENDPOINT } from "@root/constants" -import { statsMiddleware } from "@root/middleware/stats" - -const Bluebird = require("bluebird") -const express = require("express") -const _ = require("lodash") - -// Import error -const { NotFoundError } = require("@errors/NotFoundError") - -const { attachReadRouteHandlerWrapper } = require("@middleware/routeHandler") - -const { - genericGitHubAxiosInstance: axios, -} = require("@services/api/AxiosInstance") - -const router = express.Router() - -const GH_MAX_REPO_COUNT = 100 -const ISOMERPAGES_REPO_PAGE_COUNT = config.get("sites.pageCount") -const ISOMER_GITHUB_ORG_NAME = config.get("github.orgName") -const ISOMER_ADMIN_REPOS = [ - "isomercms-backend", - "isomercms-frontend", - "isomer-redirection", - "isomerpages-template", - "isomer-conversion-scripts", - "isomer-wysiwyg", - "isomer-slackbot", - "isomer-tooling", - "generate-site", - "travisci-scripts", - "recommender-train", - "editor", - "ci-test", - "infra", - "markdown-helper", -] - -// timeDiff tells us when a repo was last updated in terms of days (for e.g. 2 days ago, -// today) -const timeDiff = (lastUpdated) => { - const gapInUpdate = new Date().getTime() - new Date(lastUpdated).getTime() - const numDaysAgo = Math.floor(gapInUpdate / (1000 * 60 * 60 * 24)) - // return a message for number of days ago repo was last updated - switch (numDaysAgo) { - case 0: - return "Updated today" - case 1: - return "Updated 1 day ago" - default: - return `Updated ${numDaysAgo} days ago` - } -} - -/* Returns a list of all sites (repos) that the user has access to on Isomer. */ -// TO-DO: Paginate properly -async function getSites(req, res) { - const { userSessionData } = res.locals - const { accessToken } = userSessionData - - // Simultaneously retrieve all isomerpages repos - const paramsArr = [] - for (let i = 0; i < ISOMERPAGES_REPO_PAGE_COUNT; i += 1) { - paramsArr.push({ - per_page: GH_MAX_REPO_COUNT, - sort: "full_name", - page: i + 1, - }) - } - - const sites = await Bluebird.map(paramsArr, async (params) => { - const resp = await axios.get(GITHUB_ORG_REPOS_ENDPOINT, { - params, - headers: { - Authorization: `token ${accessToken}`, - "Content-Type": "application/json", - }, - }) - - return resp.data - .map((repoData) => { - const { - pushed_at: updatedAt, - permissions, - name, - private: isPrivate, - } = repoData - - return { - lastUpdated: timeDiff(updatedAt), - permissions, - repoName: name, - isPrivate, - } - }) - .filter( - (repoData) => - repoData.permissions.push === true && - !ISOMER_ADMIN_REPOS.includes(repoData.repoName) - ) - }) - - const flattenedSites = _.flatten(sites) - - return res.status(200).json({ siteNames: flattenedSites }) -} - -/* Checks if a user has access to a repo. */ -async function checkHasAccess(req, res) { - const { userSessionData } = res.locals - const { siteName } = req.params - - const { accessToken } = userSessionData - const userId = userSessionData.githubId - const endpoint = `https://api.github.com/repos/${ISOMER_GITHUB_ORG_NAME}/${siteName}/collaborators/${userId}` - - try { - await axios.get(endpoint, { - headers: { - Authorization: `token ${accessToken}`, - "Content-Type": "application/json", - }, - }) - return res.status(200).send("OK") - } catch (err) { - const { status } = err.response - // If user is unauthorized or site does not exist, show the same NotFoundError - if (status === 404 || status === 403) - throw new NotFoundError("Site does not exist") - throw err - } -} - -/* Gets the last updated time of the repo. */ -async function getLastUpdated(req, res) { - const { userSessionData } = res.locals - const { siteName } = req.params - - const { accessToken } = userSessionData - const endpoint = `https://api.github.com/repos/${ISOMER_GITHUB_ORG_NAME}/${siteName}` - const resp = await axios.get(endpoint, { - headers: { - Authorization: `token ${accessToken}`, - "Content-Type": "application/json", - }, - }) - const { pushed_at: updatedAt } = resp.data - return res.status(200).json({ lastUpdated: timeDiff(updatedAt) }) -} - -/* Gets the link to the staging site for a repo. */ -async function getStagingUrl(req, res) { - // TODO: reconsider how we can retrieve url - we can store this in _config.yml or a dynamodb - const { userSessionData } = res.locals - const { siteName } = req.params - - const { accessToken } = userSessionData - const endpoint = `https://api.github.com/repos/${ISOMER_GITHUB_ORG_NAME}/${siteName}` - const resp = await axios.get(endpoint, { - headers: { - Authorization: `token ${accessToken}`, - "Content-Type": "application/json", - }, - }) - - const { description } = resp.data - - let stagingUrl - - if (description) { - // Retrieve the url from the description - repo descriptions have varying formats, so we look for the first link - const descTokens = description.replace("/;/g", " ").split(" ") - // Staging urls also contain staging in their url - stagingUrl = descTokens.find( - (token) => token.includes("http") && token.includes("staging") - ) - } - - return res.status(200).json({ stagingUrl }) -} - -router.get( - "/", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "getSites"), - attachReadRouteHandlerWrapper(getSites) -) -router.get( - "/:siteName", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "checkHasAccess"), - attachReadRouteHandlerWrapper(checkHasAccess) -) -router.get( - "/:siteName/lastUpdated", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "getLastUpdated"), - attachReadRouteHandlerWrapper(getLastUpdated) -) -router.get( - "/:siteName/stagingUrl", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "getStagingUrl"), - attachReadRouteHandlerWrapper(getStagingUrl) -) - -module.exports = router diff --git a/src/routes/v1/authenticatedSites/collectionPages.js b/src/routes/v1/authenticatedSites/collectionPages.js deleted file mode 100644 index 0b1d35aa8..000000000 --- a/src/routes/v1/authenticatedSites/collectionPages.js +++ /dev/null @@ -1,298 +0,0 @@ -import { Versions } from "@constants" - -import { statsMiddleware } from "@root/middleware/stats" - -const Bluebird = require("bluebird") -const express = require("express") -const { Base64 } = require("js-base64") -const _ = require("lodash") - -// Import errors -const { NotFoundError } = require("@errors/NotFoundError") - -// Import middleware -const { - attachReadRouteHandlerWrapper, - attachWriteRouteHandlerWrapper, - attachRollbackRouteHandlerWrapper, -} = require("@middleware/routeHandler") - -// Import classes -const { Collection } = require("@classes/Collection") -const { CollectionConfig } = require("@classes/Config") -const { File, CollectionPageType } = require("@classes/File") - -// Import utils -const { readCollectionPageUtilFunc } = require("@utils/route-utils") -const { sanitizedYamlParse } = require("@utils/yaml-utils") - -const router = express.Router({ mergeParams: true }) - -// List pages in collection -async function listCollectionPages(req, res) { - const { userWithSiteSessionData } = res.locals - const { siteName, collectionName } = req.params - const { accessToken } = userWithSiteSessionData - - // TO-DO: Verify that collection exists - - const IsomerFile = new File(accessToken, siteName) - const collectionPageType = new CollectionPageType(collectionName) - IsomerFile.setFileType(collectionPageType) - const collectionPages = await IsomerFile.list() - - return res.status(200).json({ collectionPages }) -} - -// Get details on all pages in a collection -async function listCollectionPagesDetails(req, res) { - const { userWithSiteSessionData } = res.locals - const { siteName, collectionName } = req.params - const { accessToken } = userWithSiteSessionData - - // Verify that collection exists - const IsomerCollection = new Collection(accessToken, siteName) - const collections = await IsomerCollection.list() - if (!collections.includes(collectionName)) - throw new NotFoundError("Collection provided was not a valid collection") - - // Retrieve metadata of files in collection - const CollectionPage = new File(accessToken, siteName) - const collectionPageType = new CollectionPageType(collectionName) - CollectionPage.setFileType(collectionPageType) - const collectionPages = await CollectionPage.list() - const collectionPagesMetadata = await Bluebird.map( - collectionPages, - async (page) => { - const { content } = await readCollectionPageUtilFunc( - accessToken, - siteName, - collectionName, - page.fileName - ) - const frontMatter = sanitizedYamlParse( - Base64.decode(content).split("---")[1] - ) - return { - fileName: page.fileName, - title: frontMatter.title, - thirdNavTitle: frontMatter.third_nav_title, - } - } - ) - - const collectionHierarchy = collectionPagesMetadata.reduce((acc, file) => { - if (file.thirdNavTitle) { - // Check whether third nav section already exists - const thirdNavIteratee = { type: "third-nav", title: file.thirdNavTitle } - if (_.some(acc, thirdNavIteratee)) { - const thirdNavIdx = _.findIndex(acc, thirdNavIteratee) - acc[thirdNavIdx].contents.push({ - type: "third-nav-page", - title: file.title, - fileName: file.fileName, - }) - return acc - } - - // Create new third nav section - acc.push({ - type: "third-nav", - title: file.thirdNavTitle, - contents: [ - { - type: "third-nav-page", - title: file.title, - fileName: file.fileName, - }, - ], - }) - return acc - } - - // If no third nav title, just push into array - acc.push({ - type: "page", - title: file.title, - fileName: file.fileName, - }) - return acc - }, []) - - return res.status(200).json({ collectionPages: collectionHierarchy }) -} - -// // Create new page in collection -async function createCollectionPage(req, res) { - const { userWithSiteSessionData } = res.locals - const { accessToken } = userWithSiteSessionData - - const { siteName, collectionName, pageName: encodedPageName } = req.params - const { content: pageContent } = req.body - const pageName = decodeURIComponent(encodedPageName) - - const IsomerFile = new File(accessToken, siteName) - const collectionPageType = new CollectionPageType(collectionName) - IsomerFile.setFileType(collectionPageType) - await IsomerFile.create(pageName, Base64.encode(pageContent)) - - const config = new CollectionConfig(accessToken, siteName, collectionName) - await config.addItemToOrder(pageName) - - return res.status(200).json({ collectionName, pageName, pageContent }) -} - -// Read page in collection -async function readCollectionPage(req, res) { - const { userWithSiteSessionData } = res.locals - const { accessToken } = userWithSiteSessionData - - const { siteName, pageName: encodedPageName, collectionName } = req.params - const pageName = decodeURIComponent(encodedPageName) - - const IsomerFile = new File(accessToken, siteName) - const collectionPageType = new CollectionPageType(collectionName) - IsomerFile.setFileType(collectionPageType) - const { sha, content: encodedContent } = await IsomerFile.read(pageName) - const content = Base64.decode(encodedContent) - - // TO-DO: - // Validate content - - return res.status(200).json({ collectionName, pageName, sha, content }) -} - -// Update page in collection -async function updateCollectionPage(req, res) { - const { userWithSiteSessionData } = res.locals - const { accessToken } = userWithSiteSessionData - - const { siteName, pageName: encodedPageName, collectionName } = req.params - const { content: pageContent, sha } = req.body - const pageName = decodeURIComponent(encodedPageName) - - // TO-DO: - // Validate pageName and content - - const IsomerFile = new File(accessToken, siteName) - const collectionPageType = new CollectionPageType(collectionName) - IsomerFile.setFileType(collectionPageType) - const { newSha } = await IsomerFile.update( - pageName, - Base64.encode(pageContent), - sha - ) - - return res - .status(200) - .json({ collectionName, pageName, pageContent, sha: newSha }) -} - -// Delete page in collection -async function deleteCollectionPage(req, res) { - const { userWithSiteSessionData } = res.locals - const { accessToken } = userWithSiteSessionData - - const { siteName, pageName: encodedPageName, collectionName } = req.params - const { sha } = req.body - const pageName = decodeURIComponent(encodedPageName) - // TO-DO: - // Validate that collection exists - - const IsomerFile = new File(accessToken, siteName) - const collectionPageType = new CollectionPageType(collectionName) - IsomerFile.setFileType(collectionPageType) - await IsomerFile.delete(pageName, sha) - - const collectionConfig = new CollectionConfig( - accessToken, - siteName, - collectionName - ) - await collectionConfig.deleteItemFromOrder(pageName) - - return res.status(200).send("OK") -} - -// Rename page in collection -async function renameCollectionPage(req, res) { - const { userWithSiteSessionData } = res.locals - const { accessToken } = userWithSiteSessionData - - const { - siteName, - pageName: encodedPageName, - collectionName, - newPageName: encodedNewPageName, - } = req.params - const { sha, content: pageContent } = req.body - - const pageName = decodeURIComponent(encodedPageName) - const newPageName = decodeURIComponent(encodedNewPageName) - - const collectionConfig = new CollectionConfig( - accessToken, - siteName, - collectionName - ) - // TO-DO: - // Validate that collection exists - // Validate pageName and content - - // Create new collection page with name ${newPageName} - - const IsomerFile = new File(accessToken, siteName) - const collectionPageType = new CollectionPageType(collectionName) - IsomerFile.setFileType(collectionPageType) - const { sha: newSha } = await IsomerFile.create( - newPageName, - Base64.encode(pageContent) - ) - await IsomerFile.delete(pageName, sha) - await collectionConfig.updateItemInOrder(pageName, newPageName) - - return res - .status(200) - .json({ collectionName, pageName: newPageName, pageContent, sha: newSha }) -} - -router.get( - "/", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "listCollectionPages"), - attachReadRouteHandlerWrapper(listCollectionPages) -) -router.get( - "/pages", - statsMiddleware.logVersionNumberCallFor( - Versions.V1, - "listCollectionPagesDetails" - ), - attachReadRouteHandlerWrapper(listCollectionPagesDetails) -) -router.post( - "/pages/new/:pageName", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "createCollectionPage"), - attachRollbackRouteHandlerWrapper(createCollectionPage) -) -router.get( - "/pages/:pageName", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "readCollectionPage"), - attachReadRouteHandlerWrapper(readCollectionPage) -) -router.post( - "/pages/:pageName", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "updateCollectionPage"), - attachWriteRouteHandlerWrapper(updateCollectionPage) -) -router.delete( - "/pages/:pageName", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "deleteCollectionPage"), - attachRollbackRouteHandlerWrapper(deleteCollectionPage) -) -router.post( - "/pages/:pageName/rename/:newPageName", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "renameCollectionPage"), - attachRollbackRouteHandlerWrapper(renameCollectionPage) -) - -module.exports = router diff --git a/src/routes/v1/authenticatedSites/collections.js b/src/routes/v1/authenticatedSites/collections.js deleted file mode 100644 index 5a8125d26..000000000 --- a/src/routes/v1/authenticatedSites/collections.js +++ /dev/null @@ -1,214 +0,0 @@ -import { Versions } from "@constants" - -import { statsMiddleware } from "@root/middleware/stats" - -const express = require("express") -const { Base64 } = require("js-base64") - -// Import middleware -const { - attachReadRouteHandlerWrapper, - attachRollbackRouteHandlerWrapper, -} = require("@middleware/routeHandler") - -// Import classes -const { Collection } = require("@classes/Collection") -const { CollectionConfig } = require("@classes/Config") -const { File, CollectionPageType, PageType } = require("@classes/File") -const { Subfolder } = require("@classes/Subfolder") - -const { deslugifyCollectionName } = require("@utils/utils") -const { - sanitizedYamlParse, - sanitizedYamlStringify, -} = require("@utils/yaml-utils") - -const router = express.Router({ mergeParams: true }) - -// List collections -async function listCollections(req, res) { - const { userWithSiteSessionData } = res.locals - const { siteName } = req.params - const { accessToken } = userWithSiteSessionData - - const IsomerCollection = new Collection(accessToken, siteName) - const collections = await IsomerCollection.list() - - return res.status(200).json({ collections }) -} - -// Create new collection -async function createNewCollection(req, res) { - const { userWithSiteSessionData } = res.locals - const { siteName } = req.params - const { collectionName } = req.body - const { accessToken } = userWithSiteSessionData - - const IsomerCollection = new Collection(accessToken, siteName) - await IsomerCollection.create(collectionName) - - return res.status(200).json({ collectionName }) -} - -// Delete collection -async function deleteCollection(req, res) { - // TO-DO: Verify that collection exists - - // Remove collection from config file - const { userWithSiteSessionData, githubSessionData } = res.locals - const { siteName, collectionName } = req.params - const { accessToken } = userWithSiteSessionData - const { currentCommitSha, treeSha } = githubSessionData.getGithubState() - - const IsomerCollection = new Collection(accessToken, siteName) - await IsomerCollection.delete(collectionName, currentCommitSha, treeSha) - - return res.status(200).json({ collectionName }) -} - -// Rename collection -async function renameCollection(req, res) { - // TO-DO: Verify that collection exists - - // Remove collection from config file - const { userWithSiteSessionData, githubSessionData } = res.locals - const { siteName, collectionName, newCollectionName } = req.params - const { accessToken } = userWithSiteSessionData - const { currentCommitSha, treeSha } = githubSessionData.getGithubState() - - const IsomerCollection = new Collection(accessToken, siteName) - await IsomerCollection.rename( - collectionName, - newCollectionName, - currentCommitSha, - treeSha - ) - - return res.status(200).json({ collectionName, newCollectionName }) -} - -// Move files in collection -async function moveFiles(req, res) { - const { userWithSiteSessionData } = res.locals - const { accessToken } = userWithSiteSessionData - const { siteName, collectionPath, targetPath } = req.params - const { files } = req.body - const processedCollectionPathTokens = decodeURIComponent( - collectionPath - ).split("/") - const collectionName = processedCollectionPathTokens[0] - const collectionSubfolderName = processedCollectionPathTokens[1] - const processedTargetPathTokens = decodeURIComponent(targetPath).split("/") - const targetCollectionName = processedTargetPathTokens[0] - const targetSubfolderName = processedTargetPathTokens[1] - - const IsomerCollection = new Collection(accessToken, siteName) - const collections = await IsomerCollection.list() - - // Check if collection already exists - if ( - !collections.includes(targetCollectionName) && - targetCollectionName !== "pages" - ) { - await IsomerCollection.create(targetCollectionName) - } - - const oldIsomerFile = new File(accessToken, siteName) - const newIsomerFile = new File(accessToken, siteName) - const oldCollectionPageType = new CollectionPageType( - decodeURIComponent(collectionPath) - ) - const newCollectionPageType = - targetCollectionName === "pages" - ? new PageType() - : new CollectionPageType(decodeURIComponent(targetPath)) - oldIsomerFile.setFileType(oldCollectionPageType) - newIsomerFile.setFileType(newCollectionPageType) - const oldConfig = new CollectionConfig(accessToken, siteName, collectionName) - const newConfig = - targetCollectionName === "pages" - ? null - : new CollectionConfig(accessToken, siteName, targetCollectionName) - - if (newConfig && targetSubfolderName) { - // Check if subfolder exists - const IsomerSubfolder = new Subfolder( - accessToken, - siteName, - targetCollectionName - ) - const subfolders = await IsomerSubfolder.list() - if (!subfolders.includes(targetSubfolderName)) - await IsomerSubfolder.create(targetSubfolderName) - } - - // We can't perform these operations concurrently because of conflict issues - - // To fix after refactoring - /* eslint-disable no-await-in-loop, no-restricted-syntax */ - for (const fileName of files) { - const { content, sha } = await oldIsomerFile.read(fileName) - await oldIsomerFile.delete(fileName, sha) - if (targetSubfolderName || collectionSubfolderName) { - // Modifying third nav in front matter, to be removed after template rewrite - - // eslint-disable-next-line no-unused-vars - const [unused, encodedFrontMatter, pageContent] = Base64.decode( - content - ).split("---") - const frontMatter = sanitizedYamlParse(encodedFrontMatter) - if (targetSubfolderName) - frontMatter.third_nav_title = deslugifyCollectionName( - targetSubfolderName - ) - else delete frontMatter.third_nav_title - const newFrontMatter = sanitizedYamlStringify(frontMatter) - const newContent = ["---\n", newFrontMatter, "---", pageContent].join("") - const newEncodedContent = Base64.encode(newContent) - await newIsomerFile.create(fileName, newEncodedContent) - } else { - await newIsomerFile.create(fileName, content) - } - - // Update collection.yml files - await oldConfig.deleteItemFromOrder( - `${ - collectionSubfolderName ? `${collectionSubfolderName}/` : "" - }${fileName}` - ) - if (newConfig) - await newConfig.addItemToOrder( - `${targetSubfolderName ? `${targetSubfolderName}/` : ""}${fileName}` - ) - } - - return res.status(200).send("OK") -} - -router.get( - "/", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "listCollections"), - attachReadRouteHandlerWrapper(listCollections) -) -router.post( - "/", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "createNewCollection"), - attachRollbackRouteHandlerWrapper(createNewCollection) -) -router.delete( - "/:collectionName", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "deleteCollection"), - attachRollbackRouteHandlerWrapper(deleteCollection) -) -router.post( - "/:collectionName/rename/:newCollectionName", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "renameCollection"), - attachRollbackRouteHandlerWrapper(renameCollection) -) -router.post( - "/:collectionPath/move/:targetPath", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "moveFiles"), - attachRollbackRouteHandlerWrapper(moveFiles) -) - -module.exports = router diff --git a/src/routes/v1/authenticatedSites/directory.js b/src/routes/v1/authenticatedSites/directory.js deleted file mode 100644 index c003e0c8f..000000000 --- a/src/routes/v1/authenticatedSites/directory.js +++ /dev/null @@ -1,44 +0,0 @@ -import { Versions } from "@constants" - -import { statsMiddleware } from "@root/middleware/stats" - -const express = require("express") - -const router = express.Router({ mergeParams: true }) - -// Import middleware -const { attachReadRouteHandlerWrapper } = require("@middleware/routeHandler") - -const { Directory, FolderType } = require("@classes/Directory") - -// List pages and directories in folder -async function listDirectoryContent(req, res) { - const { userWithSiteSessionData } = res.locals - const { siteName, path } = req.params - const { accessToken } = userWithSiteSessionData - - const decodedPath = decodeURIComponent(path) - - const IsomerDirectory = new Directory(accessToken, siteName) - const folderType = new FolderType(decodedPath) - IsomerDirectory.setDirType(folderType) - let directoryContents = [] - - // try catch should be removed during refactor - // .list() should return an empty array instead of throwing error - try { - directoryContents = await IsomerDirectory.list() - } catch (e) { - // directory does not exist, catch error - console.log(e) - } - return res.status(200).json({ directoryContents }) -} - -router.get( - "/:path", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "listDirectoryContent"), - attachReadRouteHandlerWrapper(listDirectoryContent) -) - -module.exports = router diff --git a/src/routes/v1/authenticatedSites/documents.js b/src/routes/v1/authenticatedSites/documents.js deleted file mode 100644 index 5711374a2..000000000 --- a/src/routes/v1/authenticatedSites/documents.js +++ /dev/null @@ -1,223 +0,0 @@ -import { Versions } from "@constants" - -import { statsMiddleware } from "@root/middleware/stats" - -const express = require("express") - -const router = express.Router({ mergeParams: true }) - -// Import classes -const { - attachReadRouteHandlerWrapper, - attachWriteRouteHandlerWrapper, - attachRollbackRouteHandlerWrapper, -} = require("@middleware/routeHandler") - -const { File, DocumentType } = require("@classes/File") -const { MediaFile } = require("@classes/MediaFile") - -const extractDirectoryAndFileName = (documentName) => { - let documentDirectory - let documentFileName - - // documentName contains the file path excluding the media folder, e.g. subfolder1/subfolder2/file.pdf - const pathArr = documentName.split("/") - if (pathArr.length === 1) { - // documentName only contains the file name - documentDirectory = "files" - documentFileName = documentName - } else if (pathArr.length > 1) { - // We discard the name of the file for the directory - documentDirectory = `files/${pathArr.slice(0, -1).join("/")}` - documentFileName = pathArr[pathArr.length - 1] - } - return { - documentDirectory, - documentFileName, - } -} - -// List documents -async function listDocuments(req, res) { - const { userWithSiteSessionData } = res.locals - const { siteName } = req.params - const { accessToken } = userWithSiteSessionData - - const IsomerFile = new File(accessToken, siteName) - const documentType = new DocumentType() - IsomerFile.setFileType(documentType) - const documents = await IsomerFile.list() - - return res.status(200).json({ documents }) -} - -// Create new document -async function createNewDocument(req, res) { - const { userWithSiteSessionData } = res.locals - const { accessToken } = userWithSiteSessionData - - const { siteName } = req.params - const { documentName, documentDirectory, content } = req.body - - // TO-DO: - // Validate fileName and content - - const IsomerDocumentFile = new MediaFile(accessToken, siteName) - IsomerDocumentFile.setFileTypeToDocument(documentDirectory) - const { sha } = await IsomerDocumentFile.create(documentName, content) - - return res.status(200).json({ documentName, content, sha }) -} - -// Read document -async function readDocument(req, res) { - const { userWithSiteSessionData } = res.locals - const { siteName, documentName } = req.params - const { accessToken } = userWithSiteSessionData - - // get document directory - const { documentDirectory, documentFileName } = extractDirectoryAndFileName( - documentName - ) - - const IsomerDocumentFile = new MediaFile(accessToken, siteName) - IsomerDocumentFile.setFileTypeToDocument(documentDirectory) - const { sha, content } = await IsomerDocumentFile.read(documentFileName) - - // TO-DO: - // Validate content - - return res.status(200).json({ documentName, sha, content }) -} - -// Update document -async function updateDocument(req, res) { - const { userWithSiteSessionData } = res.locals - const { accessToken } = userWithSiteSessionData - - const { siteName, documentName } = req.params - const { content, sha } = req.body - - // TO-DO: - // Validate pageName and content - - const IsomerFile = new File(accessToken, siteName) - const documentType = new DocumentType() - IsomerFile.setFileType(documentType) - const { newSha } = await IsomerFile.update(documentName, content, sha) - - return res.status(200).json({ documentName, content, sha: newSha }) -} - -// Delete document -async function deleteDocument(req, res) { - const { userWithSiteSessionData } = res.locals - const { accessToken } = userWithSiteSessionData - - const { siteName, documentName } = req.params - const { sha } = req.body - - const IsomerFile = new File(accessToken, siteName) - const documentType = new DocumentType() - IsomerFile.setFileType(documentType) - await IsomerFile.delete(documentName, sha) - - return res.status(200).send("OK") -} - -// Rename document -async function renameDocument(req, res) { - const { userWithSiteSessionData } = res.locals - const { accessToken } = userWithSiteSessionData - - const { siteName, documentName, newDocumentName } = req.params - - // TO-DO: - // Validate documentName and content - - const { - documentDirectory: oldDocumentDirectory, - documentFileName: oldDocumentFileName, - } = extractDirectoryAndFileName(documentName) - const { - documentDirectory: newDocumentDirectory, - documentFileName: newDocumentFileName, - } = extractDirectoryAndFileName(newDocumentName) - - const oldIsomerDocumentFile = new MediaFile(accessToken, siteName) - oldIsomerDocumentFile.setFileTypeToDocument(oldDocumentDirectory) - const { sha, content } = await oldIsomerDocumentFile.read(oldDocumentFileName) - await oldIsomerDocumentFile.delete(oldDocumentFileName, sha) - - const newIsomerDocumentFile = new MediaFile(accessToken, siteName) - newIsomerDocumentFile.setFileTypeToDocument(newDocumentDirectory) - await newIsomerDocumentFile.create(newDocumentFileName, content) - - return res.status(200).send("OK") -} - -// Move document -async function moveDocument(req, res) { - const { userWithSiteSessionData } = res.locals - const { accessToken } = userWithSiteSessionData - - const { siteName, documentName, newDocumentName } = req.params - - const { - documentDirectory: oldDocumentDirectory, - documentFileName: oldDocumentFileName, - } = extractDirectoryAndFileName(documentName) - const { - documentDirectory: newDocumentDirectory, - documentFileName: newDocumentFileName, - } = extractDirectoryAndFileName(newDocumentName) - - const oldIsomerDocumentFile = new MediaFile(accessToken, siteName) - oldIsomerDocumentFile.setFileTypeToDocument(oldDocumentDirectory) - const { sha, content } = await oldIsomerDocumentFile.read(oldDocumentFileName) - await oldIsomerDocumentFile.delete(oldDocumentFileName, sha) - - const newIsomerDocumentFile = new MediaFile(accessToken, siteName) - newIsomerDocumentFile.setFileTypeToDocument(newDocumentDirectory) - await newIsomerDocumentFile.create(newDocumentFileName, content) - - return res.status(200).send("OK") -} - -router.get( - "/", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "listDocuments"), - attachReadRouteHandlerWrapper(listDocuments) -) -router.post( - "/", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "createNewDocument"), - attachWriteRouteHandlerWrapper(createNewDocument) -) -router.get( - "/:documentName", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "readDocument"), - attachReadRouteHandlerWrapper(readDocument) -) -router.post( - "/:documentName", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "updateDocument"), - attachWriteRouteHandlerWrapper(updateDocument) -) -router.delete( - "/:documentName", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "deleteDocument"), - attachWriteRouteHandlerWrapper(deleteDocument) -) -router.post( - "/:documentName/rename/:newDocumentName", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "renameDocument"), - attachRollbackRouteHandlerWrapper(renameDocument) -) -router.post( - "/:documentName/move/:newDocumentName", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "moveDocument"), - attachRollbackRouteHandlerWrapper(moveDocument) -) - -module.exports = router diff --git a/src/routes/v1/authenticatedSites/folders.js b/src/routes/v1/authenticatedSites/folders.js deleted file mode 100644 index 027a7d35a..000000000 --- a/src/routes/v1/authenticatedSites/folders.js +++ /dev/null @@ -1,175 +0,0 @@ -import { Versions } from "@constants" - -import { statsMiddleware } from "@root/middleware/stats" - -const Bluebird = require("bluebird") -const express = require("express") -const { Base64 } = require("js-base64") - -const { - attachReadRouteHandlerWrapper, - attachRollbackRouteHandlerWrapper, -} = require("@middleware/routeHandler") - -// Import classes -const { Collection } = require("@classes/Collection") -const { CollectionConfig } = require("@classes/Config") -const { File, CollectionPageType } = require("@classes/File") - -const { getTree, sendTree, deslugifyCollectionName } = require("@utils/utils") -const { - sanitizedYamlParse, - sanitizedYamlStringify, -} = require("@utils/yaml-utils") - -const router = express.Router({ mergeParams: true }) - -// List pages and directories from all folders -async function listAllFolderContent(req, res) { - const { userWithSiteSessionData } = res.locals - const { siteName } = req.params - const { accessToken } = userWithSiteSessionData - - const IsomerCollection = new Collection(accessToken, siteName) - const allFolders = IsomerCollection.list() - - const allFolderContent = [] - - await Bluebird.map(allFolders, async (collectionName) => { - const config = new CollectionConfig(accessToken, siteName, collectionName) - const { sha, content } = await config.read() - allFolderContent.push({ name: collectionName, sha, content }) - }) - - return res.status(200).json({ allFolderContent }) -} - -// Delete subfolder -async function deleteSubfolder(req, res) { - const { userWithSiteSessionData, githubSessionData } = res.locals - const { siteName, folderName, subfolderName } = req.params - const { accessToken } = userWithSiteSessionData - const { currentCommitSha, treeSha } = githubSessionData.getGithubState() - - // Delete subfolder - const commitMessage = `Delete subfolder ${folderName}/${subfolderName}` - const isRecursive = true - const gitTree = await getTree(siteName, accessToken, treeSha, isRecursive) - const newGitTree = gitTree - .filter( - (item) => - item.type !== "tree" && - item.path.startsWith(`_${folderName}/${subfolderName}/`) - ) - .map((item) => ({ - ...item, - sha: null, - })) - await sendTree( - newGitTree, - treeSha, - currentCommitSha, - siteName, - accessToken, - commitMessage - ) - - // Update collection config - const collectionConfig = new CollectionConfig( - accessToken, - siteName, - folderName - ) - await collectionConfig.deleteSubfolderFromOrder(subfolderName) - - return res.status(200).send("OK") -} - -// Rename subfolder -async function renameSubfolder(req, res) { - const { userWithSiteSessionData } = res.locals - const { siteName, folderName, subfolderName, newSubfolderName } = req.params - const { accessToken } = userWithSiteSessionData - - // Rename subfolder by: - // 1. Creating new files in the newSubfolderName folder - // 2. Modifying the `third_nav_title` of each new file to reflect the newSubfolderName - // 3. Delete existing files in the previous subfolderName folder - const currentSubfolderPath = `${folderName}/${subfolderName}` - const CurrentIsomerFile = new File(accessToken, siteName) - const currDataType = new CollectionPageType(currentSubfolderPath) - CurrentIsomerFile.setFileType(currDataType) - - const newSubfolderPath = `${folderName}/${newSubfolderName}` - const NewIsomerFile = new File(accessToken, siteName) - const newDataType = new CollectionPageType(newSubfolderPath) - NewIsomerFile.setFileType(newDataType) - - const filesToBeModified = await CurrentIsomerFile.list() - - await Bluebird.mapSeries(filesToBeModified, async (fileInfo) => { - const { fileName } = fileInfo - - // Read existing file content - const { content, sha } = await CurrentIsomerFile.read(fileName) - - // Handle keep file differently - if (fileName === ".keep") { - await NewIsomerFile.create(fileName, content) - return CurrentIsomerFile.delete(fileName, sha) - } - - const decodedContent = Base64.decode(content) - const results = decodedContent.split("---") - const frontMatter = sanitizedYamlParse(results[1]) // get the front matter as an object - const mdBody = results.slice(2).join("---") - - // Modify `third_nav_title` and save as new file in newSubfolderName - const newFrontMatter = { - ...frontMatter, - third_nav_title: deslugifyCollectionName(newSubfolderName), - } - - const newContent = [ - "---\n", - sanitizedYamlStringify(newFrontMatter), - "---\n", - mdBody, - ].join("") - - const encodedNewContent = Base64.encode(newContent) - - await NewIsomerFile.create(fileName, encodedNewContent) - - // Delete existing file in subfolderName - return CurrentIsomerFile.delete(fileName, sha) - }) - - // // Update collection config - const collectionConfig = new CollectionConfig( - accessToken, - siteName, - folderName - ) - await collectionConfig.renameSubfolderInOrder(subfolderName, newSubfolderName) - - return res.status(200).send("OK") -} - -router.get( - "/all", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "listAllFolderContent"), - attachReadRouteHandlerWrapper(listAllFolderContent) -) -router.delete( - "/:folderName/subfolder/:subfolderName", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "deleteSubfolder"), - attachRollbackRouteHandlerWrapper(deleteSubfolder) -) -router.post( - "/:folderName/subfolder/:subfolderName/rename/:newSubfolderName", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "renameSubfolder"), - attachRollbackRouteHandlerWrapper(renameSubfolder) -) - -module.exports = router diff --git a/src/routes/v1/authenticatedSites/homepage.js b/src/routes/v1/authenticatedSites/homepage.js deleted file mode 100644 index 170f4be9f..000000000 --- a/src/routes/v1/authenticatedSites/homepage.js +++ /dev/null @@ -1,77 +0,0 @@ -import { Versions } from "@constants" - -import { statsMiddleware } from "@root/middleware/stats" - -const express = require("express") -const { Base64 } = require("js-base64") - -const router = express.Router({ mergeParams: true }) - -// Import middleware -const { - attachReadRouteHandlerWrapper, - attachWriteRouteHandlerWrapper, -} = require("@middleware/routeHandler") - -// Import classes -const { File, HomepageType } = require("@classes/File") - -// Constants -const { HOMEPAGE_FILENAME } = require("@root/constants") - -// Read homepage index file -async function readHomepage(req, res) { - const { userWithSiteSessionData } = res.locals - const { accessToken } = userWithSiteSessionData - - const { siteName } = req.params - - const IsomerFile = new File(accessToken, siteName) - const homepageType = new HomepageType() - IsomerFile.setFileType(homepageType) - const { sha, content: encodedContent } = await IsomerFile.read( - HOMEPAGE_FILENAME - ) - const content = Base64.decode(encodedContent) - - // TO-DO: - // Validate content - - return res.status(200).json({ content, sha }) -} - -// Update homepage index file -async function updateHomepage(req, res) { - const { userWithSiteSessionData } = res.locals - const { accessToken } = userWithSiteSessionData - - const { siteName } = req.params - const { content, sha } = req.body - - // TO-DO: - // Validate content - - const IsomerFile = new File(accessToken, siteName) - const homepageType = new HomepageType() - IsomerFile.setFileType(homepageType) - const { newSha } = await IsomerFile.update( - HOMEPAGE_FILENAME, - Base64.encode(content), - sha - ) - - return res.status(200).json({ content, sha: newSha }) -} - -router.get( - "/", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "readHomepage"), - attachReadRouteHandlerWrapper(readHomepage) -) -router.post( - "/", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "updateHomepage"), - attachWriteRouteHandlerWrapper(updateHomepage) -) - -module.exports = router diff --git a/src/routes/v1/authenticatedSites/images.js b/src/routes/v1/authenticatedSites/images.js deleted file mode 100644 index 166ad3baa..000000000 --- a/src/routes/v1/authenticatedSites/images.js +++ /dev/null @@ -1,225 +0,0 @@ -import { Versions } from "@constants" - -import { statsMiddleware } from "@root/middleware/stats" - -const express = require("express") - -const router = express.Router({ mergeParams: true }) - -// Import middleware -const { - attachReadRouteHandlerWrapper, - attachWriteRouteHandlerWrapper, - attachRollbackRouteHandlerWrapper, -} = require("@middleware/routeHandler") - -// Import classes -const { File, ImageType } = require("@classes/File") -const { MediaFile } = require("@classes/MediaFile") - -const extractDirectoryAndFileName = (imageName) => { - let imageDirectory - let imageFileName - - // imageName contains the file path excluding the media folder, e.g. subfolder1/subfolder2/image.png - const pathArr = imageName.split("/") - if (pathArr.length === 1) { - // imageName only contains the file name - imageDirectory = "images" - imageFileName = imageName - } else if (pathArr.length > 1) { - // We discard the name of the image for the directory - imageDirectory = `images/${pathArr.slice(0, -1).join("/")}` - imageFileName = pathArr[pathArr.length - 1] - } - return { - imageDirectory, - imageFileName, - } -} - -// List images -async function listImages(req, res) { - const { userWithSiteSessionData } = res.locals - const { siteName } = req.params - const { accessToken } = userWithSiteSessionData - - const IsomerFile = new File(accessToken, siteName) - const imageType = new ImageType() - IsomerFile.setFileType(imageType) - const images = await IsomerFile.list() - - return res.status(200).json({ images }) -} - -// Create new image -async function createNewImage(req, res) { - const { userWithSiteSessionData } = res.locals - const { accessToken } = userWithSiteSessionData - - const { siteName } = req.params - const { imageName, imageDirectory, content } = req.body - - // TO-DO: - // Validate imageName and content - - const IsomerImageFile = new MediaFile(accessToken, siteName) - IsomerImageFile.setFileTypeToImage(imageDirectory) - const { sha } = await IsomerImageFile.create(imageName, content) - - return res.status(200).json({ imageName, content, sha }) -} - -// Read image -async function readImage(req, res) { - const { userWithSiteSessionData } = res.locals - const { accessToken } = userWithSiteSessionData - - const { siteName, imageName } = req.params - - // get image directory - const { imageDirectory, imageFileName } = extractDirectoryAndFileName( - imageName - ) - - const IsomerImageFile = new MediaFile(accessToken, siteName) - IsomerImageFile.setFileTypeToImage(imageDirectory) - - const { sha, content } = await IsomerImageFile.read(imageFileName) - - // TO-DO: - // Validate content - - return res.status(200).json({ imageName, sha, content }) -} - -// Update image -async function updateImage(req, res) { - const { userWithSiteSessionData } = res.locals - const { accessToken } = userWithSiteSessionData - - const { siteName, imageName } = req.params - const { content, sha } = req.body - - // TO-DO: - // Validate imageName and content - - const IsomerFile = new File(accessToken, siteName) - const imageType = new ImageType() - IsomerFile.setFileType(imageType) - const { newSha } = await IsomerFile.update(imageName, content, sha) - - return res.status(200).json({ imageName, content, sha: newSha }) -} - -// Delete image -async function deleteImage(req, res) { - const { userWithSiteSessionData } = res.locals - const { accessToken } = userWithSiteSessionData - - const { siteName, imageName } = req.params - const { sha } = req.body - - const IsomerFile = new File(accessToken, siteName) - const imageType = new ImageType() - IsomerFile.setFileType(imageType) - await IsomerFile.delete(imageName, sha) - - return res.status(200).send("OK") -} - -// Rename image -async function renameImage(req, res) { - const { userWithSiteSessionData } = res.locals - const { accessToken } = userWithSiteSessionData - - const { siteName, imageName, newImageName } = req.params - - // Create new file with name ${newImageName} - - const { - imageDirectory: oldImageDirectory, - imageFileName: oldImageFileName, - } = extractDirectoryAndFileName(imageName) - const { - imageDirectory: newImageDirectory, - imageFileName: newImageFileName, - } = extractDirectoryAndFileName(newImageName) - - const oldIsomerImageFile = new MediaFile(accessToken, siteName) - oldIsomerImageFile.setFileTypeToImage(oldImageDirectory) - const { sha, content } = await oldIsomerImageFile.read(oldImageFileName) - await oldIsomerImageFile.delete(oldImageFileName, sha) - - const newIsomerImageFile = new MediaFile(accessToken, siteName) - newIsomerImageFile.setFileTypeToImage(newImageDirectory) - await newIsomerImageFile.create(newImageFileName, content) - - return res.status(200).send("OK") -} - -// Move image -async function moveImage(req, res) { - const { userWithSiteSessionData } = res.locals - const { accessToken } = userWithSiteSessionData - - const { siteName, imageName, newImageName } = req.params - - const { - imageDirectory: oldImageDirectory, - imageFileName: oldImageFileName, - } = extractDirectoryAndFileName(imageName) - const { - imageDirectory: newImageDirectory, - imageFileName: newImageFileName, - } = extractDirectoryAndFileName(newImageName) - - const oldIsomerImageFile = new MediaFile(accessToken, siteName) - oldIsomerImageFile.setFileTypeToImage(oldImageDirectory) - const { sha, content } = await oldIsomerImageFile.read(oldImageFileName) - await oldIsomerImageFile.delete(oldImageFileName, sha) - - const newIsomerImageFile = new MediaFile(accessToken, siteName) - newIsomerImageFile.setFileTypeToImage(newImageDirectory) - await newIsomerImageFile.create(newImageFileName, content) - - return res.status(200).send("OK") -} - -router.get( - "/", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "listImages"), - attachReadRouteHandlerWrapper(listImages) -) -router.post( - "/", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "createNewImage"), - attachWriteRouteHandlerWrapper(createNewImage) -) -router.get( - "/:imageName", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "readImage"), - attachReadRouteHandlerWrapper(readImage) -) -router.post( - "/:imageName", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "updateImage"), - attachWriteRouteHandlerWrapper(updateImage) -) -router.delete( - "/:imageName", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "deleteImage"), - attachWriteRouteHandlerWrapper(deleteImage) -) -router.post( - "/:imageName/rename/:newImageName", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "renameImage"), - attachRollbackRouteHandlerWrapper(renameImage) -) -router.post( - "/:imageName/move/:newImageName", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "moveImage"), - attachRollbackRouteHandlerWrapper(moveImage) -) - -module.exports = router diff --git a/src/routes/v1/authenticatedSites/index.js b/src/routes/v1/authenticatedSites/index.js deleted file mode 100644 index 6ed57abb1..000000000 --- a/src/routes/v1/authenticatedSites/index.js +++ /dev/null @@ -1,60 +0,0 @@ -import { attachSiteHandler } from "@root/middleware" - -const express = require("express") - -const collectionPagesRouter = require("@routes/v1/authenticatedSites/collectionPages") -const collectionsRouter = require("@routes/v1/authenticatedSites/collections") -const directoryRouter = require("@routes/v1/authenticatedSites/directory") -const documentsRouter = require("@routes/v1/authenticatedSites/documents") -const foldersRouter = require("@routes/v1/authenticatedSites/folders") -const homepageRouter = require("@routes/v1/authenticatedSites/homepage") -const imagesRouter = require("@routes/v1/authenticatedSites/images") -const mediaSubfolderRouter = require("@routes/v1/authenticatedSites/mediaSubfolder") -const navigationRouter = require("@routes/v1/authenticatedSites/navigation") -const netlifyTomlRouter = require("@routes/v1/authenticatedSites/netlifyToml") -const pagesRouter = require("@routes/v1/authenticatedSites/pages") -const resourcePagesRouter = require("@routes/v1/authenticatedSites/resourcePages") -const resourceRoomRouter = require("@routes/v1/authenticatedSites/resourceRoom") -const resourcesRouter = require("@routes/v1/authenticatedSites/resources") -const settingsRouter = require("@routes/v1/authenticatedSites/settings") - -const getAuthenticatedSitesSubrouter = ({ - authenticationMiddleware, - authorizationMiddleware, - apiLogger, -}) => { - const authenticatedSitesSubrouter = express.Router({ mergeParams: true }) - - authenticatedSitesSubrouter.use(authenticationMiddleware.verifyAccess) - authenticatedSitesSubrouter.use(attachSiteHandler) - // NOTE: apiLogger needs to be after `verifyJwt` as it logs the github username - // which is only available after verifying that the jwt is valid - authenticatedSitesSubrouter.use(apiLogger) - authenticatedSitesSubrouter.use(authorizationMiddleware.verifySiteMember) - - authenticatedSitesSubrouter.use("/pages", pagesRouter) - authenticatedSitesSubrouter.use("/collections", collectionsRouter) - authenticatedSitesSubrouter.use( - "/collections/:collectionName", - collectionPagesRouter - ) - authenticatedSitesSubrouter.use("/files", directoryRouter) - authenticatedSitesSubrouter.use("/folders", foldersRouter) - authenticatedSitesSubrouter.use("/resource-room", resourceRoomRouter) - authenticatedSitesSubrouter.use("/resources", resourcesRouter) - authenticatedSitesSubrouter.use( - "/resources/:resourceName", - resourcePagesRouter - ) - authenticatedSitesSubrouter.use("/images", imagesRouter) - authenticatedSitesSubrouter.use("/documents", documentsRouter) - authenticatedSitesSubrouter.use("/media/:mediaType", mediaSubfolderRouter) - authenticatedSitesSubrouter.use("/homepage", homepageRouter) - authenticatedSitesSubrouter.use("/settings", settingsRouter) - authenticatedSitesSubrouter.use("/navigation", navigationRouter) - authenticatedSitesSubrouter.use("/netlify-toml", netlifyTomlRouter) - - return authenticatedSitesSubrouter -} - -export default getAuthenticatedSitesSubrouter diff --git a/src/routes/v1/authenticatedSites/mediaSubfolder.js b/src/routes/v1/authenticatedSites/mediaSubfolder.js deleted file mode 100644 index 7b7d32b9a..000000000 --- a/src/routes/v1/authenticatedSites/mediaSubfolder.js +++ /dev/null @@ -1,100 +0,0 @@ -import { Versions } from "@constants" - -import { statsMiddleware } from "@root/middleware/stats" - -const express = require("express") - -const router = express.Router({ mergeParams: true }) - -// Import middleware -const { - attachWriteRouteHandlerWrapper, - attachRollbackRouteHandlerWrapper, -} = require("@middleware/routeHandler") - -// Import classes -const { MediaSubfolder } = require("@classes/MediaSubfolder") - -// Create new collection -async function createSubfolder(req, res) { - const { userWithSiteSessionData } = res.locals - const { siteName, mediaType, folderPath } = req.params - const { accessToken } = userWithSiteSessionData - - const processedFolderPath = decodeURIComponent(folderPath) - - const IsomerMediaSubfolder = new MediaSubfolder( - accessToken, - siteName, - mediaType - ) - await IsomerMediaSubfolder.create(processedFolderPath) - - return res.status(200).send("OK") -} - -// Delete collection -async function deleteSubfolder(req, res) { - const { userWithSiteSessionData } = res.locals - const { siteName, mediaType, folderPath } = req.params - const { accessToken } = userWithSiteSessionData - const { currentCommitSha, treeSha } = userWithSiteSessionData.getGithubState() - - const processedFolderPath = decodeURIComponent(folderPath) - - const IsomerMediaSubfolder = new MediaSubfolder( - accessToken, - siteName, - mediaType - ) - await IsomerMediaSubfolder.delete( - processedFolderPath, - currentCommitSha, - treeSha - ) - - return res.status(200).send("OK") -} - -// Rename collection -async function renameSubfolder(req, res) { - const { userWithSiteSessionData } = res.locals - const { siteName, mediaType, oldFolderPath, newFolderPath } = req.params - const { accessToken } = userWithSiteSessionData - const { currentCommitSha, treeSha } = userWithSiteSessionData.getGithubState() - - const processedOldFolderPath = decodeURIComponent(oldFolderPath) - const processedNewFolderPath = decodeURIComponent(newFolderPath) - - const IsomerMediaSubfolder = new MediaSubfolder( - accessToken, - siteName, - mediaType - ) - await IsomerMediaSubfolder.rename( - processedOldFolderPath, - processedNewFolderPath, - currentCommitSha, - treeSha - ) - - return res.status(200).send("OK") -} - -router.post( - "/:folderPath", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "createSubfolder"), - attachWriteRouteHandlerWrapper(createSubfolder) -) -router.delete( - "/:folderPath", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "deleteSubfolder"), - attachRollbackRouteHandlerWrapper(deleteSubfolder) -) -router.post( - "/:oldFolderPath/rename/:newFolderPath", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "renameSubfolder"), - attachRollbackRouteHandlerWrapper(renameSubfolder) -) - -module.exports = router diff --git a/src/routes/v1/authenticatedSites/navigation.js b/src/routes/v1/authenticatedSites/navigation.js deleted file mode 100644 index 2a11561a5..000000000 --- a/src/routes/v1/authenticatedSites/navigation.js +++ /dev/null @@ -1,73 +0,0 @@ -import { Versions } from "@constants" - -import { statsMiddleware } from "@root/middleware/stats" - -const express = require("express") -const { Base64 } = require("js-base64") - -const { - sanitizedYamlParse, - sanitizedYamlStringify, -} = require("@utils/yaml-utils") - -const router = express.Router({ mergeParams: true }) - -// Import middleware -const { - attachReadRouteHandlerWrapper, - attachWriteRouteHandlerWrapper, -} = require("@middleware/routeHandler") - -// Import Classes -const { File, DataType } = require("@classes/File") - -const NAVIGATION_PATH = "navigation.yml" - -async function getNavigation(req, res) { - const { userWithSiteSessionData } = res.locals - const { accessToken } = userWithSiteSessionData - - const { siteName } = req.params - - const IsomerFile = new File(accessToken, siteName) - const dataType = new DataType() - IsomerFile.setFileType(dataType) - const { content, sha } = await IsomerFile.read(NAVIGATION_PATH) - - return res.status(200).json({ - sha, - content: sanitizedYamlParse(Base64.decode(content)), - }) -} - -async function updateNavigation(req, res) { - const { userWithSiteSessionData } = res.locals - const { siteName } = req.params - const { accessToken } = userWithSiteSessionData - - const { content, sha } = req.body - - const IsomerFile = new File(accessToken, siteName) - const dataType = new DataType() - IsomerFile.setFileType(dataType) - await IsomerFile.update( - NAVIGATION_PATH, - Base64.encode(sanitizedYamlStringify(content)), - sha - ) - - return res.status(200).send("OK") -} - -router.get( - "/", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "getNavigation"), - attachReadRouteHandlerWrapper(getNavigation) -) -router.post( - "/", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "updateNavigation"), - attachWriteRouteHandlerWrapper(updateNavigation) -) - -module.exports = router diff --git a/src/routes/v1/authenticatedSites/netlifyToml.js b/src/routes/v1/authenticatedSites/netlifyToml.js deleted file mode 100644 index 7ddb0b75d..000000000 --- a/src/routes/v1/authenticatedSites/netlifyToml.js +++ /dev/null @@ -1,43 +0,0 @@ -import { Versions } from "@constants" - -import { statsMiddleware } from "@root/middleware/stats" - -const express = require("express") -const { Base64 } = require("js-base64") -const toml = require("toml") - -// Import middleware -const { attachReadRouteHandlerWrapper } = require("@middleware/routeHandler") - -// Import classes -const { NetlifyToml } = require("@classes/NetlifyToml") - -const router = express.Router({ mergeParams: true }) - -// List resources -async function getNetlifyToml(req, res) { - const { userWithSiteSessionData } = res.locals - const { siteName } = req.params - const { accessToken } = userWithSiteSessionData - - const netlifyTomlFile = new NetlifyToml(accessToken, siteName) - - const { content } = await netlifyTomlFile.read() - - // Convert to readable form - const netlifyTomlReadableContent = toml.parse(Base64.decode(content)) - - // Headers is an array of objects, specifying a set of access rules for each specified path - // Under our current assumption, we apply the first set of access rules to all paths - const netlifyTomlHeaderValues = netlifyTomlReadableContent.headers[0].values - - return res.status(200).json({ netlifyTomlHeaderValues }) -} - -router.get( - "/", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "getNetlifyToml"), - attachReadRouteHandlerWrapper(getNetlifyToml) -) - -module.exports = router diff --git a/src/routes/v1/authenticatedSites/pages.js b/src/routes/v1/authenticatedSites/pages.js deleted file mode 100644 index 8b8e5bdac..000000000 --- a/src/routes/v1/authenticatedSites/pages.js +++ /dev/null @@ -1,262 +0,0 @@ -import { Versions } from "@constants" - -const express = require("express") -const { Base64 } = require("js-base64") - -// Import middleware -const { - attachReadRouteHandlerWrapper, - attachWriteRouteHandlerWrapper, - attachRollbackRouteHandlerWrapper, -} = require("@middleware/routeHandler") - -// Import classes -const { Collection } = require("@classes/Collection") -const { CollectionConfig } = require("@classes/Config") -const { File, PageType, CollectionPageType } = require("@classes/File") -const { Subfolder } = require("@classes/Subfolder") - -const { deslugifyCollectionName } = require("@utils/utils") -const { - sanitizedYamlParse, - sanitizedYamlStringify, -} = require("@utils/yaml-utils") - -const { statsMiddleware } = require("@root/middleware/stats") - -const router = express.Router({ mergeParams: true }) - -async function listPages(req, res) { - const { userWithSiteSessionData } = res.locals - const { siteName } = req.params - const { accessToken } = userWithSiteSessionData - - const IsomerFile = new File(accessToken, siteName) - const pageType = new PageType() - IsomerFile.setFileType(pageType) - const simplePages = await IsomerFile.list() - - return res.status(200).json({ pages: simplePages }) -} - -async function createPage(req, res) { - const { userWithSiteSessionData } = res.locals - const { accessToken } = userWithSiteSessionData - - const { siteName, pageName: encodedPageName } = req.params - const { content: pageContent } = req.body - const pageName = decodeURIComponent(encodedPageName) - - const IsomerFile = new File(accessToken, siteName) - const pageType = new PageType() - IsomerFile.setFileType(pageType) - await IsomerFile.create(pageName, Base64.encode(pageContent)) - - return res.status(200).json({ pageName, pageContent }) -} - -// Read page -async function readPage(req, res) { - const { userWithSiteSessionData } = res.locals - const { accessToken } = userWithSiteSessionData - - const { siteName, pageName: encodedPageName } = req.params - const pageName = decodeURIComponent(encodedPageName) - - const IsomerFile = new File(accessToken, siteName) - const pageType = new PageType() - IsomerFile.setFileType(pageType) - const { sha, content: encodedContent } = await IsomerFile.read(pageName) - - const content = Base64.decode(encodedContent) - - // TO-DO: - // Validate content - - return res.status(200).json({ pageName, sha, content }) -} - -// Update page -async function updatePage(req, res) { - const { userWithSiteSessionData } = res.locals - const { accessToken } = userWithSiteSessionData - - const { siteName, pageName: encodedPageName } = req.params - const { content: pageContent, sha } = req.body - const pageName = decodeURIComponent(encodedPageName) - - // TO-DO: - // Validate pageName and content - - const IsomerFile = new File(accessToken, siteName) - const pageType = new PageType() - IsomerFile.setFileType(pageType) - const { newSha } = await IsomerFile.update( - pageName, - Base64.encode(pageContent), - sha - ) - - return res.status(200).json({ pageName, pageContent, sha: newSha }) -} - -// Delete page -async function deletePage(req, res) { - const { userWithSiteSessionData } = res.locals - const { accessToken } = userWithSiteSessionData - - const { siteName, pageName: encodedPageName } = req.params - const { sha } = req.body - const pageName = decodeURIComponent(encodedPageName) - - const IsomerFile = new File(accessToken, siteName) - const pageType = new PageType() - IsomerFile.setFileType(pageType) - await IsomerFile.delete(pageName, sha) - - return res.status(200).send("OK") -} - -// Rename page -async function renamePage(req, res) { - const { userWithSiteSessionData } = res.locals - const { accessToken } = userWithSiteSessionData - - const { - siteName, - pageName: encodedPageName, - newPageName: encodedNewPageName, - } = req.params - const { sha, content: pageContent } = req.body - - // TO-DO: - // Validate pageName and content - const pageName = decodeURIComponent(encodedPageName) - const newPageName = decodeURIComponent(encodedNewPageName) - - const IsomerFile = new File(accessToken, siteName) - const pageType = new PageType() - IsomerFile.setFileType(pageType) - const { sha: newSha } = await IsomerFile.create( - newPageName, - Base64.encode(pageContent) - ) - await IsomerFile.delete(pageName, sha) - - return res - .status(200) - .json({ pageName: newPageName, pageContent, sha: newSha }) -} - -// Move unlinked pages -async function moveUnlinkedPages(req, res) { - const { userWithSiteSessionData } = res.locals - const { accessToken } = userWithSiteSessionData - const { siteName, newPagePath } = req.params - const { files } = req.body - const processedTargetPathTokens = decodeURIComponent(newPagePath).split("/") - const targetCollectionName = processedTargetPathTokens[0] - const targetSubfolderName = processedTargetPathTokens[1] - - const IsomerCollection = new Collection(accessToken, siteName) - const collections = await IsomerCollection.list() - - // Check if collection already exists - if (!collections.includes(targetCollectionName)) { - await IsomerCollection.create(targetCollectionName) - } - - const oldIsomerFile = new File(accessToken, siteName) - const newIsomerFile = new File(accessToken, siteName) - const oldPageType = new PageType() - const newCollectionPageType = new CollectionPageType( - decodeURIComponent(newPagePath) - ) - oldIsomerFile.setFileType(oldPageType) - newIsomerFile.setFileType(newCollectionPageType) - const newConfig = new CollectionConfig( - accessToken, - siteName, - targetCollectionName - ) - - if (newConfig && targetSubfolderName) { - // Check if subfolder exists - const IsomerSubfolder = new Subfolder( - accessToken, - siteName, - targetCollectionName - ) - const subfolders = await IsomerSubfolder.list() - if (!subfolders.includes(targetSubfolderName)) - await IsomerSubfolder.create(targetSubfolderName) - } - - // To fix after refactoring - /* eslint-disable no-await-in-loop, no-restricted-syntax */ - // We can't perform these operations concurrently because of conflict issues - for (const fileName of files) { - const { content, sha } = await oldIsomerFile.read(fileName) - await oldIsomerFile.delete(fileName, sha) - if (targetSubfolderName) { - // Adding third nav to front matter, to be removed after template rewrite - - // eslint-disable-next-line no-unused-vars - const [unused, encodedFrontMatter, pageContent] = Base64.decode( - content - ).split("---") - const frontMatter = sanitizedYamlParse(encodedFrontMatter) - frontMatter.third_nav_title = deslugifyCollectionName(targetSubfolderName) - const newFrontMatter = sanitizedYamlStringify(frontMatter) - const newContent = ["---\n", newFrontMatter, "---", pageContent].join("") - const newEncodedContent = Base64.encode(newContent) - await newIsomerFile.create(fileName, newEncodedContent) - } else { - await newIsomerFile.create(fileName, content) - } - // Update collection.yml files - await newConfig.addItemToOrder( - `${targetSubfolderName ? `${targetSubfolderName}/` : ""}${fileName}` - ) - } - - return res.status(200).send("OK") -} - -router.get( - "/", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "listPages"), - attachReadRouteHandlerWrapper(listPages) -) -router.post( - "/new/:pageName", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "createPage"), - attachWriteRouteHandlerWrapper(createPage) -) -router.get( - "/:pageName", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "readPage"), - attachReadRouteHandlerWrapper(readPage) -) -router.post( - "/:pageName", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "updatePage"), - attachWriteRouteHandlerWrapper(updatePage) -) -router.delete( - "/:pageName", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "deletePage"), - attachWriteRouteHandlerWrapper(deletePage) -) -router.post( - "/:pageName/rename/:newPageName", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "renamePage"), - attachRollbackRouteHandlerWrapper(renamePage) -) -router.post( - "/move/:newPagePath", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "moveUnlinkedPages"), - attachRollbackRouteHandlerWrapper(moveUnlinkedPages) -) - -module.exports = router diff --git a/src/routes/v1/authenticatedSites/resourcePages.js b/src/routes/v1/authenticatedSites/resourcePages.js deleted file mode 100644 index d20904a70..000000000 --- a/src/routes/v1/authenticatedSites/resourcePages.js +++ /dev/null @@ -1,211 +0,0 @@ -import { Versions } from "@constants" - -import { statsMiddleware } from "@root/middleware/stats" - -const express = require("express") -const { Base64 } = require("js-base64") - -const router = express.Router({ mergeParams: true }) - -// Import middleware -const { NotFoundError } = require("@errors/NotFoundError") - -const { - attachReadRouteHandlerWrapper, - attachWriteRouteHandlerWrapper, - attachRollbackRouteHandlerWrapper, -} = require("@middleware/routeHandler") - -// Import classes -const { File, ResourcePageType } = require("@classes/File") -const { Resource } = require("@classes/Resource") -const { ResourceRoom } = require("@classes/ResourceRoom") - -// List pages in resource -async function listResourcePages(req, res) { - const { userWithSiteSessionData } = res.locals - const { accessToken } = userWithSiteSessionData - const { siteName, resourceName } = req.params - - const ResourceRoomInstance = new ResourceRoom(accessToken, siteName) - const resourceRoomName = await ResourceRoomInstance.get() - - // Check if resource category exists - const IsomerResource = new Resource(accessToken, siteName) - const resources = await IsomerResource.list(resourceRoomName) - const resourceCategories = resources.map((resource) => resource.dirName) - if (!resourceCategories.includes(resourceName)) - throw new NotFoundError(`Resource category ${resourceName} was not found!`) - - const IsomerFile = new File(accessToken, siteName) - const resourcePageType = new ResourcePageType(resourceRoomName, resourceName) - IsomerFile.setFileType(resourcePageType) - let resourcePages = [] - try { - resourcePages = await IsomerFile.list() - } catch (error) { - if (!(error instanceof NotFoundError)) throw error - } - return res.status(200).json({ resourcePages }) -} - -// Create new page in resource -async function createNewResourcePage(req, res) { - const { userWithSiteSessionData } = res.locals - const { accessToken } = userWithSiteSessionData - - const { siteName, resourceName, pageName } = req.params - const { content: pageContent } = req.body - - // TO-DO: - // Validate pageName and content - const ResourceRoomInstance = new ResourceRoom(accessToken, siteName) - const resourceRoomName = await ResourceRoomInstance.get() - - // Check if resource category exists and create if it does not - const IsomerResource = new Resource(accessToken, siteName) - const resources = await IsomerResource.list(resourceRoomName) - const resourceCategories = resources.map((resource) => resource.dirName) - if (!resourceCategories.includes(resourceName)) { - await IsomerResource.create(resourceRoomName, resourceName) - } - - const IsomerFile = new File(accessToken, siteName) - const resourcePageType = new ResourcePageType(resourceRoomName, resourceName) - IsomerFile.setFileType(resourcePageType) - - const { sha } = await IsomerFile.create(pageName, Base64.encode(pageContent)) - - return res.status(200).json({ resourceName, pageName, pageContent, sha }) -} - -// Read page in resource -async function readResourcePage(req, res) { - const { userWithSiteSessionData } = res.locals - const { accessToken } = userWithSiteSessionData - - const { siteName, pageName, resourceName } = req.params - - const ResourceRoomInstance = new ResourceRoom(accessToken, siteName) - const resourceRoomName = await ResourceRoomInstance.get() - const IsomerFile = new File(accessToken, siteName) - const resourcePageType = new ResourcePageType(resourceRoomName, resourceName) - IsomerFile.setFileType(resourcePageType) - const { sha, content: encodedContent } = await IsomerFile.read(pageName) - const content = Base64.decode(encodedContent) - - // TO-DO: - // Validate content - - return res - .status(200) - .json({ resourceRoomName, resourceName, pageName, sha, content }) -} - -// Update page in resource -async function updateResourcePage(req, res) { - const { userWithSiteSessionData } = res.locals - const { accessToken } = userWithSiteSessionData - - const { siteName, pageName, resourceName } = req.params - const { content: pageContent, sha } = req.body - - // TO-DO: - // Validate pageName and content - - const ResourceRoomInstance = new ResourceRoom(accessToken, siteName) - const resourceRoomName = await ResourceRoomInstance.get() - const IsomerFile = new File(accessToken, siteName) - const resourcePageType = new ResourcePageType(resourceRoomName, resourceName) - IsomerFile.setFileType(resourcePageType) - const { newSha } = await IsomerFile.update( - pageName, - Base64.encode(pageContent), - sha - ) - - return res - .status(200) - .json({ resourceName, pageName, pageContent, sha: newSha }) -} - -// Delete page in resource -async function deleteResourcePage(req, res) { - const { userWithSiteSessionData } = res.locals - const { accessToken } = userWithSiteSessionData - - const { siteName, pageName, resourceName } = req.params - const { sha } = req.body - - const ResourceRoomInstance = new ResourceRoom(accessToken, siteName) - const resourceRoomName = await ResourceRoomInstance.get() - const IsomerFile = new File(accessToken, siteName) - const resourcePageType = new ResourcePageType(resourceRoomName, resourceName) - IsomerFile.setFileType(resourcePageType) - await IsomerFile.delete(pageName, sha) - - return res.status(200).send("OK") -} - -// Rename page in resource -async function renameResourcePage(req, res) { - const { userWithSiteSessionData } = res.locals - const { accessToken } = userWithSiteSessionData - - const { siteName, pageName, resourceName, newPageName } = req.params - const { sha, content: pageContent } = req.body - - // TO-DO: - // Validate that resource exists - // Validate pageName and content - - // Create new resource page with name ${newPageName} - - const ResourceRoomInstance = new ResourceRoom(accessToken, siteName) - const resourceRoomName = await ResourceRoomInstance.get() - const IsomerFile = new File(accessToken, siteName) - const resourcePageType = new ResourcePageType(resourceRoomName, resourceName) - IsomerFile.setFileType(resourcePageType) - const { sha: newSha } = await IsomerFile.create( - newPageName, - Base64.encode(pageContent) - ) - await IsomerFile.delete(pageName, sha) - - return res - .status(200) - .json({ resourceName, pageName: newPageName, pageContent, sha: newSha }) -} - -router.get( - "/", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "listResourcePages"), - attachReadRouteHandlerWrapper(listResourcePages) -) -router.post( - "/pages/new/:pageName", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "createNewResourcePage"), - attachRollbackRouteHandlerWrapper(createNewResourcePage) -) -router.get( - "/pages/:pageName", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "readResourcePage"), - attachReadRouteHandlerWrapper(readResourcePage) -) -router.post( - "/pages/:pageName", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "updateResourcePage"), - attachWriteRouteHandlerWrapper(updateResourcePage) -) -router.delete( - "/pages/:pageName", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "deleteResourcePage"), - attachRollbackRouteHandlerWrapper(deleteResourcePage) -) -router.post( - "/pages/:pageName/rename/:newPageName", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "renameResourcePage"), - attachRollbackRouteHandlerWrapper(renameResourcePage) -) - -module.exports = router diff --git a/src/routes/v1/authenticatedSites/resourceRoom.js b/src/routes/v1/authenticatedSites/resourceRoom.js deleted file mode 100644 index d66fd0755..000000000 --- a/src/routes/v1/authenticatedSites/resourceRoom.js +++ /dev/null @@ -1,93 +0,0 @@ -import { Versions } from "@constants" - -import { statsMiddleware } from "@root/middleware/stats" - -const express = require("express") - -const router = express.Router({ mergeParams: true }) - -// Import classes -const { - attachReadRouteHandlerWrapper, - attachRollbackRouteHandlerWrapper, -} = require("@middleware/routeHandler") - -const { ResourceRoom } = require("@classes/ResourceRoom") - -// Get resource room name -async function getResourceRoomName(req, res) { - const { userWithSiteSessionData } = res.locals - const { siteName } = req.params - const { accessToken } = userWithSiteSessionData - - const IsomerResourceRoom = new ResourceRoom(accessToken, siteName) - const resourceRoom = await IsomerResourceRoom.get() - - return res.status(200).json({ resourceRoom }) -} - -// Create resource room -async function createResourceRoom(req, res) { - const { userWithSiteSessionData } = res.locals - const { siteName } = req.params - const { resourceRoom } = req.body - const { accessToken } = userWithSiteSessionData - - // TO-DO: - // Validate resourceRoom - - const IsomerResourceRoom = new ResourceRoom(accessToken, siteName) - await IsomerResourceRoom.create(resourceRoom) - - return res.status(200).json({ resourceRoom }) -} - -// Rename resource room name -async function renameResourceRoom(req, res) { - const { userWithSiteSessionData } = res.locals - const { siteName, resourceRoom } = req.params - const { accessToken } = userWithSiteSessionData - - // TO-DO: - // Validate resourceRoom - - const IsomerResourceRoom = new ResourceRoom(accessToken, siteName) - await IsomerResourceRoom.rename(resourceRoom) - - return res.status(200).json({ resourceRoom }) -} - -// Delete resource room -async function deleteResourceRoom(req, res) { - const { userWithSiteSessionData } = res.locals - const { siteName } = req.params - const { accessToken } = userWithSiteSessionData - - const IsomerResourceRoom = new ResourceRoom(accessToken, siteName) - await IsomerResourceRoom.delete() - - return res.status(200).send("OK") -} - -router.get( - "/", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "getResourceRoomName"), - attachReadRouteHandlerWrapper(getResourceRoomName) -) -router.post( - "/", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "createResourceRoom"), - attachRollbackRouteHandlerWrapper(createResourceRoom) -) -router.post( - "/:resourceRoom", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "renameResourceRoom"), - attachRollbackRouteHandlerWrapper(renameResourceRoom) -) -router.delete( - "/", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "deleteResourceRoom"), - attachRollbackRouteHandlerWrapper(deleteResourceRoom) -) - -module.exports = router diff --git a/src/routes/v1/authenticatedSites/resources.js b/src/routes/v1/authenticatedSites/resources.js deleted file mode 100644 index ede4cc4a1..000000000 --- a/src/routes/v1/authenticatedSites/resources.js +++ /dev/null @@ -1,154 +0,0 @@ -import { Versions } from "@constants" - -import { statsMiddleware } from "@root/middleware/stats" - -const express = require("express") - -const router = express.Router({ mergeParams: true }) - -// Import middleware -const { NotFoundError } = require("@errors/NotFoundError") - -const { - attachReadRouteHandlerWrapper, - attachRollbackRouteHandlerWrapper, -} = require("@middleware/routeHandler") - -// Import classes -const { File, ResourcePageType } = require("@classes/File") -const { Resource } = require("@classes/Resource") -const { ResourceRoom } = require("@classes/ResourceRoom") - -// Import errors - -// List resources -async function listResources(req, res) { - const { userWithSiteSessionData } = res.locals - const { siteName } = req.params - const { accessToken } = userWithSiteSessionData - - const IsomerResourceRoom = new ResourceRoom(accessToken, siteName) - const resourceRoomName = await IsomerResourceRoom.get() - - const IsomerResource = new Resource(accessToken, siteName) - const resources = await IsomerResource.list(resourceRoomName) - - return res.status(200).json({ resourceRoomName, resources }) -} - -// Create new resource -async function createNewResource(req, res) { - const { userWithSiteSessionData } = res.locals - const { siteName } = req.params - const { resourceName } = req.body - const { accessToken } = userWithSiteSessionData - - const IsomerResourceRoom = new ResourceRoom(accessToken, siteName) - const resourceRoomName = await IsomerResourceRoom.get() - - const IsomerResource = new Resource(accessToken, siteName) - await IsomerResource.create(resourceRoomName, resourceName) - - return res.status(200).json({ resourceName }) -} - -// Delete resource -async function deleteResource(req, res) { - const { userWithSiteSessionData } = res.locals - const { siteName, resourceName } = req.params - const { accessToken } = userWithSiteSessionData - - const IsomerResourceRoom = new ResourceRoom(accessToken, siteName) - const resourceRoomName = await IsomerResourceRoom.get() - - const IsomerResource = new Resource(accessToken, siteName) - await IsomerResource.delete(resourceRoomName, resourceName) - - return res.status(200).send("OK") -} - -// Rename resource -async function renameResource(req, res) { - const { userWithSiteSessionData } = res.locals - const { siteName, resourceName, newResourceName } = req.params - const { accessToken } = userWithSiteSessionData - - const IsomerResourceRoom = new ResourceRoom(accessToken, siteName) - const resourceRoomName = await IsomerResourceRoom.get() - - const IsomerResource = new Resource(accessToken, siteName) - await IsomerResource.rename(resourceRoomName, resourceName, newResourceName) - - return res.status(200).json({ resourceName, newResourceName }) -} - -// To fix after refactoring -/* eslint-disable no-await-in-loop, no-restricted-syntax */ -// Move resource -async function moveResources(req, res) { - const { userWithSiteSessionData } = res.locals - const { siteName, resourceName, newResourceName } = req.params - const { files } = req.body - const { accessToken } = userWithSiteSessionData - - const ResourceRoomInstance = new ResourceRoom(accessToken, siteName) - const resourceRoomName = await ResourceRoomInstance.get() - - const IsomerResource = new Resource(accessToken, siteName) - const resources = await IsomerResource.list(resourceRoomName) - const resourceCategories = resources.map((resource) => resource.dirName) - if (!resourceCategories.includes(resourceName)) - throw new NotFoundError(`Resource category ${resourceName} was not found!`) - if (!resourceCategories.includes(newResourceName)) - throw new NotFoundError( - `Resource category ${newResourceName} was not found!` - ) - - const oldIsomerFile = new File(accessToken, siteName) - const newIsomerFile = new File(accessToken, siteName) - const oldResourcePageType = new ResourcePageType( - resourceRoomName, - resourceName - ) - const newResourcePageType = new ResourcePageType( - resourceRoomName, - newResourceName - ) - oldIsomerFile.setFileType(oldResourcePageType) - newIsomerFile.setFileType(newResourcePageType) - - for (const fileName of files) { - const { content, sha } = await oldIsomerFile.read(fileName) - await oldIsomerFile.delete(fileName, sha) - await newIsomerFile.create(fileName, content) - } - return res.status(200).send("OK") -} - -router.get( - "/", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "listResources"), - attachReadRouteHandlerWrapper(listResources) -) -router.post( - "/", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "createNewResource"), - attachRollbackRouteHandlerWrapper(createNewResource) -) -router.delete( - "/:resourceName", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "deleteResource"), - attachRollbackRouteHandlerWrapper(deleteResource) -) -router.post( - "/:resourceName/rename/:newResourceName", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "renameResource"), - attachRollbackRouteHandlerWrapper(renameResource) -) -router.post( - "/:resourceName/move/:newResourceName", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "moveResources"), - attachRollbackRouteHandlerWrapper(moveResources) -) - -module.exports = router diff --git a/src/routes/v1/authenticatedSites/settings.js b/src/routes/v1/authenticatedSites/settings.js deleted file mode 100644 index 67af0c9d0..000000000 --- a/src/routes/v1/authenticatedSites/settings.js +++ /dev/null @@ -1,49 +0,0 @@ -import { Versions } from "@constants" - -import { statsMiddleware } from "@root/middleware/stats" - -const express = require("express") - -const router = express.Router({ mergeParams: true }) - -// Import middleware -const { - attachReadRouteHandlerWrapper, - attachRollbackRouteHandlerWrapper, -} = require("@middleware/routeHandler") - -// Import Classes -const { Settings } = require("@classes/Settings") - -async function getSettings(req, res) { - const { userWithSiteSessionData } = res.locals - const { siteName } = req.params - const { accessToken } = userWithSiteSessionData - - const settingsFile = new Settings(accessToken, siteName) - const settings = await settingsFile.get() - return res.status(200).json({ settings }) -} - -async function updateSettings(req, res) { - const { userWithSiteSessionData } = res.locals - const { siteName } = req.params - const { accessToken } = userWithSiteSessionData - - const settings = new Settings(accessToken, siteName) - await settings.post(req.body) - return res.status(200).send("OK") -} - -router.get( - "/", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "getSettings"), - attachReadRouteHandlerWrapper(getSettings) -) -router.post( - "/", - statsMiddleware.logVersionNumberCallFor(Versions.V1, "updateSettings"), - attachRollbackRouteHandlerWrapper(updateSettings) -) - -module.exports = router diff --git a/src/server.js b/src/server.js index 73027b686..a0ab021ef 100644 --- a/src/server.js +++ b/src/server.js @@ -70,8 +70,6 @@ import ReviewRequestService from "@services/review/ReviewRequestService" import { apiLogger } from "./middleware/apiLogger" import { NotificationOnEditHandler } from "./middleware/notificationOnEditHandler" -import getAuthenticatedSubrouterV1 from "./routes/v1/authenticated" -import getAuthenticatedSitesSubrouterV1 from "./routes/v1/authenticatedSites" import getAuthenticatedSubrouter from "./routes/v2/authenticated" import { ReviewsRouter } from "./routes/v2/authenticated/review" import getAuthenticatedSitesSubrouter from "./routes/v2/authenticatedSites" @@ -323,17 +321,6 @@ const reviewRouter = new ReviewsRouter( notificationsService, gitHubService ) -const authenticatedSubrouterV1 = getAuthenticatedSubrouterV1({ - authenticationMiddleware, - statsMiddleware, - usersService, - apiLogger, -}) -const authenticatedSitesSubrouterV1 = getAuthenticatedSitesSubrouterV1({ - authenticationMiddleware, - authorizationMiddleware, - apiLogger, -}) const authenticatedSubrouterV2 = getAuthenticatedSubrouter({ authenticationMiddleware, @@ -418,13 +405,6 @@ app.use(sessionMiddleware) app.use("/v2/ping", (req, res, next) => res.status(200).send("Ok")) // Routes layer setup -// To avoid refactoring auth router v1 to use dependency injection -app.use("/v1/auth", authV2Router.getRouter()) -// Endpoints which have siteName, used to inject site access token -app.use("/v1/sites/:siteName", authenticatedSitesSubrouterV1) -// Endpoints which require login, but not site access token -app.use("/v1", authenticatedSubrouterV1) - app.use("/v2/auth", authV2Router.getRouter()) // Endpoints which have require login, but not site access token app.use("/v2", authenticatedSubrouterV2) diff --git a/src/services/middlewareServices/AuthenticationMiddlewareService.ts b/src/services/middlewareServices/AuthenticationMiddlewareService.ts index 0a9c661de..f5631e684 100644 --- a/src/services/middlewareServices/AuthenticationMiddlewareService.ts +++ b/src/services/middlewareServices/AuthenticationMiddlewareService.ts @@ -34,12 +34,7 @@ const E2E_TEST_SECRET = config.get("cypress.e2eTestSecret") export const E2E_TEST_GH_TOKEN = config.get("cypress.e2eTestGithubToken") export const E2E_TEST_GITHUB_USER = "e2e-test" -const GENERAL_ACCESS_PATHS = [ - "/v1/sites", - "/v1/auth/whoami", - "/v2/sites", - "/v2/auth/whoami", -] +const GENERAL_ACCESS_PATHS = ["/v2/sites", "/v2/auth/whoami"] interface E2eCookie { isomercmsE2E: string @@ -168,7 +163,7 @@ export default class AuthenticationMiddlewareService { if (!cookies) return false const { isomercmsE2E, e2eUserType } = cookies - const urlTokens = url.split("/") // urls take the form "/v1/sites//"" + const urlTokens = url.split("/") // urls take the form "/v2/sites//"" // NOTE: If the cookie is not set, this is an actual user. if (!isomercmsE2E || !e2eUserType) return false diff --git a/src/services/middlewareServices/__tests__/AuthenticationMiddlewareService.spec.ts b/src/services/middlewareServices/__tests__/AuthenticationMiddlewareService.spec.ts index 673d24a71..a82aa10c6 100644 --- a/src/services/middlewareServices/__tests__/AuthenticationMiddlewareService.spec.ts +++ b/src/services/middlewareServices/__tests__/AuthenticationMiddlewareService.spec.ts @@ -34,8 +34,8 @@ const extractSpy = jest const authenticationMiddlewareService = new AuthenticationMiddlewareService() -const E2E_GITHUB_REPO_URL = `/v1/sites/${E2E_TEST_REPO}` -const E2E_EMAIL_REPO_URL = `/v1/sites/${E2E_EMAIL_TEST_SITE.repo}` +const E2E_GITHUB_REPO_URL = `/v2/sites/${E2E_TEST_REPO}` +const E2E_EMAIL_REPO_URL = `/v2/sites/${E2E_EMAIL_TEST_SITE.repo}` const MOCK_GITHUB_USER_PROPS: VerifyAccessProps = { // NOTE: Actual users won't have cookies - instead, they will use our session @@ -197,7 +197,7 @@ describe("AuthenticationMiddlewareService", () => { it("should throw an error when github e2e user tries to access a non-e2e repo", () => { // Arrange - const props = getMockUser("github", `/v1/sites/some-repo`) + const props = getMockUser("github", `/v2/sites/some-repo`) // Act // NOTE: Have to do this cos it should throw @@ -209,7 +209,7 @@ describe("AuthenticationMiddlewareService", () => { it("should throw an error when email e2e admin tries to access a non-e2e repo", () => { // Arrange - const props = getMockUser("email admin", `/v1/sites/some-repo`) + const props = getMockUser("email admin", `/v2/sites/some-repo`) // Act // NOTE: Have to do this cos it should throw @@ -221,7 +221,7 @@ describe("AuthenticationMiddlewareService", () => { it("should throw an error when email e2e collab tries to access a non-e2e repo", () => { // Arrange - const props = getMockUser("email collab", `/v1/sites/some-repo`) + const props = getMockUser("email collab", `/v2/sites/some-repo`) // Act // NOTE: Have to do this cos it should throw diff --git a/src/utils/route-utils.js b/src/utils/route-utils.js deleted file mode 100644 index a88cadf44..000000000 --- a/src/utils/route-utils.js +++ /dev/null @@ -1,46 +0,0 @@ -// Import classes -const { - File, - PageType, - CollectionPageType, - DataType, -} = require("@classes/File") - -const readPageUtilFunc = async (accessToken, siteName, pageName) => { - const IsomerFile = new File(accessToken, siteName) - const pageType = new PageType() - IsomerFile.setFileType(pageType) - const fileContents = await IsomerFile.read(pageName) - return fileContents -} - -const readCollectionPageUtilFunc = async ( - accessToken, - siteName, - collectionName, - pageName -) => { - const IsomerFile = new File(accessToken, siteName) - const collectionPageType = new CollectionPageType(collectionName) - IsomerFile.setFileType(collectionPageType) - const fileContents = await IsomerFile.read(pageName) - return fileContents -} - -const createDataFileUtilFunc = async ( - accessToken, - siteName, - filePath, - content -) => { - const IsomerFile = new File(accessToken, siteName) - const dataType = new DataType() - IsomerFile.setFileType(dataType) - await IsomerFile.create(filePath, content) -} - -module.exports = { - readPageUtilFunc, - readCollectionPageUtilFunc, - createDataFileUtilFunc, -}