├── .github
└── dependabot.yml
├── .gitignore
├── LICENSE
├── README.md
├── cli
├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .github
│ ├── dependabot.yml
│ └── workflows
│ │ ├── automerge.yml
│ │ ├── failureNotifications.yml
│ │ ├── manualRelease.yml
│ │ ├── notify-slack-on-pr-open.yml
│ │ ├── onPushToMain.yml
│ │ ├── onRelease.yml
│ │ └── test.yml
├── .gitignore
├── .mocharc.json
├── .vscode
│ └── launch.json
├── CONTRIBUTING.md
├── README.md
├── bin
│ ├── dev
│ ├── dev.cmd
│ ├── run
│ └── run.cmd
├── cdk.json
├── oclif.md
├── package.json
├── src
│ ├── assume-role.ts
│ ├── commands
│ │ └── init.ts
│ ├── configure-aws-client.ts
│ ├── constants
│ │ ├── _code-gen-presigned-api.ts
│ │ ├── _code-gen-upload-component.ts
│ │ ├── file-names.ts
│ │ └── index.ts
│ ├── create-files.ts
│ ├── fetch-user.ts
│ ├── iam
│ │ ├── createRoleParams.ts
│ │ └── index.ts
│ ├── index.ts
│ ├── launch-bucket.ts
│ ├── update-status.ts
│ └── update-user.ts
├── test
│ ├── commands
│ │ ├── clean-up.ts
│ │ └── init.test.ts
│ ├── helpers
│ │ └── init.js
│ └── tsconfig.json
└── tsconfig.json
├── client
├── .eslintrc.cjs
├── .gitignore
├── .npmignore
├── README.md
├── dist
│ ├── index.cjs
│ ├── index.cjs.map
│ ├── index.d.ts
│ ├── index.mjs
│ └── index.mjs.map
├── package.json
├── pnpm-lock.yaml
├── rollup.config.mjs
├── src
│ ├── client.ts
│ ├── index.ts
│ ├── nextjs
│ │ ├── components
│ │ │ ├── Demo.tsx
│ │ │ └── FileUploadInput.tsx
│ │ ├── file-upload-hook.ts
│ │ └── s3-presigned-api.ts
│ └── util
│ │ ├── config.ts
│ │ ├── helpers.ts
│ │ ├── index.ts
│ │ └── s3-client.ts
├── test
│ └── main.test.mjs
└── tsconfig.json
├── docs
├── NR_PLATFORM.md
└── logo
│ ├── file-upload-completed.png
│ ├── gifs
│ ├── bucket-creation.gif
│ ├── demo.gif
│ ├── file-upload.gif
│ └── hero-gif-full-demo.gif
│ ├── netrunner-main-character.png
│ ├── screenshot-10-july.png
│ ├── screenshot-22-july.png
│ └── screenshot-25-july.png
├── examples
├── app-router-example
│ ├── .env.template
│ ├── .eslintrc.json
│ ├── .gitignore
│ ├── README.md
│ ├── app
│ │ ├── api
│ │ │ └── upload
│ │ │ │ ├── helper.ts
│ │ │ │ └── route.ts
│ │ ├── layout.tsx
│ │ └── page.tsx
│ ├── next.config.js
│ ├── package.json
│ ├── postcss.config.js
│ ├── styles
│ │ └── globals.css
│ ├── tailwind.config.js
│ ├── tsconfig.json
│ └── utils
│ │ ├── components
│ │ └── Common.tsx
│ │ ├── index.tsx
│ │ └── upload
│ │ ├── components
│ │ ├── FilePreview.tsx
│ │ ├── Status.tsx
│ │ └── icons
│ │ │ └── IconUpload.tsx
│ │ ├── hooks
│ │ └── useUploadFile.tsx
│ │ ├── index.tsx
│ │ └── utils
│ │ └── index.ts
└── pages-router-example
│ ├── .eslintrc.json
│ ├── .gitignore
│ ├── README.md
│ ├── components
│ ├── index.tsx
│ └── upload
│ │ ├── components
│ │ ├── FilePreview.tsx
│ │ ├── Status.tsx
│ │ └── icons
│ │ │ └── IconUpload.tsx
│ │ ├── hooks
│ │ └── useUploadFile.tsx
│ │ ├── index.tsx
│ │ └── utils
│ │ └── index.ts
│ ├── next.config.js
│ ├── package.json
│ ├── pages
│ ├── _app.tsx
│ ├── _document.tsx
│ ├── api
│ │ └── generateImgUrl.ts
│ └── index.tsx
│ ├── pnpm-lock.yaml
│ ├── postcss.config.js
│ ├── styles
│ └── globals.css
│ ├── tailwind.config.js
│ └── tsconfig.json
└── iam_netrunner_setup.yaml
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: "npm " # See documentation for possible values
9 | directory: "/" # Location of package manifests
10 | schedule:
11 | interval: "weekly"
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.log
2 | .DS_Store
3 | node_modules
4 | dist
5 |
6 | **/.env
7 |
8 | .env
9 |
10 | **/.env.Local
11 |
12 | **/*.env
13 | # Logs
14 | logs
15 | *.log
16 | npm-debug.log*
17 | yarn-debug.log*
18 | yarn-error.log*
19 | xata
20 |
21 | # Diagnostic reports (https://nodejs.org/api/report.html)
22 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
23 |
24 | # Runtime data
25 | pids
26 | *.pid
27 | *.seed
28 | *.pid.lock
29 |
30 | # Directory for instrumented libs generated by jscoverage/JSCover
31 | lib-cov
32 |
33 | # Coverage directory used by tools like istanbul
34 | coverage
35 | *.lcov
36 |
37 | # nyc test coverage
38 | .nyc_output
39 |
40 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
41 | .grunt
42 |
43 | # Bower dependency directory (https://bower.io/)
44 | bower_components
45 |
46 | # node-waf configuration
47 | .lock-wscript
48 |
49 | # Compiled binary addons (https://nodejs.org/api/addons.html)
50 | build/Release
51 |
52 | # Dependency directories
53 | node_modules/
54 | jspm_packages/
55 |
56 | # TypeScript v1 declaration files
57 | typings/
58 |
59 | # TypeScript cache
60 | *.tsbuildinfo
61 |
62 | # Optional npm cache directory
63 | .npm
64 |
65 | # Optional eslint cache
66 | .eslintcache
67 |
68 | # Microbundle cache
69 | .rpt2_cache/
70 | .rts2_cache_cjs/
71 | .rts2_cache_es/
72 | .rts2_cache_umd/
73 |
74 | # Optional REPL history
75 | .node_repl_history
76 |
77 | # Output of 'npm pack'
78 | *.tgz
79 |
80 | # Yarn Integrity file
81 | .yarn-integrity
82 |
83 | # dotenv environment variables file
84 | .env
85 | .env.test
86 |
87 | # parcel-bundler cache (https://parceljs.org/)
88 | .cache
89 |
90 | # Next.js build output
91 | .next
92 |
93 | # Nuxt.js build / generate output
94 | .nuxt
95 | dist
96 |
97 | # Gatsby files
98 | .cache/
99 | # Comment in the public line in if your project uses Gatsby and *not* Next.js
100 | # https://nextjs.org/blog/next-9-1#public-directory-support
101 | # public
102 |
103 | # vuepress build output
104 | .vuepress/dist
105 |
106 | # Serverless directories
107 | .serverless/
108 |
109 | # FuseBox cache
110 | .fusebox/
111 |
112 | # DynamoDB Local files
113 | .dynamodb/
114 |
115 | # TernJS port file
116 | .tern-port
117 |
118 | # NPM config
119 | .npmrc
120 |
121 | .DS_Store
122 | .turbo
123 |
124 | local-example
125 | local-example/
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 netrunnerhq
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | ⚡️ Implement AWS S3 In Your Next.js App In Under 5 Minutes
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | 👩💻 Demo
12 | •
13 | 🦾 Examples •
15 | 🕸️ Website
16 | •
17 | 🤝 Contribute
18 |
19 |
20 |
21 |
22 | Netrunner is a batteries included tool to implement secure AWS storage in Next.js apps. Unlike other tools, it also solves the problem of configuring S3 buckets in your AWS account. Here's how it works:
23 |
24 | 1. view the [example](/examples) repositories for both app-and-pages router
25 | 2. create an S3 bucket in the Netrunner dashboard: https://netrunnerhq.com/
26 | 3. copy the template .env file in your terminal: ```cp .env.template .env.local``` and paste your S3 bucket name and env variables
27 | 4. run ```pnpm dev``` and upload a file!
28 |
29 | ## 🪄 Get started with Next.js and AWS S3 file uploads
30 |
31 |
32 |
33 | Make sure you have a Next.js app and AWS account at the ready. Let's get started!
34 |
35 | ### 🔋 Step 1. Package installation
36 |
37 | First install the Netrunner [npm](https://npmwebsite.com) package to handle file uploads.
38 |
39 | ```console
40 | npm install @netrunner/next-s3
41 | ```
42 |
43 | ```console
44 | yarn add @netrunner/next-s3
45 | ```
46 |
47 | ```console
48 | pnpm add @netrunner/nextjs-s3
49 | ```
50 |
51 | ### 🌱 Step 2. Implement file upload component
52 |
53 | When using app router:
54 |
55 | ```tsx
56 | // app/upload-page.tsx or for page router: pages/upload-page.tsx
57 | import { useFileUpload } from "netrunnerhq/next-s3-upload";
58 |
59 | export default function UploadComponent() {
60 | const { FileUploadInput, uploadFile } = useFileUpload();
61 | return ;
62 | }
63 | ```
64 |
65 | ### 🔌 Step 3. Create a new API route
66 |
67 | ```tsx
68 | // app/upload/route.ts or for page router: pages/api/upload.ts
69 | import { apiSignS3Url } from "@netrunnerhq/client";
70 |
71 | export default async function handler(req, res) {
72 | if (req.method !== "GET")
73 | // different res for app router
74 | return res.status(405).json({ message: "Method not allowed" });
75 |
76 | if (!req.query.filename || !req.query.filetype)
77 | return res.status(400).json({ message: "Missing filename or filetype" });
78 |
79 | try {
80 | const { filename, filetype } = req.query;
81 | const { signed_url } = await apiSignS3Url(filename, filetype);
82 | res.status(200).json({ signed_url });
83 | } catch (error) {
84 | console.error(error);
85 | res.status(500).json({ error: "An error occurred" });
86 | }
87 | }
88 | ```
89 |
90 | ### 🪣 Step 4. Bucket configuration
91 |
92 | - Create an S3 bucket inside your AWS account from the Netrunner app by logging in with GitHub and following the quickstart on [netrunnerhq.com](https://netrunnerhq.com/)
93 |
94 | - Enter your AWS account ID that you can find on the right top of the AWS console
95 |
96 | - Verify in your AWS console if the bucket is deployed correctly in the S3 service [page](https://s3.console.aws.amazon.com/s3/home?region=us-east-1)
97 |
98 |
99 |
100 |
101 |
102 | ### 🎉 Step 5. Environment variables and upload file!
103 |
104 | - Add the environment variables to your .env.local file for the bucket name and region. You can copy them from the Netrunner console.
105 |
106 | - Run your app and or use the [example](https://github.com/netrunnerhq/nextjs-aws-s3/tree/main/example) code in this repository if convenient.
107 |
108 | - Click the upload button to select a test image. The file should upload correctly!
109 |
110 |
111 |
112 |
113 |
114 | ## 🦾 About Netrunner
115 |
116 | Netrunner is being developed by [Vincent Hus](https://github.com/davincios) with the mission to enable JavaScript software engineers to transform their AWS cloud account into a personalised Firebase developer platform.
117 |
118 | You can learn more by visiting our [website](https://netrunnerhq.com) or ping Vincent on twitter [@jvf_hus](https://twitter.com/vincent_hus)
119 |
--------------------------------------------------------------------------------
/cli/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | charset = utf-8
7 | trim_trailing_whitespace = true
8 | insert_final_newline = true
9 |
10 | [*.md]
11 | trim_trailing_whitespace = false
12 |
--------------------------------------------------------------------------------
/cli/.eslintignore:
--------------------------------------------------------------------------------
1 | /dist
2 |
3 | src/templates/**
4 | /src/templates
5 |
--------------------------------------------------------------------------------
/cli/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "oclif",
4 | "oclif-typescript"
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/cli/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: 'npm'
4 | directory: '/'
5 | schedule:
6 | interval: 'weekly'
7 | day: 'saturday'
8 | versioning-strategy: 'increase'
9 | labels:
10 | - 'dependencies'
11 | open-pull-requests-limit: 5
12 | pull-request-branch-name:
13 | separator: '-'
14 | commit-message:
15 | # cause a release for non-dev-deps
16 | prefix: fix(deps)
17 | # no release for dev-deps
18 | prefix-development: chore(dev-deps)
19 | ignore:
20 | - dependency-name: '@salesforce/dev-scripts'
21 | - dependency-name: '*'
22 | update-types: ['version-update:semver-major']
23 |
--------------------------------------------------------------------------------
/cli/.github/workflows/automerge.yml:
--------------------------------------------------------------------------------
1 | name: automerge
2 | on:
3 | workflow_dispatch:
4 | schedule:
5 | - cron: '17 2,5,8,11 * * *'
6 |
7 | jobs:
8 | automerge:
9 | uses: oclif/github-workflows/.github/workflows/automerge.yml@main
10 | secrets: inherit
11 |
--------------------------------------------------------------------------------
/cli/.github/workflows/failureNotifications.yml:
--------------------------------------------------------------------------------
1 | name: failureNotifications
2 |
3 | on:
4 | workflow_run:
5 | workflows:
6 | - version, tag and github release
7 | - publish
8 | types:
9 | - completed
10 |
11 | jobs:
12 | failure-notify:
13 | runs-on: ubuntu-latest
14 | if: ${{ github.event.workflow_run.conclusion == 'failure' }}
15 | steps:
16 | - name: Announce Failure
17 | id: slack
18 | uses: slackapi/slack-github-action@v1.21.0
19 | env:
20 | # for non-CLI-team-owned plugins, you can send this anywhere you like
21 | SLACK_WEBHOOK_URL: ${{ secrets.CLI_ALERTS_SLACK_WEBHOOK }}
22 | SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
23 | with:
24 | payload: |
25 | {
26 | "text": "${{ github.event.workflow_run.name }} failed: ${{ github.event.workflow_run.repository.name }}",
27 | "blocks": [
28 | {
29 | "type": "header",
30 | "text": {
31 | "type": "plain_text",
32 | "text": ":bh-alert: ${{ github.event.workflow_run.name }} failed: ${{ github.event.workflow_run.repository.name }} :bh-alert:"
33 | }
34 | },
35 | {
36 | "type": "section",
37 | "text": {
38 | "type": "mrkdwn",
39 | "text": "Repo: ${{ github.event.workflow_run.repository.html_url }}\nWorkflow name: `${{ github.event.workflow_run.name }}`\nJob url: ${{ github.event.workflow_run.html_url }}"
40 | }
41 | }
42 | ]
43 | }
44 |
--------------------------------------------------------------------------------
/cli/.github/workflows/manualRelease.yml:
--------------------------------------------------------------------------------
1 | name: manual release
2 |
3 | on:
4 | workflow_dispatch:
5 |
6 | jobs:
7 | release:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/checkout@v3
11 | with:
12 | token: ${{ secrets.SVC_CLI_BOT_GITHUB_TOKEN }}
13 | - name: Conventional Changelog Action
14 | id: changelog
15 | uses: TriPSs/conventional-changelog-action@d360fad3a42feca6462f72c97c165d60a02d4bf2
16 | # overriding some of the basic behaviors to just get the changelog
17 | with:
18 | git-user-name: svc-cli-bot
19 | git-user-email: svc_cli_bot@salesforce.com
20 | github-token: ${{ secrets.SVC_CLI_BOT_GITHUB_TOKEN }}
21 | output-file: false
22 | # always do the release, even if there are no semantic commits
23 | skip-on-empty: false
24 | tag-prefix: ''
25 | - uses: notiz-dev/github-action-json-property@2192e246737701f108a4571462b76c75e7376216
26 | id: packageVersion
27 | with:
28 | path: 'package.json'
29 | prop_path: 'version'
30 | - name: Create Github Release
31 | uses: actions/create-release@v1
32 | env:
33 | GITHUB_TOKEN: ${{ secrets.SVC_CLI_BOT_GITHUB_TOKEN }}
34 | with:
35 | tag_name: ${{ steps.packageVersion.outputs.prop }}
36 | release_name: ${{ steps.packageVersion.outputs.prop }}
37 |
--------------------------------------------------------------------------------
/cli/.github/workflows/notify-slack-on-pr-open.yml:
--------------------------------------------------------------------------------
1 | name: Pull Request Slack Notification
2 |
3 | on:
4 | pull_request:
5 | types: [opened, reopened]
6 |
7 | jobs:
8 | build:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - name: Notify Slack on PR open
12 | env:
13 | WEBHOOK_URL : ${{ secrets.CLI_TEAM_SLACK_WEBHOOK_URL }}
14 | PULL_REQUEST_AUTHOR_ICON_URL : ${{ github.event.pull_request.user.avatar_url }}
15 | PULL_REQUEST_AUTHOR_NAME : ${{ github.event.pull_request.user.login }}
16 | PULL_REQUEST_AUTHOR_PROFILE_URL: ${{ github.event.pull_request.user.html_url }}
17 | PULL_REQUEST_BASE_BRANCH_NAME : ${{ github.event.pull_request.base.ref }}
18 | PULL_REQUEST_COMPARE_BRANCH_NAME : ${{ github.event.pull_request.head.ref }}
19 | PULL_REQUEST_NUMBER : ${{ github.event.pull_request.number }}
20 | PULL_REQUEST_REPO: ${{ github.event.pull_request.head.repo.name }}
21 | PULL_REQUEST_TITLE : ${{ github.event.pull_request.title }}
22 | PULL_REQUEST_URL : ${{ github.event.pull_request.html_url }}
23 | uses: salesforcecli/github-workflows/.github/actions/prNotification@main
24 |
--------------------------------------------------------------------------------
/cli/.github/workflows/onPushToMain.yml:
--------------------------------------------------------------------------------
1 | # test
2 | name: version, tag and github release
3 |
4 | on:
5 | push:
6 | branches: [main]
7 |
8 | jobs:
9 | release:
10 | uses: oclif/github-workflows/.github/workflows/githubRelease.yml@main
11 | secrets: inherit
12 |
13 | # most repos won't use this
14 | # depends on previous job to avoid git collisions, not for any functionality reason
15 | # docs:
16 | # uses: salesforcecli/github-workflows/.github/workflows/publishTypedoc.yml@main
17 | # secrets: inherit
18 | # needs: release
19 |
--------------------------------------------------------------------------------
/cli/.github/workflows/onRelease.yml:
--------------------------------------------------------------------------------
1 | name: publish
2 |
3 | on:
4 | release:
5 | types: [released]
6 | # support manual release in case something goes wrong and needs to be repeated or tested
7 | workflow_dispatch:
8 | inputs:
9 | tag:
10 | description: tag that needs to publish
11 | type: string
12 | required: true
13 | jobs:
14 | npm:
15 | uses: oclif/github-workflows/.github/workflows/npmPublish.yml@main
16 | with:
17 | tag: latest
18 | githubTag: ${{ github.event.release.tag_name || inputs.tag }}
19 | secrets: inherit
20 |
--------------------------------------------------------------------------------
/cli/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: tests
2 | on:
3 | push:
4 | branches-ignore: [main]
5 | workflow_dispatch:
6 |
7 | jobs:
8 | unit-tests:
9 | uses: oclif/github-workflows/.github/workflows/unitTest.yml@main
10 |
--------------------------------------------------------------------------------
/cli/.gitignore:
--------------------------------------------------------------------------------
1 | *-debug.log
2 | *-error.log
3 | /.nyc_output
4 | /dist
5 | /lib
6 | /package-lock.json
7 | /tmp
8 | /yarn.lock
9 | node_modules
10 | oclif.manifest.json
11 | pages
12 |
13 | .env.local
14 |
15 | # local env files
16 | .env
17 | .env.local
18 | .env.development.local
19 | .env.test.local
20 | .env.production.local
--------------------------------------------------------------------------------
/cli/.mocharc.json:
--------------------------------------------------------------------------------
1 | {
2 | "require": [
3 | "test/helpers/init.js",
4 | "ts-node/register"
5 | ],
6 | "watch-extensions": [
7 | "ts"
8 | ],
9 | "recursive": true,
10 | "reporter": "spec",
11 | "timeout": 60000
12 | }
13 |
--------------------------------------------------------------------------------
/cli/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "type": "node",
6 | "request": "launch",
7 | "name": "Execute Command",
8 | "skipFiles": [
9 | "/**"
10 | ],
11 | "program": "${workspaceFolder}/bin/dev",
12 | "args": [
13 | "hello",
14 | "world",
15 | ],
16 | }
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/cli/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Development Notes
2 |
3 | ###
4 |
5 | Add command:
6 |
7 | ```shell
8 | oclif generate command NAME
9 | ```
10 |
11 | Add hook:
12 |
13 | ```shell
14 | oclif generate hook NAME
15 | ```
16 |
17 | ### Todos May 28th
18 |
19 | #### [A] Adjust the CL function:
20 |
21 | 1. Customer does init command via the CLI. This CLI creates a custom role in the customer's account.
22 | 2. The CLI stores the signup parameters in a users table in Xata to keep developing fast. For the API call we need (targetAccountId, roleName, targetExternalId, targetRegion).
23 | 3. Once the role has been created, a second request is executed (all in the same API call) to create the bucket in the target account. This bucket name is also stored in the users table database
24 | —-------
25 |
26 | #### [B] NextJS API:
27 |
28 | 4. On the front-end web application, we make an API call to the users table, and check if there is a key present for the user’s bucket. If that key is present, a second request is made with an assumed role to check if the bucket exists (with the head http method).
29 | 5. If the bucket exists in the other account, we return “bucket verified status”
30 |
31 | ## AWS Switching Profile Notes
32 |
33 | - aws sts get-caller-identity
34 | -
35 |
--------------------------------------------------------------------------------
/cli/README.md:
--------------------------------------------------------------------------------
1 | # oclif-hello-world
2 |
3 | oclif example Hello World CLI
4 |
5 | [](https://oclif.io)
6 | [](https://npmjs.org/package/oclif-hello-world)
7 | [](https://circleci.com/gh/oclif/hello-world/tree/main)
8 | [](https://npmjs.org/package/oclif-hello-world)
9 | [](https://github.com/oclif/hello-world/blob/main/package.json)
10 |
11 |
12 | * [oclif-hello-world](#oclif-hello-world)
13 | * [Usage](#usage)
14 | * [Commands](#commands)
15 |
16 |
17 | # Usage
18 |
19 |
20 | ```sh-session
21 | $ npm install -g @storengine/cli
22 | $ storengine COMMAND
23 | running command...
24 | $ storengine (--version)
25 | @storengine/cli/0.0.9 darwin-arm64 node-v18.15.0
26 | $ storengine --help [COMMAND]
27 | USAGE
28 | $ storengine COMMAND
29 | ...
30 | ```
31 |
32 |
33 | # Commands
34 |
35 |
36 | * [`storengine cross-acc`](#storengine-cross-acc)
37 | * [`storengine help [COMMANDS]`](#storengine-help-commands)
38 | * [`storengine init USERNAME TEMPLATEID`](#storengine-init-username-templateid)
39 | * [`storengine plugins`](#storengine-plugins)
40 | * [`storengine plugins:install PLUGIN...`](#storengine-pluginsinstall-plugin)
41 | * [`storengine plugins:inspect PLUGIN...`](#storengine-pluginsinspect-plugin)
42 | * [`storengine plugins:install PLUGIN...`](#storengine-pluginsinstall-plugin-1)
43 | * [`storengine plugins:link PLUGIN`](#storengine-pluginslink-plugin)
44 | * [`storengine plugins:uninstall PLUGIN...`](#storengine-pluginsuninstall-plugin)
45 | * [`storengine plugins:uninstall PLUGIN...`](#storengine-pluginsuninstall-plugin-1)
46 | * [`storengine plugins:uninstall PLUGIN...`](#storengine-pluginsuninstall-plugin-2)
47 | * [`storengine plugins update`](#storengine-plugins-update)
48 |
49 | ## `storengine cross-acc`
50 |
51 | ```
52 | USAGE
53 | $ storengine cross-acc
54 | ```
55 |
56 | _See code: [dist/commands/cross-acc.ts](https://github.com/davincios/storengine/blob/v0.0.9/dist/commands/cross-acc.ts)_
57 |
58 | ## `storengine help [COMMANDS]`
59 |
60 | Display help for storengine.
61 |
62 | ```
63 | USAGE
64 | $ storengine help [COMMANDS] [-n]
65 |
66 | ARGUMENTS
67 | COMMANDS Command to show help for.
68 |
69 | FLAGS
70 | -n, --nested-commands Include all nested commands in the output.
71 |
72 | DESCRIPTION
73 | Display help for storengine.
74 | ```
75 |
76 | _See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v5.2.9/src/commands/help.ts)_
77 |
78 | ## `storengine init USERNAME TEMPLATEID`
79 |
80 | Creates an upload demo in your project, consisting of an API route and page component.
81 |
82 | ```
83 | USAGE
84 | $ storengine init USERNAME TEMPLATEID
85 |
86 | ARGUMENTS
87 | USERNAME Your username
88 | TEMPLATEID The ID of the template to use
89 |
90 | DESCRIPTION
91 | Creates an upload demo in your project, consisting of an API route and page component.
92 | ```
93 |
94 | _See code: [dist/commands/init/index.ts](https://github.com/davincios/storengine/blob/v0.0.9/dist/commands/init/index.ts)_
95 |
96 | ## `storengine plugins`
97 |
98 | List installed plugins.
99 |
100 | ```
101 | USAGE
102 | $ storengine plugins [--core]
103 |
104 | FLAGS
105 | --core Show core plugins.
106 |
107 | DESCRIPTION
108 | List installed plugins.
109 |
110 | EXAMPLES
111 | $ storengine plugins
112 | ```
113 |
114 | _See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v2.4.7/src/commands/plugins/index.ts)_
115 |
116 | ## `storengine plugins:install PLUGIN...`
117 |
118 | Installs a plugin into the CLI.
119 |
120 | ```
121 | USAGE
122 | $ storengine plugins:install PLUGIN...
123 |
124 | ARGUMENTS
125 | PLUGIN Plugin to install.
126 |
127 | FLAGS
128 | -f, --force Run yarn install with force flag.
129 | -h, --help Show CLI help.
130 | -v, --verbose
131 |
132 | DESCRIPTION
133 | Installs a plugin into the CLI.
134 | Can be installed from npm or a git url.
135 |
136 | Installation of a user-installed plugin will override a core plugin.
137 |
138 | e.g. If you have a core plugin that has a 'hello' command, installing a user-installed plugin with a 'hello' command
139 | will override the core plugin implementation. This is useful if a user needs to update core plugin functionality in
140 | the CLI without the need to patch and update the whole CLI.
141 |
142 |
143 | ALIASES
144 | $ storengine plugins add
145 |
146 | EXAMPLES
147 | $ storengine plugins:install myplugin
148 |
149 | $ storengine plugins:install https://github.com/someuser/someplugin
150 |
151 | $ storengine plugins:install someuser/someplugin
152 | ```
153 |
154 | ## `storengine plugins:inspect PLUGIN...`
155 |
156 | Displays installation properties of a plugin.
157 |
158 | ```
159 | USAGE
160 | $ storengine plugins:inspect PLUGIN...
161 |
162 | ARGUMENTS
163 | PLUGIN [default: .] Plugin to inspect.
164 |
165 | FLAGS
166 | -h, --help Show CLI help.
167 | -v, --verbose
168 |
169 | GLOBAL FLAGS
170 | --json Format output as json.
171 |
172 | DESCRIPTION
173 | Displays installation properties of a plugin.
174 |
175 | EXAMPLES
176 | $ storengine plugins:inspect myplugin
177 | ```
178 |
179 | ## `storengine plugins:install PLUGIN...`
180 |
181 | Installs a plugin into the CLI.
182 |
183 | ```
184 | USAGE
185 | $ storengine plugins:install PLUGIN...
186 |
187 | ARGUMENTS
188 | PLUGIN Plugin to install.
189 |
190 | FLAGS
191 | -f, --force Run yarn install with force flag.
192 | -h, --help Show CLI help.
193 | -v, --verbose
194 |
195 | DESCRIPTION
196 | Installs a plugin into the CLI.
197 | Can be installed from npm or a git url.
198 |
199 | Installation of a user-installed plugin will override a core plugin.
200 |
201 | e.g. If you have a core plugin that has a 'hello' command, installing a user-installed plugin with a 'hello' command
202 | will override the core plugin implementation. This is useful if a user needs to update core plugin functionality in
203 | the CLI without the need to patch and update the whole CLI.
204 |
205 |
206 | ALIASES
207 | $ storengine plugins add
208 |
209 | EXAMPLES
210 | $ storengine plugins:install myplugin
211 |
212 | $ storengine plugins:install https://github.com/someuser/someplugin
213 |
214 | $ storengine plugins:install someuser/someplugin
215 | ```
216 |
217 | ## `storengine plugins:link PLUGIN`
218 |
219 | Links a plugin into the CLI for development.
220 |
221 | ```
222 | USAGE
223 | $ storengine plugins:link PLUGIN
224 |
225 | ARGUMENTS
226 | PATH [default: .] path to plugin
227 |
228 | FLAGS
229 | -h, --help Show CLI help.
230 | -v, --verbose
231 |
232 | DESCRIPTION
233 | Links a plugin into the CLI for development.
234 | Installation of a linked plugin will override a user-installed or core plugin.
235 |
236 | e.g. If you have a user-installed or core plugin that has a 'hello' command, installing a linked plugin with a 'hello'
237 | command will override the user-installed or core plugin implementation. This is useful for development work.
238 |
239 |
240 | EXAMPLES
241 | $ storengine plugins:link myplugin
242 | ```
243 |
244 | ## `storengine plugins:uninstall PLUGIN...`
245 |
246 | Removes a plugin from the CLI.
247 |
248 | ```
249 | USAGE
250 | $ storengine plugins:uninstall PLUGIN...
251 |
252 | ARGUMENTS
253 | PLUGIN plugin to uninstall
254 |
255 | FLAGS
256 | -h, --help Show CLI help.
257 | -v, --verbose
258 |
259 | DESCRIPTION
260 | Removes a plugin from the CLI.
261 |
262 | ALIASES
263 | $ storengine plugins unlink
264 | $ storengine plugins remove
265 | ```
266 |
267 | ## `storengine plugins:uninstall PLUGIN...`
268 |
269 | Removes a plugin from the CLI.
270 |
271 | ```
272 | USAGE
273 | $ storengine plugins:uninstall PLUGIN...
274 |
275 | ARGUMENTS
276 | PLUGIN plugin to uninstall
277 |
278 | FLAGS
279 | -h, --help Show CLI help.
280 | -v, --verbose
281 |
282 | DESCRIPTION
283 | Removes a plugin from the CLI.
284 |
285 | ALIASES
286 | $ storengine plugins unlink
287 | $ storengine plugins remove
288 | ```
289 |
290 | ## `storengine plugins:uninstall PLUGIN...`
291 |
292 | Removes a plugin from the CLI.
293 |
294 | ```
295 | USAGE
296 | $ storengine plugins:uninstall PLUGIN...
297 |
298 | ARGUMENTS
299 | PLUGIN plugin to uninstall
300 |
301 | FLAGS
302 | -h, --help Show CLI help.
303 | -v, --verbose
304 |
305 | DESCRIPTION
306 | Removes a plugin from the CLI.
307 |
308 | ALIASES
309 | $ storengine plugins unlink
310 | $ storengine plugins remove
311 | ```
312 |
313 | ## `storengine plugins update`
314 |
315 | Update installed plugins.
316 |
317 | ```
318 | USAGE
319 | $ storengine plugins update [-h] [-v]
320 |
321 | FLAGS
322 | -h, --help Show CLI help.
323 | -v, --verbose
324 |
325 | DESCRIPTION
326 | Update installed plugins.
327 | ```
328 |
329 |
--------------------------------------------------------------------------------
/cli/bin/dev:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const oclif = require('@oclif/core')
4 |
5 | const path = require('path')
6 | const project = path.join(__dirname, '..', 'tsconfig.json')
7 |
8 | // In dev mode -> use ts-node and dev plugins
9 | process.env.NODE_ENV = 'development'
10 |
11 | require('ts-node').register({project})
12 |
13 | // In dev mode, always show stack traces
14 | oclif.settings.debug = true;
15 |
16 | // Start the CLI
17 | oclif.run().then(oclif.flush).catch(oclif.Errors.handle)
18 |
--------------------------------------------------------------------------------
/cli/bin/dev.cmd:
--------------------------------------------------------------------------------
1 | @echo off
2 |
3 | node "%~dp0\dev" %*
--------------------------------------------------------------------------------
/cli/bin/run:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const oclif = require('@oclif/core')
4 |
5 | oclif.run().then(require('@oclif/core/flush')).catch(require('@oclif/core/handle'))
6 |
--------------------------------------------------------------------------------
/cli/bin/run.cmd:
--------------------------------------------------------------------------------
1 | @echo off
2 |
3 | node "%~dp0\run" %*
4 |
--------------------------------------------------------------------------------
/cli/cdk.json:
--------------------------------------------------------------------------------
1 | {
2 | "app": "dist/infra/main.js",
3 | "context": {
4 | "@aws-cdk/aws-lambda:recognizeLayerVersion": true,
5 | "@aws-cdk/core:checkSecretUsage": true,
6 | "@aws-cdk/core:target-partitions": [
7 | "aws",
8 | "aws-cn"
9 | ],
10 | "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
11 | "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
12 | "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
13 | "@aws-cdk/aws-iam:minimizePolicies": true,
14 | "@aws-cdk/core:validateSnapshotRemovalPolicy": true,
15 | "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
16 | "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
17 | "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
18 | "@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
19 | "@aws-cdk/core:enablePartitionLiterals": true,
20 | "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
21 | "@aws-cdk/aws-iam:standardizedServicePrincipals": true,
22 | "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
23 | "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
24 | "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
25 | "@aws-cdk/aws-route53-patters:useCertificate": true,
26 | "@aws-cdk/customresources:installLatestAwsSdkDefault": false,
27 | "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true,
28 | "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true,
29 | "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true,
30 | "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true,
31 | "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true,
32 | "@aws-cdk/aws-redshift:columnId": true,
33 | "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true,
34 | "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true,
35 | "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true
36 | }
37 | }
--------------------------------------------------------------------------------
/cli/oclif.md:
--------------------------------------------------------------------------------
1 | # oclif-hello-world
2 |
3 | oclif example Hello World CLI
4 |
5 | [](https://oclif.io)
6 | [](https://npmjs.org/package/oclif-hello-world)
7 | [](https://circleci.com/gh/oclif/hello-world/tree/main)
8 | [](https://npmjs.org/package/oclif-hello-world)
9 | [](https://github.com/oclif/hello-world/blob/main/package.json)
10 |
11 |
12 | * [oclif-hello-world](#oclif-hello-world)
13 | * [Usage](#usage)
14 | * [Commands](#commands)
15 |
16 |
17 | # Usage
18 |
19 |
20 | ```sh-session
21 | $ npm install -g @storengine/cli
22 | $ storengine COMMAND
23 | running command...
24 | $ storengine (--version)
25 | @storengine/cli/0.0.9 darwin-arm64 node-v18.15.0
26 | $ storengine --help [COMMAND]
27 | USAGE
28 | $ storengine COMMAND
29 | ...
30 | ```
31 |
32 |
33 | # Commands
34 |
35 |
36 | * [`storengine cross-acc`](#storengine-cross-acc)
37 | * [`storengine help [COMMANDS]`](#storengine-help-commands)
38 | * [`storengine init USERNAME TEMPLATEID`](#storengine-init-username-templateid)
39 | * [`storengine plugins`](#storengine-plugins)
40 | * [`storengine plugins:install PLUGIN...`](#storengine-pluginsinstall-plugin)
41 | * [`storengine plugins:inspect PLUGIN...`](#storengine-pluginsinspect-plugin)
42 | * [`storengine plugins:install PLUGIN...`](#storengine-pluginsinstall-plugin-1)
43 | * [`storengine plugins:link PLUGIN`](#storengine-pluginslink-plugin)
44 | * [`storengine plugins:uninstall PLUGIN...`](#storengine-pluginsuninstall-plugin)
45 | * [`storengine plugins:uninstall PLUGIN...`](#storengine-pluginsuninstall-plugin-1)
46 | * [`storengine plugins:uninstall PLUGIN...`](#storengine-pluginsuninstall-plugin-2)
47 | * [`storengine plugins update`](#storengine-plugins-update)
48 |
49 | ## `storengine cross-acc`
50 |
51 | ```
52 | USAGE
53 | $ storengine cross-acc
54 | ```
55 |
56 | _See code: [dist/commands/cross-acc.ts](https://github.com/davincios/storengine/blob/v0.0.9/dist/commands/cross-acc.ts)_
57 |
58 | ## `storengine help [COMMANDS]`
59 |
60 | Display help for storengine.
61 |
62 | ```
63 | USAGE
64 | $ storengine help [COMMANDS] [-n]
65 |
66 | ARGUMENTS
67 | COMMANDS Command to show help for.
68 |
69 | FLAGS
70 | -n, --nested-commands Include all nested commands in the output.
71 |
72 | DESCRIPTION
73 | Display help for storengine.
74 | ```
75 |
76 | _See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v5.2.9/src/commands/help.ts)_
77 |
78 | ## `storengine init USERNAME TEMPLATEID`
79 |
80 | Creates an upload demo in your project, consisting of an API route and page component.
81 |
82 | ```
83 | USAGE
84 | $ storengine init USERNAME TEMPLATEID
85 |
86 | ARGUMENTS
87 | USERNAME Your username
88 | TEMPLATEID The ID of the template to use
89 |
90 | DESCRIPTION
91 | Creates an upload demo in your project, consisting of an API route and page component.
92 | ```
93 |
94 | _See code: [dist/commands/init/index.ts](https://github.com/davincios/storengine/blob/v0.0.9/dist/commands/init/index.ts)_
95 |
96 | ## `storengine plugins`
97 |
98 | List installed plugins.
99 |
100 | ```
101 | USAGE
102 | $ storengine plugins [--core]
103 |
104 | FLAGS
105 | --core Show core plugins.
106 |
107 | DESCRIPTION
108 | List installed plugins.
109 |
110 | EXAMPLES
111 | $ storengine plugins
112 | ```
113 |
114 | _See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v2.4.7/src/commands/plugins/index.ts)_
115 |
116 | ## `storengine plugins:install PLUGIN...`
117 |
118 | Installs a plugin into the CLI.
119 |
120 | ```
121 | USAGE
122 | $ storengine plugins:install PLUGIN...
123 |
124 | ARGUMENTS
125 | PLUGIN Plugin to install.
126 |
127 | FLAGS
128 | -f, --force Run yarn install with force flag.
129 | -h, --help Show CLI help.
130 | -v, --verbose
131 |
132 | DESCRIPTION
133 | Installs a plugin into the CLI.
134 | Can be installed from npm or a git url.
135 |
136 | Installation of a user-installed plugin will override a core plugin.
137 |
138 | e.g. If you have a core plugin that has a 'hello' command, installing a user-installed plugin with a 'hello' command
139 | will override the core plugin implementation. This is useful if a user needs to update core plugin functionality in
140 | the CLI without the need to patch and update the whole CLI.
141 |
142 |
143 | ALIASES
144 | $ storengine plugins add
145 |
146 | EXAMPLES
147 | $ storengine plugins:install myplugin
148 |
149 | $ storengine plugins:install https://github.com/someuser/someplugin
150 |
151 | $ storengine plugins:install someuser/someplugin
152 | ```
153 |
154 | ## `storengine plugins:inspect PLUGIN...`
155 |
156 | Displays installation properties of a plugin.
157 |
158 | ```
159 | USAGE
160 | $ storengine plugins:inspect PLUGIN...
161 |
162 | ARGUMENTS
163 | PLUGIN [default: .] Plugin to inspect.
164 |
165 | FLAGS
166 | -h, --help Show CLI help.
167 | -v, --verbose
168 |
169 | GLOBAL FLAGS
170 | --json Format output as json.
171 |
172 | DESCRIPTION
173 | Displays installation properties of a plugin.
174 |
175 | EXAMPLES
176 | $ storengine plugins:inspect myplugin
177 | ```
178 |
179 | ## `storengine plugins:install PLUGIN...`
180 |
181 | Installs a plugin into the CLI.
182 |
183 | ```
184 | USAGE
185 | $ storengine plugins:install PLUGIN...
186 |
187 | ARGUMENTS
188 | PLUGIN Plugin to install.
189 |
190 | FLAGS
191 | -f, --force Run yarn install with force flag.
192 | -h, --help Show CLI help.
193 | -v, --verbose
194 |
195 | DESCRIPTION
196 | Installs a plugin into the CLI.
197 | Can be installed from npm or a git url.
198 |
199 | Installation of a user-installed plugin will override a core plugin.
200 |
201 | e.g. If you have a core plugin that has a 'hello' command, installing a user-installed plugin with a 'hello' command
202 | will override the core plugin implementation. This is useful if a user needs to update core plugin functionality in
203 | the CLI without the need to patch and update the whole CLI.
204 |
205 |
206 | ALIASES
207 | $ storengine plugins add
208 |
209 | EXAMPLES
210 | $ storengine plugins:install myplugin
211 |
212 | $ storengine plugins:install https://github.com/someuser/someplugin
213 |
214 | $ storengine plugins:install someuser/someplugin
215 | ```
216 |
217 | ## `storengine plugins:link PLUGIN`
218 |
219 | Links a plugin into the CLI for development.
220 |
221 | ```
222 | USAGE
223 | $ storengine plugins:link PLUGIN
224 |
225 | ARGUMENTS
226 | PATH [default: .] path to plugin
227 |
228 | FLAGS
229 | -h, --help Show CLI help.
230 | -v, --verbose
231 |
232 | DESCRIPTION
233 | Links a plugin into the CLI for development.
234 | Installation of a linked plugin will override a user-installed or core plugin.
235 |
236 | e.g. If you have a user-installed or core plugin that has a 'hello' command, installing a linked plugin with a 'hello'
237 | command will override the user-installed or core plugin implementation. This is useful for development work.
238 |
239 |
240 | EXAMPLES
241 | $ storengine plugins:link myplugin
242 | ```
243 |
244 | ## `storengine plugins:uninstall PLUGIN...`
245 |
246 | Removes a plugin from the CLI.
247 |
248 | ```
249 | USAGE
250 | $ storengine plugins:uninstall PLUGIN...
251 |
252 | ARGUMENTS
253 | PLUGIN plugin to uninstall
254 |
255 | FLAGS
256 | -h, --help Show CLI help.
257 | -v, --verbose
258 |
259 | DESCRIPTION
260 | Removes a plugin from the CLI.
261 |
262 | ALIASES
263 | $ storengine plugins unlink
264 | $ storengine plugins remove
265 | ```
266 |
267 | ## `storengine plugins:uninstall PLUGIN...`
268 |
269 | Removes a plugin from the CLI.
270 |
271 | ```
272 | USAGE
273 | $ storengine plugins:uninstall PLUGIN...
274 |
275 | ARGUMENTS
276 | PLUGIN plugin to uninstall
277 |
278 | FLAGS
279 | -h, --help Show CLI help.
280 | -v, --verbose
281 |
282 | DESCRIPTION
283 | Removes a plugin from the CLI.
284 |
285 | ALIASES
286 | $ storengine plugins unlink
287 | $ storengine plugins remove
288 | ```
289 |
290 | ## `storengine plugins:uninstall PLUGIN...`
291 |
292 | Removes a plugin from the CLI.
293 |
294 | ```
295 | USAGE
296 | $ storengine plugins:uninstall PLUGIN...
297 |
298 | ARGUMENTS
299 | PLUGIN plugin to uninstall
300 |
301 | FLAGS
302 | -h, --help Show CLI help.
303 | -v, --verbose
304 |
305 | DESCRIPTION
306 | Removes a plugin from the CLI.
307 |
308 | ALIASES
309 | $ storengine plugins unlink
310 | $ storengine plugins remove
311 | ```
312 |
313 | ## `storengine plugins update`
314 |
315 | Update installed plugins.
316 |
317 | ```
318 | USAGE
319 | $ storengine plugins update [-h] [-v]
320 |
321 | FLAGS
322 | -h, --help Show CLI help.
323 | -v, --verbose
324 |
325 | DESCRIPTION
326 | Update installed plugins.
327 | ```
328 |
329 |
--------------------------------------------------------------------------------
/cli/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@storengine/cli",
3 | "version": "0.0.9",
4 | "description": "storengine CLI",
5 | "author": "Vincent Hus @davincios",
6 | "bin": {
7 | "storengine": "bin/run"
8 | },
9 | "homepage": "https://github.com/davincios/storengine",
10 | "license": "UNLICENSED",
11 | "main": "dist/index.js",
12 | "repository": {
13 | "type": "git",
14 | "url": "git+https://github.com/davincios/storengine.git"
15 | },
16 | "files": [
17 | "/bin",
18 | "/dist",
19 | "/npm-shrinkwrap.json",
20 | "/oclif.manifest.json"
21 | ],
22 | "dependencies": {
23 | "@aws-sdk/client-iam": "3.354.0",
24 | "@oclif/core": "^2",
25 | "@oclif/plugin-help": "^5",
26 | "@oclif/plugin-plugins": "^2.4.6",
27 | "dotenv": "^16.0.3",
28 | "install": "^0.13.0",
29 | "log-symbols": "^5.1.0",
30 | "npm": "^9.7.1"
31 | },
32 | "devDependencies": {
33 | "@oclif/test": "^2.3.16",
34 | "@types/chai": "^4",
35 | "@types/mocha": "^9.0.0",
36 | "@types/node": "^16.18.24",
37 | "chai": "^4",
38 | "eslint": "^7.32.0",
39 | "eslint-config-oclif": "^4",
40 | "eslint-config-oclif-typescript": "^1.0.3",
41 | "mocha": "^9",
42 | "oclif": "^3",
43 | "shx": "^0.3.3",
44 | "ts-node": "^10.9.1",
45 | "tslib": "^2.5.0",
46 | "typescript": "^5.0"
47 | },
48 | "oclif": {
49 | "bin": "storengine",
50 | "dirname": "storengine",
51 | "commands": "./dist/commands",
52 | "plugins": [
53 | "@oclif/plugin-help",
54 | "@oclif/plugin-plugins"
55 | ],
56 | "topicSeparator": " ",
57 | "topics": {
58 | "hello": {
59 | "description": "Say hello to the world and others"
60 | }
61 | }
62 | },
63 | "scripts": {
64 | "build": "shx rm -rf dist && tsc -b",
65 | "postpack": "shx rm -f oclif.manifest.json",
66 | "prepack": "npm run build && oclif manifest && oclif readme",
67 | "test": "mocha --forbid-only \"test/**/*.test.ts\"",
68 | "version": "oclif readme && git add README.md",
69 | "init": "npm run build && node ./bin/run init 6494cc4dde7a3181e1aa6a71",
70 | "setup": "npm install"
71 | },
72 | "engines": {
73 | "node": ">=12.0.0"
74 | },
75 | "bugs": {
76 | "url": "https://github.com/davincios/storengine/issues"
77 | },
78 | "keywords": [
79 | "oclif"
80 | ],
81 | "types": "dist/index.d.ts",
82 | "directories": {
83 | "test": "test"
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/cli/src/assume-role.ts:
--------------------------------------------------------------------------------
1 | import { STSClient, AssumeRoleCommand } from "@aws-sdk/client-sts";
2 | import {
3 | IAwsAccountConfig,
4 | getEnvAwsAccountConfig,
5 | } from "./configure-aws-client";
6 |
7 | export async function assumeRole(
8 | targetAccountID: string,
9 | targetRoleName: string,
10 | targetExternalId: string,
11 | targetRegion: string
12 | ): Promise {
13 | if (!targetAccountID || !targetRoleName || !targetExternalId) {
14 | throw new Error(
15 | "Missing required input. Please provide targetAccountID, targetRoleName, and targetExternalId."
16 | );
17 | }
18 |
19 | const uniqueSessionName = `AssumedRoleSession_${Date.now()}`;
20 |
21 | const assumeRoleParams = {
22 | RoleArn: `arn:aws:iam::${targetAccountID}:role/${targetRoleName}`,
23 | RoleSessionName: uniqueSessionName,
24 | ExternalId: targetExternalId,
25 | };
26 |
27 | const config = await getEnvAwsAccountConfig();
28 | const stsClient = new STSClient(config);
29 |
30 | try {
31 | const assumeRoleCommand = new AssumeRoleCommand(assumeRoleParams);
32 | const data = await stsClient.send(assumeRoleCommand);
33 | const assumedCredentials = data.Credentials;
34 |
35 | return {
36 | region: `${targetRegion}`, // Replace with your desired AWS region
37 | credentials: {
38 | accessKeyId: `${assumedCredentials?.AccessKeyId}`,
39 | secretAccessKey: `${assumedCredentials?.SecretAccessKey}`,
40 | sessionToken: `${assumedCredentials?.SessionToken}`,
41 | },
42 | };
43 | } catch (err) {
44 | console.error("Error assuming role:", err);
45 | throw err;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/cli/src/commands/init.ts:
--------------------------------------------------------------------------------
1 | import { Command, Args } from "@oclif/core";
2 | import { createIamRoleInTargetAccount } from "../iam/index";
3 | import { getEnvAwsAccountConfig } from "../configure-aws-client";
4 |
5 | import { createInitFiles } from "../create-files";
6 | import { updateUser } from "../update-user";
7 | import { launchBucketApi } from "../launch-bucket";
8 | import { fetchUserById } from "../fetch-user";
9 |
10 | import { DEFAULT_ROLE_NAME, STATUS_IAM_ROLE_CREATED } from "../constants/index";
11 |
12 | export default class Init extends Command {
13 | static description =
14 | "Creates an upload demo in your nextjs project, consisting of an API route and upload demo page component.";
15 |
16 | static args = {
17 | userId: Args.string({
18 | name: "userId",
19 | required: true,
20 | description: "The ID of the user",
21 | }),
22 | };
23 |
24 | public async run(): Promise {
25 | try {
26 | const { args } = await this.parse(Init);
27 |
28 | const user = await fetchUserById(args.userId);
29 | const localAwsClientConfigForTarget = await getEnvAwsAccountConfig();
30 |
31 | const { userId, trustId, quickStartBucketName } = user;
32 |
33 | if (!quickStartBucketName) throw new Error("No quickstartbucket found");
34 | if (!userId) throw new Error("No user id found in the retrieved user");
35 | if (!trustId) throw new Error("No trust id found in the retrieved user");
36 |
37 | await createIamRoleInTargetAccount({
38 | targetAccountClientConfig: localAwsClientConfigForTarget,
39 | targetNewRoleName: DEFAULT_ROLE_NAME,
40 | trustId: trustId,
41 | });
42 |
43 | await updateUser(userId, {
44 | quickStartStatus: STATUS_IAM_ROLE_CREATED,
45 | targetAccountId: localAwsClientConfigForTarget.accountId,
46 | targetRoleName: DEFAULT_ROLE_NAME,
47 | iamRegion: localAwsClientConfigForTarget.region,
48 | });
49 |
50 | const reponse = await launchBucketApi({
51 | userId: userId,
52 | bucketParams: {
53 | region: localAwsClientConfigForTarget.region,
54 | templateName: "FILE_UPLOADS",
55 | serviceName: "quickstart-netrunner",
56 | },
57 | });
58 | console.log("[launchBucketApi] response", reponse);
59 | await createInitFiles(quickStartBucketName);
60 | } catch (error) {
61 | console.error(`Error in Init: ${error}`);
62 | throw error;
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/cli/src/configure-aws-client.ts:
--------------------------------------------------------------------------------
1 | import * as dotenv from "dotenv";
2 | import { STSClient, GetCallerIdentityCommand } from "@aws-sdk/client-sts";
3 |
4 | dotenv.config({ path: `.env.local`, override: true });
5 |
6 | export async function getLocalEnvAWSAccountId(): Promise {
7 | try {
8 | const stsClient = new STSClient({});
9 | const command = new GetCallerIdentityCommand({});
10 | const response = await stsClient.send(command);
11 | return response.Account ?? "";
12 | } catch (error) {
13 | console.error("Error retrieving default account ID:", error);
14 | throw error;
15 | }
16 | }
17 |
18 | export interface IAwsAccountConfig {
19 | region: string;
20 | accountId?: string;
21 | credentials: {
22 | accessKeyId: string;
23 | secretAccessKey: string;
24 | sessionToken?: string;
25 | };
26 | }
27 |
28 | function validateEnvVar(varName: string, varValue: string | undefined): void {
29 | if (!varValue) throw new Error(`${varName} must be set in the environment`);
30 | }
31 |
32 | export const getEnvAwsAccountConfig = async (): Promise => {
33 | const {
34 | AWS_ACCESS_KEY_ID,
35 | AWS_SECRET_ACCESS_KEY,
36 | AWS_REGION,
37 | AWS_ACCOUNT_ID: accountIdFromEnv,
38 | } = process.env;
39 |
40 | validateEnvVar("AWS_ACCESS_KEY_ID", AWS_ACCESS_KEY_ID);
41 | validateEnvVar("AWS_SECRET_ACCESS_KEY", AWS_SECRET_ACCESS_KEY);
42 | validateEnvVar("AWS_REGION", AWS_REGION);
43 |
44 | const AWS_ACCOUNT_ID = accountIdFromEnv ?? (await getLocalEnvAWSAccountId());
45 | validateEnvVar("AWS_ACCOUNT_ID", AWS_ACCOUNT_ID);
46 |
47 | return {
48 | region: "" + AWS_REGION,
49 | accountId: "" + AWS_ACCOUNT_ID,
50 | credentials: {
51 | accessKeyId: "" + AWS_ACCESS_KEY_ID,
52 | secretAccessKey: "" + AWS_SECRET_ACCESS_KEY,
53 | },
54 | };
55 | };
56 |
--------------------------------------------------------------------------------
/cli/src/constants/_code-gen-presigned-api.ts:
--------------------------------------------------------------------------------
1 | export const TEMPLATE_API = `
2 | import { NextApiHandler } from "next";
3 | import { apiSignS3Url } from "@storengine/client";
4 |
5 | const handler: NextApiHandler = async (req, res) => {
6 | if (req.method !== "GET")
7 | return res.status(405).json({ message: "Method not allowed" });
8 |
9 | try {
10 | const { filename, filetype } = req.query;
11 | const { signed_url } = await apiSignS3Url(filename, filetype);
12 | res.status(200).json({ signed_url });
13 | } catch (error) {
14 | console.error(error);
15 | res.status(500).json({ error: error.message });
16 | }
17 | };
18 |
19 | export default handler;
20 | `;
21 |
--------------------------------------------------------------------------------
/cli/src/constants/_code-gen-upload-component.ts:
--------------------------------------------------------------------------------
1 | export const TEMPLATE_UPLOAD_COMPONENT = `
2 |
3 | import { useFileUpload } from "@storengine/client";
4 |
5 | export default function UploadComponent() {
6 | const { FileUploadInput, uploadFile } = useFileUpload();
7 | // @ts-ignore
8 | return ;
9 | }
10 | `;
11 |
--------------------------------------------------------------------------------
/cli/src/constants/file-names.ts:
--------------------------------------------------------------------------------
1 | export const API_FILE_NAME = "upload-presigned.ts";
2 | export const PAGES_FILE_NAME = "upload-demo.tsx";
3 |
4 | export const PAGES_DIR_PATH = "pages";
5 | export const API_DIR_PATH = "pages/api";
6 | export const ENV_DIR_PATH = "";
7 |
--------------------------------------------------------------------------------
/cli/src/constants/index.ts:
--------------------------------------------------------------------------------
1 | // Main url
2 | export const DEFAULT_ROLE_NAME = `netrunner-S3FullAndCloudWatchRead`;
3 | export const NETRUNNER_API_URL = "https://connect.forge-api.com/netrunner/";
4 |
5 | // Operating IDs
6 | export const NETRUNNER_AWS_ACCOUNT_ID = "395261708130";
7 | export const TEST_USER_ID = "6494cc4dde7a3181e1aa6a71";
8 | export const TEST_EXTERNAL_CUSTOMER_ID = TEST_USER_ID;
9 |
10 | // Status constants
11 | export const STATUS_DEMO_COMPLETED = "DEMO_COMPLETED";
12 | export const STATUS_IAM_ROLE_CREATED = "STATUS_IAM_ROLE_CREATED";
13 |
--------------------------------------------------------------------------------
/cli/src/create-files.ts:
--------------------------------------------------------------------------------
1 | import { promises as fs } from "fs";
2 | import * as path from "path";
3 | import { TEMPLATE_API } from "./constants/_code-gen-presigned-api";
4 | import { TEMPLATE_UPLOAD_COMPONENT } from "./constants/_code-gen-upload-component";
5 | import {
6 | API_FILE_NAME,
7 | API_DIR_PATH,
8 | PAGES_FILE_NAME,
9 | PAGES_DIR_PATH,
10 | ENV_DIR_PATH,
11 | } from "./constants/file-names";
12 |
13 | async function createDirectory(directoryPath: string): Promise {
14 | try {
15 | await fs.mkdir(directoryPath, { recursive: true });
16 | } catch (err) {
17 | throw new Error(
18 | "[createFiles.ts] Error creating directory: " + directoryPath
19 | );
20 | }
21 | }
22 |
23 | async function writeFile(filePath: string, fileContent: string): Promise {
24 | await fs.writeFile(filePath, fileContent, "utf-8");
25 | }
26 |
27 | export interface IWriteFileResponse {
28 | apiFilePath: string;
29 | pagesFilePath: string;
30 | }
31 |
32 | export async function createInitFiles(
33 | bucketName: string
34 | ): Promise {
35 | // Define the path where to write the new api that will upload files to S3
36 | const apiDirPath = path.join(process.cwd(), API_DIR_PATH);
37 | const apiFilePath = path.join(apiDirPath, API_FILE_NAME);
38 |
39 | // define the path where to create a new page file for the upload demo
40 | const pagesDirPath = path.join(process.cwd(), PAGES_DIR_PATH);
41 | const pagesFilePath = path.join(pagesDirPath, PAGES_FILE_NAME);
42 |
43 | //
44 | const envDirPath = path.join(process.cwd(), ENV_DIR_PATH);
45 | const envFilePath = path.join(envDirPath, ".env.local");
46 |
47 | // Read the contents of the ".env" file in the pagesDirPath
48 | const envFileContent = await fs.readFile(envFilePath, "utf-8");
49 |
50 | // Define the code template of the new files. append the new bucketname to the existing .env.local file
51 | const apiCodeContent = TEMPLATE_API;
52 | const pagesCodeContent = TEMPLATE_UPLOAD_COMPONENT;
53 | const updatedEnvFileContent = `${envFileContent}\nS3_BUCKET_NAME=${bucketName}`;
54 |
55 | // Create the directories if they're not present yet
56 | await createDirectory(apiDirPath);
57 | await createDirectory(pagesDirPath);
58 |
59 | // Write the updated contents to the .env file
60 | await writeFile(envFilePath, updatedEnvFileContent);
61 |
62 | // Write the new files to disk
63 | await writeFile(apiFilePath, apiCodeContent);
64 | await writeFile(pagesFilePath, pagesCodeContent);
65 |
66 | return {
67 | apiFilePath,
68 | pagesFilePath,
69 | };
70 | }
71 |
--------------------------------------------------------------------------------
/cli/src/fetch-user.ts:
--------------------------------------------------------------------------------
1 | import { NETRUNNER_API_URL } from "./constants";
2 |
3 | export async function fetchUserById(userId: string): Promise {
4 | try {
5 | if (!userId) {
6 | throw new Error("[launchBucketApi.ts] userId is required.");
7 | }
8 | const url = NETRUNNER_API_URL;
9 | const params = {
10 | action: "FETCH_USER",
11 | userId: userId,
12 | };
13 |
14 | const response = await fetch(url, {
15 | method: "POST",
16 | headers: {
17 | "Content-Type": "application/json",
18 | },
19 | body: JSON.stringify(params),
20 | });
21 |
22 | const body = await response.json();
23 |
24 | if (!response.ok) {
25 | console.log("[fetch-user.ts] error response", body);
26 | throw new Error(`HTTP error! status: ${response.status}`);
27 | }
28 |
29 | return body;
30 | } catch (error) {
31 | console.log("\n");
32 | console.log(error);
33 | console.log("\n");
34 | console.error(`Error in launchBucketApi: ${error}\n`);
35 | // rethrow the error so it can be caught further up the call stack if necessary
36 | throw error;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/cli/src/iam/createRoleParams.ts:
--------------------------------------------------------------------------------
1 | import { CreateRoleCommandInput } from "@aws-sdk/client-iam";
2 |
3 | export function createRoleParams(
4 | targetNewRoleName: string,
5 | trustId: string
6 | ): CreateRoleCommandInput {
7 | const NETRUNNER_AWS_ACCOUNT_ID = "395261708130";
8 | const roleParams = {
9 | RoleName: targetNewRoleName,
10 | AssumeRolePolicyDocument: JSON.stringify({
11 | Version: "2012-10-17",
12 | Statement: [
13 | {
14 | Effect: "Allow",
15 | Principal: {
16 | AWS: [
17 | `arn:aws:iam::${NETRUNNER_AWS_ACCOUNT_ID}:user/netrunner-app`,
18 | ],
19 | },
20 | Action: "sts:AssumeRole",
21 | Condition: {
22 | StringEquals: {
23 | "sts:ExternalId": `${trustId}`,
24 | },
25 | },
26 | },
27 | ],
28 | }),
29 | };
30 |
31 | return roleParams;
32 | }
33 |
--------------------------------------------------------------------------------
/cli/src/iam/index.ts:
--------------------------------------------------------------------------------
1 | import {
2 | IAMClient,
3 | CreateRoleCommand,
4 | AttachRolePolicyCommand,
5 | GetRoleCommand,
6 | } from "@aws-sdk/client-iam";
7 | import { IAwsAccountConfig } from "../configure-aws-client";
8 | import { createRoleParams } from "./createRoleParams";
9 | import chalk from "chalk";
10 |
11 | export interface ICreateIamRoleInTargetAccountProps {
12 | targetNewRoleName: string;
13 | trustId: string;
14 | targetAccountClientConfig: IAwsAccountConfig;
15 | }
16 |
17 | async function doesRoleExist(iamClient: IAMClient, roleName: string) {
18 | const getRoleCommand = new GetRoleCommand({ RoleName: roleName });
19 |
20 | try {
21 | await iamClient.send(getRoleCommand);
22 | return true;
23 | } catch (err) {
24 | return false;
25 | }
26 | }
27 |
28 | async function createAndAttachIamPolicies(
29 | iamClient: IAMClient,
30 | roleName: string,
31 | trustId: string
32 | ) {
33 | const createRoleCommand = new CreateRoleCommand(
34 | createRoleParams(roleName, trustId)
35 | );
36 |
37 | try {
38 | const createRoleResponse = await iamClient.send(createRoleCommand);
39 | const roleArn = createRoleResponse?.Role?.Arn;
40 |
41 | console.log("[implement-policy.ts] createRoleResponse", createRoleResponse);
42 |
43 | const attachCloudWatchPolicyCommand = new AttachRolePolicyCommand({
44 | PolicyArn: "arn:aws:iam::aws:policy/CloudWatchLogsReadOnlyAccess",
45 | RoleName: roleName,
46 | });
47 | await iamClient.send(attachCloudWatchPolicyCommand);
48 |
49 | const attachS3PolicyCommand = new AttachRolePolicyCommand({
50 | PolicyArn: "arn:aws:iam::aws:policy/AmazonS3FullAccess",
51 | RoleName: roleName,
52 | });
53 | await iamClient.send(attachS3PolicyCommand);
54 | console.log(
55 | `[implement-policy.ts] Successfully created the ${roleName} role.\nRole ARN: ${roleArn}`
56 | );
57 | } catch (err) {
58 | console.error(
59 | `[implement-policy.ts] Error creating the ${roleName} role:`,
60 | err
61 | );
62 | return false;
63 | }
64 | }
65 |
66 | export async function createIamRoleInTargetAccount({
67 | targetNewRoleName,
68 | trustId,
69 | targetAccountClientConfig,
70 | }: ICreateIamRoleInTargetAccountProps): Promise {
71 | const { accountId } = targetAccountClientConfig;
72 |
73 | if (
74 | !accountId ||
75 | !targetNewRoleName ||
76 | !trustId ||
77 | !targetAccountClientConfig
78 | ) {
79 | throw new Error(
80 | "Missing required properties in ICreateIamRoleInTargetAccountProps"
81 | );
82 | }
83 |
84 | const iamClient = new IAMClient(targetAccountClientConfig);
85 |
86 | if (await doesRoleExist(iamClient, targetNewRoleName)) {
87 | console.log(
88 | chalk.redBright(
89 | `[implement-policy.ts] The role ${targetNewRoleName} already exists, skipping creation of policies\n`
90 | )
91 | );
92 | return true;
93 | }
94 |
95 | try {
96 | await createAndAttachIamPolicies(iamClient, targetNewRoleName, trustId);
97 | } catch (err) {
98 | console.error(`Error creating the ${targetNewRoleName} role:`, err);
99 | return false;
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/cli/src/index.ts:
--------------------------------------------------------------------------------
1 | export { run } from "@oclif/core";
2 |
--------------------------------------------------------------------------------
/cli/src/launch-bucket.ts:
--------------------------------------------------------------------------------
1 | import { NETRUNNER_API_URL } from "./constants";
2 |
3 | // Data types
4 | interface BucketParams {
5 | region: string;
6 | templateName: string;
7 | serviceName: string;
8 | }
9 |
10 | interface LaunchBucketApiParams {
11 | userId: string;
12 | bucketParams: BucketParams;
13 | }
14 |
15 | // Utility function to check input data
16 | function validateInput(params: LaunchBucketApiParams) {
17 | if (!params.userId) {
18 | throw new Error("[launchBucketApi.ts] userId is required.");
19 | }
20 |
21 | const { region, templateName, serviceName } = params.bucketParams;
22 | if (!region || !templateName || !serviceName) {
23 | throw new Error("[launchBucketApi.ts] Invalid bucket parameters.");
24 | }
25 | }
26 |
27 | export async function launchBucketApi(
28 | params: LaunchBucketApiParams
29 | ): Promise {
30 | try {
31 | // Validate input data
32 | validateInput(params);
33 |
34 | const url = NETRUNNER_API_URL;
35 |
36 | // Request parameters
37 | const requestParams = {
38 | action: "LAUNCH_NEW_BUCKET",
39 | userId: params.userId,
40 | params: params.bucketParams,
41 | };
42 |
43 | // Send request
44 | const response = await fetch(url, {
45 | method: "POST",
46 | headers: {
47 | "Content-Type": "application/json",
48 | },
49 | body: JSON.stringify(requestParams),
50 | });
51 |
52 | // Parse response
53 | const body = await response.json();
54 | if (!response.ok) {
55 | console.error("HTTP response error", body);
56 | throw new Error(
57 | `[launch-bucket.ts] HTTP error! status: ${response.status}`
58 | );
59 | }
60 |
61 | return body;
62 | } catch (error) {
63 | console.error(
64 | `[launch-bucket.ts] Error in launchBucketApi: ${JSON.stringify(error)}`
65 | );
66 | // tslint:disable-next-line: no-throw
67 | throw new Error(
68 | `[launch-bucket.ts] cError in launchBucketApi ${JSON.stringify(error)}`
69 | );
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/cli/src/update-status.ts:
--------------------------------------------------------------------------------
1 | import { NETRUNNER_API_URL } from "./constants";
2 |
3 | export async function updateStatus(
4 | userId: string,
5 | status: string
6 | ): Promise {
7 | try {
8 | if (!userId) throw new Error("[launchBucketApi.ts] userId is required.");
9 | if (!status) throw new Error("[launchBucketApi.ts] status is required.");
10 |
11 | const url = NETRUNNER_API_URL;
12 | const params = {
13 | action: "UPDATE_USER_STATUS",
14 | userId: userId,
15 | params: {
16 | status: status,
17 | },
18 | };
19 |
20 | const response = await fetch(url, {
21 | method: "POST",
22 | headers: {
23 | "Content-Type": "application/json",
24 | },
25 | body: JSON.stringify(params),
26 | });
27 |
28 | if (!response.ok) {
29 | const body = await response.text(); // or response.json() if you know the response is JSON
30 | console.log("response", body);
31 | throw new Error(`HTTP error! status: ${response.status}`);
32 | }
33 |
34 | return response;
35 | } catch (error) {
36 | console.log("\n");
37 | console.log(error);
38 | console.log("\n");
39 | console.error(`Error in launchBucketApi: ${error}\n`);
40 | // rethrow the error so it can be caught further up the call stack if necessary
41 | throw error;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/cli/src/update-user.ts:
--------------------------------------------------------------------------------
1 | import { NETRUNNER_API_URL } from "./constants";
2 |
3 | export async function updateUser(
4 | userId: string,
5 | toUpdateValues: any
6 | ): Promise {
7 | try {
8 | if (!userId) throw new Error("[update-user.ts] userId is required.");
9 | if (!toUpdateValues)
10 | throw new Error("[update-user.ts] toUpdateValues is required.");
11 |
12 | const url = NETRUNNER_API_URL;
13 | const params = {
14 | action: "UPDATE_USER",
15 | userId: userId,
16 | params: {
17 | updateParams: {
18 | ...toUpdateValues,
19 | anotherParam: "lol3293409349",
20 | },
21 | },
22 | };
23 |
24 | const response = await fetch(url, {
25 | method: "POST",
26 | headers: {
27 | "Content-Type": "application/json",
28 | },
29 | body: JSON.stringify(params),
30 | });
31 |
32 | const body = await response.text(); // or response.json() if you know the response is JSON
33 |
34 | if (!response.ok) {
35 | console.error("response", body);
36 | throw new Error(
37 | `HTTP error! status: ${response.status}. Response body: ${body}`
38 | );
39 | }
40 |
41 | return body;
42 | } catch (error) {
43 | console.error(`Error in launchBucketApi:`, error);
44 | // rethrow the error so it can be caught further up the call stack if necessary
45 | throw error;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/cli/test/commands/clean-up.ts:
--------------------------------------------------------------------------------
1 | import * as fs from "fs/promises";
2 | import * as path from "path";
3 | import chalk from "chalk";
4 | import { API_DIR_PATH, PAGES_DIR_PATH } from "../../src/constants/file-names";
5 |
6 | export async function cleanupTestFiles() {
7 | // Clean up any existing test files
8 | let pagesExist = false;
9 | let apiExist = false;
10 | try {
11 | await fs.access(path.join(process.cwd(), PAGES_DIR_PATH));
12 | pagesExist = true;
13 | } catch {}
14 |
15 | try {
16 | await fs.access(path.join(process.cwd(), API_DIR_PATH));
17 | apiExist = true;
18 | } catch {}
19 |
20 | if (pagesExist || apiExist) {
21 | console.log(
22 | "\n " +
23 | chalk.magenta("pages/api directory deleted") +
24 | (pagesExist ? `, ${chalk.magenta("pages directory deleted\n")}` : "")
25 | );
26 | }
27 |
28 | if (pagesExist) {
29 | await fs.rm(path.join(process.cwd(), PAGES_DIR_PATH), {
30 | recursive: true,
31 | force: true,
32 | });
33 | }
34 |
35 | if (apiExist) {
36 | await fs.rm(path.join(process.cwd(), API_DIR_PATH), {
37 | recursive: true,
38 | force: true,
39 | });
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/cli/test/commands/init.test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from "@oclif/test";
2 | import * as fs from "fs/promises";
3 | import * as path from "path";
4 | import Init from "../../src/commands/init";
5 | import {
6 | API_FILE_NAME,
7 | API_DIR_PATH,
8 | PAGES_FILE_NAME,
9 | PAGES_DIR_PATH,
10 | } from "../../src/constants/file-names";
11 | import { cleanupTestFiles } from "./clean-up";
12 | import { TEST_USER_ID } from "../../src/constants";
13 |
14 | describe("Init command", () => {
15 | test.it("exports the Init command", () => {
16 | expect(Init).to.not.be.undefined;
17 | });
18 |
19 | it("executes without throwing an error", async () => {
20 | await cleanupTestFiles();
21 |
22 | // Call the run() function within a try-catch block.
23 | try {
24 | // execute the Init command
25 | await Init.run([TEST_USER_ID]);
26 | // If no error is thrown, the test will pass.
27 | } catch (error) {
28 | // If an error is thrown, fail the test.
29 | throw new Error(`The Init command threw an error: ${error}`);
30 | }
31 | });
32 |
33 | test.it("creates new files and logs their paths", async () => {
34 | const apiFilePath = path.join(process.cwd(), API_DIR_PATH, API_FILE_NAME);
35 | const pagesFilePath = path.join(
36 | process.cwd(),
37 | PAGES_DIR_PATH,
38 | PAGES_FILE_NAME
39 | );
40 |
41 | expect(await fs.access(apiFilePath)).to.not.throw;
42 | expect(await fs.access(pagesFilePath)).to.not.throw;
43 |
44 | // Clean up the test files after the test
45 | // await cleanupTestFiles();
46 | });
47 | });
48 |
--------------------------------------------------------------------------------
/cli/test/helpers/init.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | process.env.TS_NODE_PROJECT = path.resolve('test/tsconfig.json')
3 | process.env.NODE_ENV = 'development'
4 |
5 | global.oclif = global.oclif || {}
6 | global.oclif.columns = 80
7 |
--------------------------------------------------------------------------------
/cli/test/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig",
3 | "compilerOptions": {
4 | "noEmit": true
5 | }
6 | // "references": [
7 | // {"path": ".."}
8 | // ]
9 | }
10 |
--------------------------------------------------------------------------------
/cli/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "esModuleInterop": true,
4 | "declaration": true,
5 | "importHelpers": true,
6 | "module": "commonjs",
7 | "outDir": "dist",
8 | "rootDir": "src",
9 | "strict": true,
10 | "target": "es2019"
11 | },
12 | "include": ["src/**/*", "src/*"],
13 | "exclude": ["node_modules", "dist", "cdk"]
14 | }
15 |
--------------------------------------------------------------------------------
/client/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | ignorePatterns: ['dist'],
3 | parserOptions: {
4 | ecmaVersion: 2020,
5 | sourceType: 'module',
6 | project: 'packages/client/tsconfig.json'
7 | },
8 | rules: {
9 | '@typescript-eslint/no-floating-promises': 'error',
10 | '@typescript-eslint/strict-boolean-expressions': ['error', { allowNullableString: true, allowNullableObject: true }],
11 | "@typescript-eslint/ban-types": "off",
12 | }
13 | };
--------------------------------------------------------------------------------
/client/.gitignore:
--------------------------------------------------------------------------------
1 | # Auto generated version file
2 | src/version.ts
3 |
4 | node_modules
5 | node_modules/
6 | node_modules/*
7 |
8 | ROADMAP.md
--------------------------------------------------------------------------------
/client/.npmignore:
--------------------------------------------------------------------------------
1 | src
2 | tsconfig.json
3 | rollup.config.mjs
4 | .eslintrc.cjs
5 | .gitignore
--------------------------------------------------------------------------------
/client/README.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/client/dist/index.cjs:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var clientS3 = require('@aws-sdk/client-s3');
4 | var uuid = require('uuid');
5 | var s3RequestPresigner = require('@aws-sdk/s3-request-presigner');
6 | var axios = require('axios');
7 | var React = require('react');
8 |
9 | function validateParams(params) {
10 | const { accessKeyId, secretAccessKey, bucket, region } = params;
11 | if (!accessKeyId) {
12 | throw new Error("Missing AWS_ACCESS_KEY_ID environment variable");
13 | }
14 | if (!secretAccessKey) {
15 | throw new Error("Missing AWS_SECRET_ACCESS_KEY environment variable");
16 | }
17 | if (!bucket) {
18 | console.warn(
19 | "\x1B[41m\x1B[31m%s\x1B[0m",
20 | "Missing BUCKET_NAME environment variable"
21 | );
22 | }
23 | if (!region) {
24 | console.warn(
25 | "\x1B[41m\x1B[31m%s\x1B[0m",
26 | "Missing AWS_REGION environment variable"
27 | );
28 | }
29 | }
30 | function getConfig(params) {
31 | validateParams(params);
32 | const { accessKeyId, secretAccessKey, bucket, region } = params;
33 | const _DEFAULT_BUCKET_NAME = `--undefined-bucket-name-${uuid.v4()}`;
34 | const _DEFAULT_REGION = "us-east-1";
35 | const config = {
36 | accessKeyId: `${accessKeyId}`,
37 | secretAccessKey: `${secretAccessKey}`,
38 | region: `${region || _DEFAULT_REGION}`,
39 | bucket: `${bucket || _DEFAULT_BUCKET_NAME}`
40 | };
41 | return config;
42 | }
43 |
44 | function getS3Client(config) {
45 | let client = new clientS3.S3Client({
46 | credentials: {
47 | accessKeyId: config.accessKeyId,
48 | secretAccessKey: config.secretAccessKey
49 | },
50 | region: config.region
51 | });
52 | return client;
53 | }
54 | const getS3ClientNative = () => {
55 | const params = {
56 | accessKeyId: process.env.AWS_ACCESS_KEY_ID,
57 | secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
58 | region: process.env.AWS_REGION,
59 | bucket: process.env.S3_BUCKET_NAME
60 | };
61 | let config = getConfig(params);
62 | return getS3Client(config);
63 | };
64 |
65 | function validateStringInput(input) {
66 | if (!input) {
67 | throw new Error("Input cannot be empty");
68 | } else if (Array.isArray(input)) {
69 | return input.join(",");
70 | } else {
71 | return input;
72 | }
73 | }
74 |
75 | async function apiSignS3Url(filename, filetype) {
76 | const client = getS3ClientNative();
77 | filename = validateStringInput(filename);
78 | filetype = validateStringInput(filetype);
79 | const objectKey = `${filename}-${Date.now()}`;
80 | const params = {
81 | Bucket: process.env.S3_BUCKET_NAME,
82 | Key: objectKey,
83 | ContentType: filetype,
84 | CacheControl: "max-age=630720000",
85 | Metadata: {
86 | metadata1: "value12",
87 | metadata2: "value2"
88 | }
89 | };
90 | const command = new clientS3.PutObjectCommand(params);
91 | const signed_url = await s3RequestPresigner.getSignedUrl(client, command, { expiresIn: 3600 });
92 | return { signed_url, params, command };
93 | }
94 |
95 | function FileUploadInput({ handleUpload }) {
96 | const handleFileChange = (e) => {
97 | e.preventDefault();
98 | const file = e.target.files?.[0];
99 | if (!file) {
100 | return alert("No file selected.");
101 | } else {
102 | handleUpload(file);
103 | }
104 | };
105 | return /* @__PURE__ */ React.createElement(
106 | "input",
107 | {
108 | type: "file",
109 | accept: "image/png, image/jpeg",
110 | onChange: handleFileChange
111 | }
112 | );
113 | }
114 |
115 | function validateS3SignedUrl(url) {
116 | const regex = new RegExp(/^(ftp|http|https):\/\/[^ "]+$/);
117 | if (!regex.test(url)) {
118 | throw new Error("[UploadSection/uploadFile] Invalid URL");
119 | }
120 | }
121 | async function fetchPresignedUrl(filename, filetype) {
122 | const res = await fetch(
123 | `/api/upload-presigned?filename=${filename}&filetype=${encodeURIComponent(
124 | filetype
125 | )}`
126 | );
127 | const signed_url = (await res.json()).signed_url;
128 | validateS3SignedUrl(signed_url);
129 | return signed_url;
130 | }
131 | async function uploadFile(file) {
132 | if (!file) {
133 | throw new Error("[UploadSection/uploadFile] NO FILE");
134 | }
135 | const signed_url = await fetchPresignedUrl(
136 | file.name || "unknown-front-end-filename",
137 | file.type
138 | );
139 | try {
140 | const upload_response = await axios.put(signed_url, file);
141 | return upload_response;
142 | } catch (err) {
143 | console.error({ err });
144 | return err;
145 | }
146 | }
147 | const useFileUpload = () => ({
148 | FileUploadInput,
149 | uploadFile
150 | });
151 |
152 | function demo(a, b) {
153 | console.log("The package is correctly compiled and distributed!");
154 | return a + b;
155 | }
156 |
157 | exports.apiSignS3Url = apiSignS3Url;
158 | exports.demo = demo;
159 | exports.fetchPresignedUrl = fetchPresignedUrl;
160 | exports.getConfig = getConfig;
161 | exports.getS3Client = getS3Client;
162 | exports.getS3ClientNative = getS3ClientNative;
163 | exports.uploadFile = uploadFile;
164 | exports.useFileUpload = useFileUpload;
165 | //# sourceMappingURL=index.cjs.map
166 |
--------------------------------------------------------------------------------
/client/dist/index.cjs.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"index.cjs","sources":["../src/util/config.ts","../src/util/s3-client.ts","../src/util/helpers.ts","../src/nextjs/s3-presigned-api.ts","../src/nextjs/components/FileUploadInput.tsx","../src/nextjs/file-upload-hook.ts","../src/client.ts"],"sourcesContent":["import { v4 as uuidv4 } from \"uuid\";\n\nexport type TS3Config = {\n accessKeyId: string;\n secretAccessKey: string;\n bucket: string;\n region: string;\n};\n\nexport type TGetConfigParams = {\n accessKeyId: string | undefined;\n secretAccessKey: string | undefined;\n bucket: string | undefined;\n region: string | undefined;\n};\n\nfunction validateParams(params: TGetConfigParams) {\n const { accessKeyId, secretAccessKey, bucket, region } = params;\n\n if (!accessKeyId) {\n throw new Error(\"Missing AWS_ACCESS_KEY_ID environment variable\");\n }\n\n if (!secretAccessKey) {\n throw new Error(\"Missing AWS_SECRET_ACCESS_KEY environment variable\");\n }\n if (!bucket) {\n console.warn(\n \"\\x1b[41m\\x1b[31m%s\\x1b[0m\",\n \"Missing BUCKET_NAME environment variable\"\n );\n }\n\n if (!region) {\n console.warn(\n \"\\x1b[41m\\x1b[31m%s\\x1b[0m\",\n \"Missing AWS_REGION environment variable\"\n );\n }\n}\n\nexport function getConfig(params: TGetConfigParams): TS3Config {\n validateParams(params);\n\n const { accessKeyId, secretAccessKey, bucket, region } = params;\n\n // If bucket or region are not defined, we use a default value\n const _DEFAULT_BUCKET_NAME = `--undefined-bucket-name-${uuidv4()}`;\n const _DEFAULT_REGION = \"us-east-1\";\n\n const config: TS3Config = {\n accessKeyId: `${accessKeyId}`,\n secretAccessKey: `${secretAccessKey}`,\n region: `${region || _DEFAULT_REGION}`,\n bucket: `${bucket || _DEFAULT_BUCKET_NAME}`,\n };\n\n return config;\n}\n","import { S3Client } from \"@aws-sdk/client-s3\";\nimport { TS3Config, getConfig } from \"./config\";\n\nexport function getS3Client(config: TS3Config) {\n let client = new S3Client({\n credentials: {\n accessKeyId: config.accessKeyId,\n secretAccessKey: config.secretAccessKey,\n },\n region: config.region,\n });\n\n return client;\n}\n\nexport const getS3ClientNative = () => {\n // this version of the function gets the config from the environment variables\n const params = {\n accessKeyId: process.env.AWS_ACCESS_KEY_ID,\n secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,\n region: process.env.AWS_REGION,\n bucket: process.env.S3_BUCKET_NAME,\n };\n\n let config = getConfig(params);\n\n return getS3Client(config);\n};\n","export function validateStringInput(input: string | string[]): string {\n // convert string of arrays or string to string / used in apiSignS3Url\n if (!input) {\n throw new Error(\"Input cannot be empty\");\n } else if (Array.isArray(input)) {\n return input.join(\",\");\n } else {\n return input;\n }\n}\n","import { PutObjectCommand } from \"@aws-sdk/client-s3\";\nimport { getSignedUrl } from \"@aws-sdk/s3-request-presigner\";\nimport { getS3ClientNative } from \"../util\";\nimport { validateStringInput } from \"../util/helpers\";\n\ninterface S3UploadResponse {\n signed_url: string;\n params: any;\n command: any;\n}\n\nexport async function apiSignS3Url(\n filename: string | string[],\n filetype: string | string[]\n): Promise {\n const client = getS3ClientNative();\n filename = validateStringInput(filename);\n filetype = validateStringInput(filetype);\n\n const objectKey = `${filename}-${Date.now()}`;\n const params = {\n Bucket: process.env.S3_BUCKET_NAME,\n Key: objectKey,\n ContentType: filetype,\n CacheControl: \"max-age=630720000\",\n Metadata: {\n metadata1: \"value12\",\n metadata2: \"value2\",\n },\n };\n\n const command = new PutObjectCommand(params);\n const signed_url = await getSignedUrl(client, command, { expiresIn: 3600 });\n\n return { signed_url, params, command };\n}\n","import React from \"react\";\n\nexport function FileUploadInput({ handleUpload }: { handleUpload: any }) {\n const handleFileChange = (e: React.ChangeEvent) => {\n e.preventDefault();\n const file = e.target.files?.[0]!;\n if (!file) {\n return alert(\"No file selected.\");\n } else {\n handleUpload(file);\n }\n };\n\n return (\n \n );\n}\n","import axios from \"axios\";\nimport { FileUploadInput } from \"./components/FileUploadInput\";\n\nfunction validateS3SignedUrl(url: string) {\n const regex = new RegExp(/^(ftp|http|https):\\/\\/[^ \"]+$/);\n\n if (!regex.test(url)) {\n throw new Error(\"[UploadSection/uploadFile] Invalid URL\");\n }\n}\n\nexport async function fetchPresignedUrl(\n filename: string,\n filetype: string\n): Promise {\n const res = await fetch(\n `/api/upload-presigned?filename=${filename}&filetype=${encodeURIComponent(\n filetype\n )}`\n );\n const signed_url = (await res.json()).signed_url;\n validateS3SignedUrl(signed_url);\n return signed_url;\n}\n\nexport async function uploadFile(file: File): Promise {\n if (!file) {\n throw new Error(\"[UploadSection/uploadFile] NO FILE\");\n }\n\n const signed_url = await fetchPresignedUrl(\n file.name || \"unknown-front-end-filename\",\n file.type\n );\n\n try {\n const upload_response = await axios.put(signed_url, file);\n return upload_response;\n } catch (err) {\n console.error({ err });\n return err;\n }\n}\n\nexport const useFileUpload = () => ({\n FileUploadInput,\n uploadFile,\n});\n","export * from \"./util/s3-client\";\nexport * from \"./util/config\";\nexport * from \"./nextjs/s3-presigned-api\";\nexport * from \"./nextjs/file-upload-hook\";\n\n// for testing purposes should be removed at some point\nexport function demo(a: number, b: number): number {\n console.log(\"The package is correctly compiled and distributed!\");\n return a + b;\n}\n"],"names":["uuidv4","S3Client","PutObjectCommand","getSignedUrl"],"mappings":"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"}
--------------------------------------------------------------------------------
/client/dist/index.d.ts:
--------------------------------------------------------------------------------
1 | import { S3Client } from '@aws-sdk/client-s3';
2 |
3 | type TS3Config = {
4 | accessKeyId: string;
5 | secretAccessKey: string;
6 | bucket: string;
7 | region: string;
8 | };
9 | type TGetConfigParams = {
10 | accessKeyId: string | undefined;
11 | secretAccessKey: string | undefined;
12 | bucket: string | undefined;
13 | region: string | undefined;
14 | };
15 | declare function getConfig(params: TGetConfigParams): TS3Config;
16 |
17 | declare function getS3Client(config: TS3Config): S3Client;
18 | declare const getS3ClientNative: () => S3Client;
19 |
20 | interface S3UploadResponse {
21 | signed_url: string;
22 | params: any;
23 | command: any;
24 | }
25 | declare function apiSignS3Url(filename: string | string[], filetype: string | string[]): Promise;
26 |
27 | declare function FileUploadInput({ handleUpload }: {
28 | handleUpload: any;
29 | }): JSX.Element;
30 |
31 | declare function fetchPresignedUrl(filename: string, filetype: string): Promise;
32 | declare function uploadFile(file: File): Promise;
33 | declare const useFileUpload: () => {
34 | FileUploadInput: typeof FileUploadInput;
35 | uploadFile: typeof uploadFile;
36 | };
37 |
38 | declare function demo(a: number, b: number): number;
39 |
40 | export { TGetConfigParams, TS3Config, apiSignS3Url, demo, fetchPresignedUrl, getConfig, getS3Client, getS3ClientNative, uploadFile, useFileUpload };
41 |
--------------------------------------------------------------------------------
/client/dist/index.mjs:
--------------------------------------------------------------------------------
1 | import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
2 | import { v4 } from 'uuid';
3 | import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
4 | import axios from 'axios';
5 | import React from 'react';
6 |
7 | function validateParams(params) {
8 | const { accessKeyId, secretAccessKey, bucket, region } = params;
9 | if (!accessKeyId) {
10 | throw new Error("Missing AWS_ACCESS_KEY_ID environment variable");
11 | }
12 | if (!secretAccessKey) {
13 | throw new Error("Missing AWS_SECRET_ACCESS_KEY environment variable");
14 | }
15 | if (!bucket) {
16 | console.warn(
17 | "\x1B[41m\x1B[31m%s\x1B[0m",
18 | "Missing BUCKET_NAME environment variable"
19 | );
20 | }
21 | if (!region) {
22 | console.warn(
23 | "\x1B[41m\x1B[31m%s\x1B[0m",
24 | "Missing AWS_REGION environment variable"
25 | );
26 | }
27 | }
28 | function getConfig(params) {
29 | validateParams(params);
30 | const { accessKeyId, secretAccessKey, bucket, region } = params;
31 | const _DEFAULT_BUCKET_NAME = `--undefined-bucket-name-${v4()}`;
32 | const _DEFAULT_REGION = "us-east-1";
33 | const config = {
34 | accessKeyId: `${accessKeyId}`,
35 | secretAccessKey: `${secretAccessKey}`,
36 | region: `${region || _DEFAULT_REGION}`,
37 | bucket: `${bucket || _DEFAULT_BUCKET_NAME}`
38 | };
39 | return config;
40 | }
41 |
42 | function getS3Client(config) {
43 | let client = new S3Client({
44 | credentials: {
45 | accessKeyId: config.accessKeyId,
46 | secretAccessKey: config.secretAccessKey
47 | },
48 | region: config.region
49 | });
50 | return client;
51 | }
52 | const getS3ClientNative = () => {
53 | const params = {
54 | accessKeyId: process.env.AWS_ACCESS_KEY_ID,
55 | secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
56 | region: process.env.AWS_REGION,
57 | bucket: process.env.S3_BUCKET_NAME
58 | };
59 | let config = getConfig(params);
60 | return getS3Client(config);
61 | };
62 |
63 | function validateStringInput(input) {
64 | if (!input) {
65 | throw new Error("Input cannot be empty");
66 | } else if (Array.isArray(input)) {
67 | return input.join(",");
68 | } else {
69 | return input;
70 | }
71 | }
72 |
73 | async function apiSignS3Url(filename, filetype) {
74 | const client = getS3ClientNative();
75 | filename = validateStringInput(filename);
76 | filetype = validateStringInput(filetype);
77 | const objectKey = `${filename}-${Date.now()}`;
78 | const params = {
79 | Bucket: process.env.S3_BUCKET_NAME,
80 | Key: objectKey,
81 | ContentType: filetype,
82 | CacheControl: "max-age=630720000",
83 | Metadata: {
84 | metadata1: "value12",
85 | metadata2: "value2"
86 | }
87 | };
88 | const command = new PutObjectCommand(params);
89 | const signed_url = await getSignedUrl(client, command, { expiresIn: 3600 });
90 | return { signed_url, params, command };
91 | }
92 |
93 | function FileUploadInput({ handleUpload }) {
94 | const handleFileChange = (e) => {
95 | e.preventDefault();
96 | const file = e.target.files?.[0];
97 | if (!file) {
98 | return alert("No file selected.");
99 | } else {
100 | handleUpload(file);
101 | }
102 | };
103 | return /* @__PURE__ */ React.createElement(
104 | "input",
105 | {
106 | type: "file",
107 | accept: "image/png, image/jpeg",
108 | onChange: handleFileChange
109 | }
110 | );
111 | }
112 |
113 | function validateS3SignedUrl(url) {
114 | const regex = new RegExp(/^(ftp|http|https):\/\/[^ "]+$/);
115 | if (!regex.test(url)) {
116 | throw new Error("[UploadSection/uploadFile] Invalid URL");
117 | }
118 | }
119 | async function fetchPresignedUrl(filename, filetype) {
120 | const res = await fetch(
121 | `/api/upload-presigned?filename=${filename}&filetype=${encodeURIComponent(
122 | filetype
123 | )}`
124 | );
125 | const signed_url = (await res.json()).signed_url;
126 | validateS3SignedUrl(signed_url);
127 | return signed_url;
128 | }
129 | async function uploadFile(file) {
130 | if (!file) {
131 | throw new Error("[UploadSection/uploadFile] NO FILE");
132 | }
133 | const signed_url = await fetchPresignedUrl(
134 | file.name || "unknown-front-end-filename",
135 | file.type
136 | );
137 | try {
138 | const upload_response = await axios.put(signed_url, file);
139 | return upload_response;
140 | } catch (err) {
141 | console.error({ err });
142 | return err;
143 | }
144 | }
145 | const useFileUpload = () => ({
146 | FileUploadInput,
147 | uploadFile
148 | });
149 |
150 | function demo(a, b) {
151 | console.log("The package is correctly compiled and distributed!");
152 | return a + b;
153 | }
154 |
155 | export { apiSignS3Url, demo, fetchPresignedUrl, getConfig, getS3Client, getS3ClientNative, uploadFile, useFileUpload };
156 | //# sourceMappingURL=index.mjs.map
157 |
--------------------------------------------------------------------------------
/client/dist/index.mjs.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"index.mjs","sources":["../src/util/config.ts","../src/util/s3-client.ts","../src/util/helpers.ts","../src/nextjs/s3-presigned-api.ts","../src/nextjs/components/FileUploadInput.tsx","../src/nextjs/file-upload-hook.ts","../src/client.ts"],"sourcesContent":["import { v4 as uuidv4 } from \"uuid\";\n\nexport type TS3Config = {\n accessKeyId: string;\n secretAccessKey: string;\n bucket: string;\n region: string;\n};\n\nexport type TGetConfigParams = {\n accessKeyId: string | undefined;\n secretAccessKey: string | undefined;\n bucket: string | undefined;\n region: string | undefined;\n};\n\nfunction validateParams(params: TGetConfigParams) {\n const { accessKeyId, secretAccessKey, bucket, region } = params;\n\n if (!accessKeyId) {\n throw new Error(\"Missing AWS_ACCESS_KEY_ID environment variable\");\n }\n\n if (!secretAccessKey) {\n throw new Error(\"Missing AWS_SECRET_ACCESS_KEY environment variable\");\n }\n if (!bucket) {\n console.warn(\n \"\\x1b[41m\\x1b[31m%s\\x1b[0m\",\n \"Missing BUCKET_NAME environment variable\"\n );\n }\n\n if (!region) {\n console.warn(\n \"\\x1b[41m\\x1b[31m%s\\x1b[0m\",\n \"Missing AWS_REGION environment variable\"\n );\n }\n}\n\nexport function getConfig(params: TGetConfigParams): TS3Config {\n validateParams(params);\n\n const { accessKeyId, secretAccessKey, bucket, region } = params;\n\n // If bucket or region are not defined, we use a default value\n const _DEFAULT_BUCKET_NAME = `--undefined-bucket-name-${uuidv4()}`;\n const _DEFAULT_REGION = \"us-east-1\";\n\n const config: TS3Config = {\n accessKeyId: `${accessKeyId}`,\n secretAccessKey: `${secretAccessKey}`,\n region: `${region || _DEFAULT_REGION}`,\n bucket: `${bucket || _DEFAULT_BUCKET_NAME}`,\n };\n\n return config;\n}\n","import { S3Client } from \"@aws-sdk/client-s3\";\nimport { TS3Config, getConfig } from \"./config\";\n\nexport function getS3Client(config: TS3Config) {\n let client = new S3Client({\n credentials: {\n accessKeyId: config.accessKeyId,\n secretAccessKey: config.secretAccessKey,\n },\n region: config.region,\n });\n\n return client;\n}\n\nexport const getS3ClientNative = () => {\n // this version of the function gets the config from the environment variables\n const params = {\n accessKeyId: process.env.AWS_ACCESS_KEY_ID,\n secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,\n region: process.env.AWS_REGION,\n bucket: process.env.S3_BUCKET_NAME,\n };\n\n let config = getConfig(params);\n\n return getS3Client(config);\n};\n","export function validateStringInput(input: string | string[]): string {\n // convert string of arrays or string to string / used in apiSignS3Url\n if (!input) {\n throw new Error(\"Input cannot be empty\");\n } else if (Array.isArray(input)) {\n return input.join(\",\");\n } else {\n return input;\n }\n}\n","import { PutObjectCommand } from \"@aws-sdk/client-s3\";\nimport { getSignedUrl } from \"@aws-sdk/s3-request-presigner\";\nimport { getS3ClientNative } from \"../util\";\nimport { validateStringInput } from \"../util/helpers\";\n\ninterface S3UploadResponse {\n signed_url: string;\n params: any;\n command: any;\n}\n\nexport async function apiSignS3Url(\n filename: string | string[],\n filetype: string | string[]\n): Promise {\n const client = getS3ClientNative();\n filename = validateStringInput(filename);\n filetype = validateStringInput(filetype);\n\n const objectKey = `${filename}-${Date.now()}`;\n const params = {\n Bucket: process.env.S3_BUCKET_NAME,\n Key: objectKey,\n ContentType: filetype,\n CacheControl: \"max-age=630720000\",\n Metadata: {\n metadata1: \"value12\",\n metadata2: \"value2\",\n },\n };\n\n const command = new PutObjectCommand(params);\n const signed_url = await getSignedUrl(client, command, { expiresIn: 3600 });\n\n return { signed_url, params, command };\n}\n","import React from \"react\";\n\nexport function FileUploadInput({ handleUpload }: { handleUpload: any }) {\n const handleFileChange = (e: React.ChangeEvent) => {\n e.preventDefault();\n const file = e.target.files?.[0]!;\n if (!file) {\n return alert(\"No file selected.\");\n } else {\n handleUpload(file);\n }\n };\n\n return (\n \n );\n}\n","import axios from \"axios\";\nimport { FileUploadInput } from \"./components/FileUploadInput\";\n\nfunction validateS3SignedUrl(url: string) {\n const regex = new RegExp(/^(ftp|http|https):\\/\\/[^ \"]+$/);\n\n if (!regex.test(url)) {\n throw new Error(\"[UploadSection/uploadFile] Invalid URL\");\n }\n}\n\nexport async function fetchPresignedUrl(\n filename: string,\n filetype: string\n): Promise {\n const res = await fetch(\n `/api/upload-presigned?filename=${filename}&filetype=${encodeURIComponent(\n filetype\n )}`\n );\n const signed_url = (await res.json()).signed_url;\n validateS3SignedUrl(signed_url);\n return signed_url;\n}\n\nexport async function uploadFile(file: File): Promise {\n if (!file) {\n throw new Error(\"[UploadSection/uploadFile] NO FILE\");\n }\n\n const signed_url = await fetchPresignedUrl(\n file.name || \"unknown-front-end-filename\",\n file.type\n );\n\n try {\n const upload_response = await axios.put(signed_url, file);\n return upload_response;\n } catch (err) {\n console.error({ err });\n return err;\n }\n}\n\nexport const useFileUpload = () => ({\n FileUploadInput,\n uploadFile,\n});\n","export * from \"./util/s3-client\";\nexport * from \"./util/config\";\nexport * from \"./nextjs/s3-presigned-api\";\nexport * from \"./nextjs/file-upload-hook\";\n\n// for testing purposes should be removed at some point\nexport function demo(a: number, b: number): number {\n console.log(\"The package is correctly compiled and distributed!\");\n return a + b;\n}\n"],"names":["uuidv4"],"mappings":"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"}
--------------------------------------------------------------------------------
/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@storengine/client",
3 | "version": "0.0.4",
4 | "description": "netrunner storengine SDK for Typescript and JavaScript",
5 | "main": "./dist/index.cjs",
6 | "module": "./dist/index.mjs",
7 | "types": "./dist/index.d.ts",
8 | "exports": {
9 | ".": {
10 | "import": "./dist/index.mjs",
11 | "require": "./dist/index.cjs",
12 | "types": "./dist/index.d.ts"
13 | }
14 | },
15 | "scripts": {
16 | "test": "node test/main.test.mjs",
17 | "build": "rimraf dist && rollup -c && pnpm test"
18 | },
19 | "repository": {
20 | "type": "git",
21 | "url": "git+https://github.com/davincios/netrunner.git#main"
22 | },
23 | "author": "Vincent Hus",
24 | "license": "Apache-2.0",
25 | "bugs": {
26 | "url": "https://github.com/davincios/netrunner/issues"
27 | },
28 | "homepage": "https://github.com/davincios/netrunner/tree/main#readme",
29 | "browser": {
30 | "child_process": false
31 | },
32 | "peerDependencies": {
33 | "typescript": "^=5.0.4"
34 | },
35 | "dependencies": {
36 | "@aws-sdk/client-s3": "^3.405.0",
37 | "@aws-sdk/s3-request-presigner": "^3.405.0",
38 | "react": "^18.2.0",
39 | "uuid": "^9.0.0"
40 | },
41 | "devDependencies": {
42 | "@rollup/plugin-alias": "^5.0.0",
43 | "@types/node": "^18.16.2",
44 | "@types/react": "^18.2.0",
45 | "@types/uuid": "^9.0.1",
46 | "rimraf": "^5.0.0",
47 | "rollup": "^3.21.0",
48 | "rollup-plugin-dts": "^5.3.0",
49 | "rollup-plugin-esbuild": "^5.0.0",
50 | "rollup-plugin-strip-code": "^0.2.7",
51 | "typescript": "^5.0.4"
52 | },
53 | "directories": {
54 | "test": "test"
55 | }
56 | }
--------------------------------------------------------------------------------
/client/rollup.config.mjs:
--------------------------------------------------------------------------------
1 | import dts from "rollup-plugin-dts";
2 | import esbuild from "rollup-plugin-esbuild";
3 | import stripCode from "rollup-plugin-strip-code";
4 | import alias from "@rollup/plugin-alias";
5 |
6 | export default [
7 | {
8 | input: "src/index.ts",
9 | plugins: [
10 | stripCode({
11 | start_comment: "REMOVE_CJS_BUNDLE_START",
12 | end_comment: "REMOVE_CJS_BUNDLE_END",
13 | }),
14 | esbuild(),
15 | ],
16 | output: {
17 | file: `dist/index.cjs`,
18 | format: "cjs",
19 | sourcemap: true,
20 | },
21 | },
22 | {
23 | input: "src/index.ts",
24 | plugins: [
25 | stripCode({
26 | start_comment: "REMOVE_ESM_BUNDLE_START",
27 | end_comment: "REMOVE_ESM_BUNDLE_END",
28 | }),
29 | esbuild(),
30 | ],
31 | output: {
32 | file: `dist/index.mjs`,
33 | format: "es",
34 | sourcemap: true,
35 | },
36 | },
37 | {
38 | input: "src/index.ts",
39 | plugins: [
40 | dts(),
41 | alias({
42 | entries: [{ find: "aws-crt", replacement: "node_modules/aws-crt" }],
43 | // "aws-crt": path.resolve(__dirname, "node_modules/aws-crt"),
44 | }),
45 | ],
46 |
47 | output: {
48 | file: `dist/index.d.ts`,
49 | format: "es",
50 | },
51 | },
52 | ];
53 |
--------------------------------------------------------------------------------
/client/src/client.ts:
--------------------------------------------------------------------------------
1 | export * from "./util/s3-client";
2 | export * from "./util/config";
3 | export * from "./nextjs/s3-presigned-api";
4 | export * from "./nextjs/file-upload-hook";
5 |
6 | // for testing purposes should be removed at some point
7 | export function demo(a: number, b: number): number {
8 | console.log("The package is correctly compiled and distributed!");
9 | return a + b;
10 | }
11 |
--------------------------------------------------------------------------------
/client/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./client";
2 |
--------------------------------------------------------------------------------
/client/src/nextjs/components/Demo.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useFileUpload } from "../file-upload-hook";
3 |
4 | export function DemoUploadComponent() {
5 | // This is the component as we show it in the demo
6 | const { FileUploadInput, uploadFile } = useFileUpload();
7 | return ;
8 | }
9 |
--------------------------------------------------------------------------------
/client/src/nextjs/components/FileUploadInput.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export function FileUploadInput({ handleUpload }: { handleUpload: any }) {
4 | const handleFileChange = (e: React.ChangeEvent) => {
5 | e.preventDefault();
6 | const file = e.target.files?.[0]!;
7 | if (!file) {
8 | return alert("No file selected.");
9 | } else {
10 | handleUpload(file);
11 | }
12 | };
13 |
14 | return (
15 |
20 | );
21 | }
22 |
--------------------------------------------------------------------------------
/client/src/nextjs/file-upload-hook.ts:
--------------------------------------------------------------------------------
1 | import { FileUploadInput } from "./components/FileUploadInput";
2 |
3 | function validateS3SignedUrl(url: string) {
4 | const regex = new RegExp(/^(ftp|http|https):\/\/[^ "]+$/);
5 |
6 | if (!regex.test(url)) {
7 | throw new Error("[UploadSection/uploadFile] Invalid URL");
8 | }
9 | }
10 |
11 | export async function fetchPresignedUrl(
12 | filename: string,
13 | filetype: string
14 | ): Promise {
15 | const res = await fetch(
16 | `/api/upload-presigned?filename=${filename}&filetype=${encodeURIComponent(
17 | filetype
18 | )}`
19 | );
20 | const signed_url = (await res.json()).signed_url;
21 | validateS3SignedUrl(signed_url);
22 | return signed_url;
23 | }
24 |
25 | export async function uploadFile(file: File): Promise {
26 | if (!file) {
27 | throw new Error("[UploadSection/uploadFile] NO FILE");
28 | }
29 |
30 | const signed_url = await fetchPresignedUrl(
31 | file.name || "unknown-front-end-filename",
32 | file.type
33 | );
34 |
35 | try {
36 | const upload_response = await fetch(signed_url, {
37 | method: "PUT",
38 | body: file,
39 | headers: {
40 | "Content-Type": file.type,
41 | },
42 | });
43 |
44 | if (!upload_response.ok) {
45 | throw new Error(`HTTP error! Status: ${upload_response.status}`);
46 | }
47 |
48 | return await upload_response.json();
49 | } catch (err) {
50 | console.error({ err });
51 | return err;
52 | }
53 | }
54 |
55 | export const useFileUpload = () => ({
56 | FileUploadInput,
57 | uploadFile,
58 | });
59 |
--------------------------------------------------------------------------------
/client/src/nextjs/s3-presigned-api.ts:
--------------------------------------------------------------------------------
1 | import { PutObjectCommand } from "@aws-sdk/client-s3";
2 | import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
3 | import { getS3ClientNative } from "../util";
4 | import { validateStringInput } from "../util/helpers";
5 |
6 | interface S3UploadResponse {
7 | signed_url: string;
8 | params: any;
9 | command: any;
10 | }
11 |
12 | export async function apiSignS3Url(
13 | filename: string | string[],
14 | filetype: string | string[]
15 | ): Promise {
16 | const client = getS3ClientNative();
17 | filename = validateStringInput(filename);
18 | filetype = validateStringInput(filetype);
19 |
20 | const objectKey = `${filename}-${Date.now()}`;
21 | const params = {
22 | Bucket: process.env.S3_BUCKET_NAME,
23 | Key: objectKey,
24 | ContentType: filetype,
25 | CacheControl: "max-age=630720000",
26 | Metadata: {
27 | metadata1: "value12",
28 | metadata2: "value2",
29 | },
30 | };
31 |
32 | const command = new PutObjectCommand(params);
33 | const signed_url = await getSignedUrl(client, command, { expiresIn: 3600 });
34 |
35 | return { signed_url, params, command };
36 | }
37 |
--------------------------------------------------------------------------------
/client/src/util/config.ts:
--------------------------------------------------------------------------------
1 | import { v4 as uuidv4 } from "uuid";
2 |
3 | export type TS3Config = {
4 | accessKeyId: string;
5 | secretAccessKey: string;
6 | bucket: string;
7 | region: string;
8 | };
9 |
10 | export type TGetConfigParams = {
11 | accessKeyId: string | undefined;
12 | secretAccessKey: string | undefined;
13 | bucket: string | undefined;
14 | region: string | undefined;
15 | };
16 |
17 | function validateParams(params: TGetConfigParams) {
18 | const { accessKeyId, secretAccessKey, bucket, region } = params;
19 |
20 | if (!accessKeyId) {
21 | throw new Error("Missing AWS_ACCESS_KEY_ID environment variable");
22 | }
23 |
24 | if (!secretAccessKey) {
25 | throw new Error("Missing AWS_SECRET_ACCESS_KEY environment variable");
26 | }
27 | if (!bucket) {
28 | console.warn(
29 | "\x1b[41m\x1b[31m%s\x1b[0m",
30 | "Missing BUCKET_NAME environment variable"
31 | );
32 | }
33 |
34 | if (!region) {
35 | console.warn(
36 | "\x1b[41m\x1b[31m%s\x1b[0m",
37 | "Missing AWS_REGION environment variable"
38 | );
39 | }
40 | }
41 |
42 | export function getConfig(params: TGetConfigParams): TS3Config {
43 | validateParams(params);
44 |
45 | const { accessKeyId, secretAccessKey, bucket, region } = params;
46 |
47 | // If bucket or region are not defined, we use a default value
48 | const _DEFAULT_BUCKET_NAME = `--undefined-bucket-name-${uuidv4()}`;
49 | const _DEFAULT_REGION = "us-east-1";
50 |
51 | const config: TS3Config = {
52 | accessKeyId: `${accessKeyId}`,
53 | secretAccessKey: `${secretAccessKey}`,
54 | region: `${region || _DEFAULT_REGION}`,
55 | bucket: `${bucket || _DEFAULT_BUCKET_NAME}`,
56 | };
57 |
58 | return config;
59 | }
60 |
--------------------------------------------------------------------------------
/client/src/util/helpers.ts:
--------------------------------------------------------------------------------
1 | export function validateStringInput(input: string | string[]): string {
2 | // convert string of arrays or string to string / used in apiSignS3Url
3 | if (!input) {
4 | throw new Error("Input cannot be empty");
5 | } else if (Array.isArray(input)) {
6 | return input.join(",");
7 | } else {
8 | return input;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/client/src/util/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./config";
2 | export * from "./s3-client";
3 |
--------------------------------------------------------------------------------
/client/src/util/s3-client.ts:
--------------------------------------------------------------------------------
1 | import { S3Client } from "@aws-sdk/client-s3";
2 | import { TS3Config, getConfig } from "./config";
3 |
4 | export function getS3Client(config: TS3Config) {
5 | let client = new S3Client({
6 | credentials: {
7 | accessKeyId: config.accessKeyId,
8 | secretAccessKey: config.secretAccessKey,
9 | },
10 | region: config.region,
11 | });
12 |
13 | return client;
14 | }
15 |
16 | export const getS3ClientNative = () => {
17 | // this version of the function gets the config from the environment variables
18 | const params = {
19 | accessKeyId: process.env.AWS_ACCESS_KEY_ID,
20 | secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
21 | region: process.env.AWS_REGION,
22 | bucket: process.env.S3_BUCKET_NAME,
23 | };
24 |
25 | let config = getConfig(params);
26 |
27 | return getS3Client(config);
28 | };
29 |
--------------------------------------------------------------------------------
/client/test/main.test.mjs:
--------------------------------------------------------------------------------
1 | import { demo } from "../dist/index.mjs";
2 |
3 | const successMessage = () =>
4 | console.log("\x1b[32m", "All tests passed!", "\x1b[0m");
5 |
6 | function testDemo() {
7 | const result = demo(1, 2);
8 | const expected = 3;
9 | if (result !== expected) {
10 | throw new Error(`${result} is not equal to ${expected}`);
11 | } else {
12 | return true;
13 | }
14 | }
15 |
16 | function main() {
17 | const t1 = testDemo();
18 | const t2 = true;
19 |
20 | if (t1 && t2) return successMessage();
21 | }
22 |
23 | main();
24 |
--------------------------------------------------------------------------------
/client/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2020",
4 | "lib": ["esnext", "dom"],
5 | "jsx": "react",
6 | "allowJs": true,
7 | "skipLibCheck": true,
8 | "esModuleInterop": true,
9 | "allowSyntheticDefaultImports": true,
10 | "strict": true,
11 | "strictNullChecks": true,
12 | "forceConsistentCasingInFileNames": true,
13 | "noFallthroughCasesInSwitch": true,
14 | "module": "es2020",
15 | "moduleResolution": "node",
16 | "resolveJsonModule": true,
17 | "isolatedModules": true,
18 | "noEmit": false,
19 | "outDir": "dist",
20 | "declaration": true,
21 | "types": ["node"]
22 | },
23 | "include": ["src"],
24 | "exclude": ["node_modules"]
25 | }
26 |
--------------------------------------------------------------------------------
/docs/NR_PLATFORM.md:
--------------------------------------------------------------------------------
1 |
2 | 🛜🏃🦾 Turn your AWS account into a personalised Firebase with Netrunner 🛜🏃🦾
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | 🕸️ Website
15 | •
16 | 🤝 Contribute
17 |
18 |
19 | ## Introducing Netrunner for Next.js and AWS S3
20 |
21 | Netrunner streamlines the process of integrating AWS S3 storage into your Next.js application. It is a storage development tool that creates and configures AWS S3 buckets for you, and provides you with instant APIs and code snippets for file uploads.
22 |
23 | And it doesn't stop at just configuration - Netrunner also supplies you with file previews for any media and easy-to-use security policy configurations, so that you don't have to reinvent the wheel.
24 |
25 | ## ✨ UI Screenshot
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | ## 🎉 Features
34 |
35 | - [x] ☁️ AWS S3 bucket creation and secure configuration for file uploads
36 | - [x] 🦾 Ready-to-use code snippets using AWS v3 or Netrunner SDKs
37 | - [x] 🔗 Easy storage, organization, and serving of large files
38 | - [x] 🏞️ Media previews, including videos and images
39 | - [ ] 🔓 Security policy creation from the UI (coming soon)
40 | - [ ] 🧠 Log streaming & OpenAI GPT-4 automated bug fixing (coming soon)
41 | - [ ] 🪐 Lambda functions and database provisioning (coming soon)
42 |
43 | Netrunner is currently in closed beta. You can sign up for beta access on the Netrunner [website](https://netrunnerhq.com).
44 |
45 | ## 👨🚀 Getting Started
46 |
47 | The easiest way to get started with Netrunner is the automatic setup CLI available on [npm](https://www.npmjs.com/package/@storengine/client). The cli sets up the following for Netrunner:
48 |
49 | - Environment variables.
50 | - Example code repository
51 | - A quickstart S3 bucket in your cloud account
52 |
53 | Before you get started, please make sure you have the following installed:
54 |
55 | - Netrunner API keys (available after signup)
56 | - AWS SDK and CLI
57 | - An OpenAI API key
58 | - Node.js
59 | - Visual studio code
60 |
61 | ## 🚀 Tech Stack
62 |
63 | - ✅ Framework: Nextjs 13 + Typescript + FastAPI
64 | - ✅ Auth: Auth0.js
65 | - ✅ Database: MongoDB.
66 | - ✅ Styling: TailwindCSS + RadixUI.
67 | - ✅ Infrasturcture as a code: Terraform + CloudFormation
68 | - ✅ Cloud platform: AWS
69 |
70 | ## 🦾 About Netrunner
71 |
72 | We are on a mission to enable JavaScript software engineers to transform their AWS cloud account into an AI-powered, personalised Firebase developer platform. Learn more by visiting our [website](https://netrunnerhq.com).
73 |
--------------------------------------------------------------------------------
/docs/logo/file-upload-completed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netrunnerhq/nextjs-aws-s3/bbd46bc850ea6bd487d0775832d65d033fe1c6f4/docs/logo/file-upload-completed.png
--------------------------------------------------------------------------------
/docs/logo/gifs/bucket-creation.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netrunnerhq/nextjs-aws-s3/bbd46bc850ea6bd487d0775832d65d033fe1c6f4/docs/logo/gifs/bucket-creation.gif
--------------------------------------------------------------------------------
/docs/logo/gifs/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netrunnerhq/nextjs-aws-s3/bbd46bc850ea6bd487d0775832d65d033fe1c6f4/docs/logo/gifs/demo.gif
--------------------------------------------------------------------------------
/docs/logo/gifs/file-upload.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netrunnerhq/nextjs-aws-s3/bbd46bc850ea6bd487d0775832d65d033fe1c6f4/docs/logo/gifs/file-upload.gif
--------------------------------------------------------------------------------
/docs/logo/gifs/hero-gif-full-demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netrunnerhq/nextjs-aws-s3/bbd46bc850ea6bd487d0775832d65d033fe1c6f4/docs/logo/gifs/hero-gif-full-demo.gif
--------------------------------------------------------------------------------
/docs/logo/netrunner-main-character.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netrunnerhq/nextjs-aws-s3/bbd46bc850ea6bd487d0775832d65d033fe1c6f4/docs/logo/netrunner-main-character.png
--------------------------------------------------------------------------------
/docs/logo/screenshot-10-july.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netrunnerhq/nextjs-aws-s3/bbd46bc850ea6bd487d0775832d65d033fe1c6f4/docs/logo/screenshot-10-july.png
--------------------------------------------------------------------------------
/docs/logo/screenshot-22-july.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netrunnerhq/nextjs-aws-s3/bbd46bc850ea6bd487d0775832d65d033fe1c6f4/docs/logo/screenshot-22-july.png
--------------------------------------------------------------------------------
/docs/logo/screenshot-25-july.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netrunnerhq/nextjs-aws-s3/bbd46bc850ea6bd487d0775832d65d033fe1c6f4/docs/logo/screenshot-25-july.png
--------------------------------------------------------------------------------
/examples/app-router-example/.env.template:
--------------------------------------------------------------------------------
1 | # template file for .env
2 | # go to the AWS console to get your credentials
3 | S3_BUCKET_NAME=XXXXXXX
4 | AWS_REGION=XXXXXX
5 |
6 |
7 | # aws credentials
8 | AWS_ACCESS_KEY_ID=xxxxx
9 | AWS_SECRET_ACCESS_KEY=xxxxx
10 |
--------------------------------------------------------------------------------
/examples/app-router-example/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals",
3 | "rules": {
4 | "@next/next/no-img-element": "off"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/examples/app-router-example/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # local env files
4 | .env*.local
5 | .env
6 |
7 | # dependencies
8 | /node_modules
9 | /.pnp
10 | .pnp.js
11 |
12 | # testing
13 | /coverage
14 |
15 | # next.js
16 | /.next/
17 | /out/
18 |
19 | # production
20 | /build
21 |
22 | # misc
23 | .DS_Store
24 | *.pem
25 |
26 | # debug
27 | npm-debug.log*
28 | yarn-debug.log*
29 | yarn-error.log*
30 |
31 |
32 | # vercel
33 | .vercel
34 |
35 | # typescript
36 | *.tsbuildinfo
37 | next-env.d.ts
38 |
--------------------------------------------------------------------------------
/examples/app-router-example/README.md:
--------------------------------------------------------------------------------
1 | # Next JS App Router Example
2 |
3 | ## QuickStart
4 |
5 | 1. Create an S3 bucket in the Netrunner dashboard: https://netrunnerhq.com/
6 | 2. copy the template .env file in your terminal: `cp .env.template .env.local` and paste your S3 bucket name and AWS credentials
7 | 3. `pnpm dev`
8 | 4. Upload an example file.
9 |
--------------------------------------------------------------------------------
/examples/app-router-example/app/api/upload/helper.ts:
--------------------------------------------------------------------------------
1 | // app/api/upload.ts
2 | import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
3 | import {
4 | GetObjectCommand,
5 | PutObjectCommand,
6 | S3Client,
7 | } from "@aws-sdk/client-s3";
8 |
9 | export function getAwsConsoleUrl(
10 | bucketName: string,
11 | region: string,
12 | objectKey: string
13 | ): string {
14 | return `https://s3.console.aws.amazon.com/s3/object/${bucketName}?region=${region}&prefix=${objectKey}`;
15 | }
16 |
17 | export async function getPresignedUrlS3({
18 | bucketName,
19 | objectKey,
20 | client,
21 | requestType,
22 | }: {
23 | bucketName: string;
24 | objectKey: string;
25 | client?: S3Client;
26 | requestType?: "put" | "get";
27 | }): Promise {
28 | if (!client) throw new Error("[presigned-url.ts] client is required");
29 | if (!bucketName) throw new Error("[presigned-url.ts] bucketName is required");
30 | if (!objectKey) throw new Error("[presigned-url.ts] objectKey is required");
31 | if (!requestType)
32 | throw new Error("[presigned-url.ts] requestType is required");
33 |
34 | try {
35 | let command;
36 |
37 | switch (requestType) {
38 | case "get":
39 | command = new GetObjectCommand({
40 | Bucket: bucketName,
41 | Key: objectKey,
42 | });
43 | break;
44 | case "put":
45 | command = new PutObjectCommand({
46 | Bucket: bucketName,
47 | Key: objectKey,
48 | });
49 | break;
50 | default:
51 | throw new Error(
52 | "[presigned-url.ts] Invalid request type. It should be either 'put' or 'get'."
53 | );
54 | }
55 |
56 | const signedUrl = await getSignedUrl(client as any, command as any, {
57 | expiresIn: 3600,
58 | });
59 |
60 | return signedUrl;
61 | } catch (err) {
62 | console.error("Error generating presigned URL:", err);
63 | throw err;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/examples/app-router-example/app/api/upload/route.ts:
--------------------------------------------------------------------------------
1 | // app/api/upload.ts
2 | import { S3Client } from "@aws-sdk/client-s3";
3 | import { NextResponse } from "next/server";
4 | import { getAwsConsoleUrl, getPresignedUrlS3 } from "./helper";
5 |
6 | const s3Client = new S3Client({
7 | region: `${process.env.AWS_REGION}`,
8 | credentials: {
9 | accessKeyId: `${process.env.AWS_ACCESS_KEY_ID}`,
10 | secretAccessKey: `${process.env.AWS_SECRET_ACCESS_KEY}`,
11 | },
12 | });
13 |
14 | export async function POST(req: Request) {
15 | const body = await req.json();
16 | const { objectKey, requestType } = body;
17 |
18 | if (!objectKey) {
19 | return NextResponse.json(
20 | { error: "[api/upload/route.ts] Object key is required" },
21 | { status: 400 }
22 | );
23 | }
24 |
25 | if (!requestType) {
26 | return NextResponse.json(
27 | { error: "[api/upload/route.ts] Request type is required" },
28 | { status: 400 }
29 | );
30 | }
31 |
32 | try {
33 | const signedUrl = await getPresignedUrlS3({
34 | bucketName: `${process.env.S3_BUCKET_NAME}`,
35 | objectKey,
36 | requestType,
37 | client: s3Client,
38 | });
39 |
40 | const awsConsoleUrl = getAwsConsoleUrl(
41 | `${process.env.S3_BUCKET_NAME}`,
42 | `${process.env.AWS_REGION}`,
43 | objectKey
44 | );
45 |
46 | return NextResponse.json({ signedUrl, awsConsoleUrl });
47 | } catch (err) {
48 | console.error("API Error:", err);
49 | return NextResponse.json(
50 | { error: "Failed to generate presigned URL." },
51 | { status: 500 }
52 | );
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/examples/app-router-example/app/layout.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import "../styles/globals.css";
4 | import { Footer, Header } from "@/utils/components/Common";
5 |
6 | export default function RootLayout({
7 | children,
8 | }: {
9 | children: React.ReactNode;
10 | }) {
11 | return (
12 |
13 |
14 |
15 | {children}
16 |
17 |
18 |
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/examples/app-router-example/app/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { UploadComponent } from "../utils/upload";
4 |
5 | export default function Home() {
6 | return (
7 |
8 |
9 |
10 | );
11 | }
12 |
--------------------------------------------------------------------------------
/examples/app-router-example/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 |
3 | const config = {
4 | reactStrictMode: true,
5 | eslint: { ignoreDuringBuilds: true },
6 | typescript: { ignoreBuildErrors: true },
7 | };
8 |
9 | module.exports = config;
10 |
--------------------------------------------------------------------------------
/examples/app-router-example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example-app",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "clean": "git clean -xdf .next node_modules",
8 | "build": "next build",
9 | "start": "next start",
10 | "lint": "next lint"
11 | },
12 | "dependencies": {
13 | "@aws-sdk/client-s3": "3.405.0",
14 | "@aws-sdk/s3-request-presigner": "3.405.0",
15 | "next": "13.4.19",
16 | "react": "18.2.0",
17 | "react-dom": "18.2.0"
18 | },
19 | "devDependencies": {
20 | "@types/node": "20.4.4",
21 | "@types/react": "18.2.15",
22 | "@types/react-dom": "18.2.7",
23 | "eslint": "8.45.0",
24 | "eslint-config-next": "13.4.12",
25 | "postcss": "8.4.27",
26 | "tailwindcss": "3.3.3",
27 | "typescript": "5.1.6"
28 | }
29 | }
--------------------------------------------------------------------------------
/examples/app-router-example/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | },
5 | };
6 |
--------------------------------------------------------------------------------
/examples/app-router-example/styles/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | body {
6 | color: rgb(var(--foreground-rgb));
7 | font-size: 1.125rem /* 18px */;
8 | line-height: 1.75rem; /* 28px */
9 | min-height: 100vh;
10 | color: black;
11 | text-align: center;
12 | padding: 6rem /* 96px */;
13 | }
14 |
--------------------------------------------------------------------------------
/examples/app-router-example/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | content: [
4 | "./pages/**/*.{js,ts,jsx,tsx,mdx}",
5 | "./components/**/*.{js,ts,jsx,tsx,mdx}",
6 | "./utils/**/*.{js,ts,jsx,tsx,mdx}",
7 | "./app/**/*.{js,ts,jsx,tsx,mdx}",
8 | ],
9 | plugins: [],
10 | };
11 |
--------------------------------------------------------------------------------
/examples/app-router-example/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "strict": true,
12 | "forceConsistentCasingInFileNames": true,
13 | "noEmit": true,
14 | "esModuleInterop": true,
15 | "module": "esnext",
16 | "moduleResolution": "bundler",
17 | "resolveJsonModule": true,
18 | "isolatedModules": true,
19 | "jsx": "preserve",
20 | "incremental": true,
21 | "paths": {
22 | "@/*": [
23 | "./*"
24 | ]
25 | },
26 | "plugins": [
27 | {
28 | "name": "next"
29 | }
30 | ]
31 | },
32 | "include": [
33 | "next-env.d.ts",
34 | "**/*.ts",
35 | "**/*.tsx",
36 | ".next/types/**/*.ts"
37 | ],
38 | "exclude": [
39 | "node_modules"
40 | ]
41 | }
42 |
--------------------------------------------------------------------------------
/examples/app-router-example/utils/components/Common.tsx:
--------------------------------------------------------------------------------
1 | export const Footer = () => {
2 | return (
3 |
13 | );
14 | };
15 |
16 | export const Header = () => (
17 |
18 | Next.js to AWS S3 upload file example
19 |
20 | );
21 |
--------------------------------------------------------------------------------
/examples/app-router-example/utils/index.tsx:
--------------------------------------------------------------------------------
1 | export { UploadComponent } from "./upload";
2 |
--------------------------------------------------------------------------------
/examples/app-router-example/utils/upload/components/FilePreview.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Status, TStatus } from "./Status";
3 |
4 | const FilePreview: React.FC<{
5 | imgUrl: string | null;
6 | status: TStatus;
7 | awsConsole: string | null;
8 | }> = ({ imgUrl, status, awsConsole }) => {
9 | return (
10 | <>
11 | {imgUrl && status === "success" && (
12 |
17 | )}
18 |
19 | >
20 | );
21 | };
22 |
23 | export { FilePreview };
24 |
--------------------------------------------------------------------------------
/examples/app-router-example/utils/upload/components/Status.tsx:
--------------------------------------------------------------------------------
1 | import { IconUpload } from "./icons/IconUpload";
2 |
3 | export type TStatus = "idle" | "loading" | "error" | "success";
4 |
5 | export function Status({
6 | status,
7 | awsConsoleObjectUrl,
8 | }: {
9 | status: TStatus;
10 | awsConsoleObjectUrl: string | null;
11 | }) {
12 | const isLoading = status === "loading";
13 | const isError = status === "error";
14 | const isSuccess = status === "success";
15 |
16 | return (
17 |
18 | {isLoading && (
19 |
22 | )}
23 |
24 | {isError &&
An error has occured while uploading the file
}
25 |
26 | {isSuccess && awsConsoleObjectUrl && (
27 |
37 | )}
38 |
39 | );
40 | }
41 |
--------------------------------------------------------------------------------
/examples/app-router-example/utils/upload/components/icons/IconUpload.tsx:
--------------------------------------------------------------------------------
1 | export function IconUpload() {
2 | return <>🔗>;
3 | }
4 |
--------------------------------------------------------------------------------
/examples/app-router-example/utils/upload/hooks/useUploadFile.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import { uploadFileToS3 } from "../utils/index";
3 | import { TStatus } from "../components/Status";
4 |
5 | export const useUploadFile = () => {
6 | const [imgUrl, setImgUrl] = useState(null);
7 | const [status, setStatus] = useState("idle");
8 | const [awsConsole, setAwsConsole] = useState(null);
9 |
10 | const handleFileUpload = async (file: File | undefined) => {
11 | if (!file) return;
12 |
13 | try {
14 | setStatus("loading");
15 | const { uploadedImgUrl, awsConsoleUrl } = await uploadFileToS3(file);
16 | setImgUrl(uploadedImgUrl);
17 | setAwsConsole(awsConsoleUrl);
18 | setStatus("success");
19 | } catch {
20 | setStatus("error");
21 | }
22 | };
23 |
24 | return {
25 | imgUrl,
26 | status,
27 | awsConsole,
28 | handleFileUpload,
29 | };
30 | };
31 |
--------------------------------------------------------------------------------
/examples/app-router-example/utils/upload/index.tsx:
--------------------------------------------------------------------------------
1 | import { useUploadFile } from "./hooks/useUploadFile";
2 | import { FilePreview } from "./components/FilePreview";
3 |
4 | export function UploadComponent() {
5 | const { handleFileUpload, imgUrl, status, awsConsole } = useUploadFile();
6 |
7 | return (
8 | <>
9 | handleFileUpload(e.target.files?.[0])}
11 | type="file"
12 | className="py-8"
13 | />
14 |
15 | >
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/examples/app-router-example/utils/upload/utils/index.ts:
--------------------------------------------------------------------------------
1 | async function getPresignedUrl(
2 | objectKey: string,
3 | action: "put" | "get"
4 | ): Promise<{
5 | signedUrl: string;
6 | awsConsoleUrl: string;
7 | }> {
8 | try {
9 | const response = await fetch("/api/upload", {
10 | method: "POST",
11 | headers: { "Content-Type": "application/json" },
12 | body: JSON.stringify({
13 | objectKey,
14 | requestType: action,
15 | }),
16 | });
17 |
18 | if (!response.ok) {
19 | const errorData = await response.json();
20 | throw new Error(errorData.message || "Failed to get presigned URL");
21 | }
22 |
23 | return await response.json();
24 | } catch (error) {
25 | console.error("Error getting presigned URL:", error);
26 | throw error;
27 | }
28 | }
29 |
30 | export async function uploadFileToS3(file: File): Promise<{
31 | uploadedImgUrl: string;
32 | awsConsoleUrl: string;
33 | }> {
34 | try {
35 | // Step 1: Get the presigned URL for upload
36 | const { signedUrl: uploadFileUrl, awsConsoleUrl } = await getPresignedUrl(
37 | file.name,
38 | "put"
39 | );
40 | if (!uploadFileUrl) throw Error("[f:uploadFileToS3] undef uploadFileUrl");
41 | if (!awsConsoleUrl) throw Error("[f:uploadFileToS3] undef awsConsoleUrl");
42 |
43 | // Step 2: Upload the file using the presigned URL
44 | const uploadResponse = await fetch(uploadFileUrl, {
45 | method: "PUT",
46 | body: file,
47 | headers: {
48 | "Content-Type": file.type,
49 | },
50 | });
51 |
52 | if (!uploadResponse.ok) {
53 | throw new Error("Failed to upload the file to S3");
54 | }
55 |
56 | console.log("File uploaded successfully to S3 via presigned URL");
57 |
58 | // Step 3: Get the URL of the uploaded file for rendering/viewing
59 | const { signedUrl: uploadedImgUrl } = await getPresignedUrl(
60 | file.name,
61 | "get"
62 | );
63 |
64 | return {
65 | uploadedImgUrl,
66 | awsConsoleUrl,
67 | };
68 | } catch (err) {
69 | console.error("Error occurred while uploading file:", err);
70 | throw err;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/examples/pages-router-example/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals",
3 | "rules": {
4 | "@next/next/no-img-element": "off"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/examples/pages-router-example/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env*.local
29 |
30 | # vercel
31 | .vercel
32 |
33 | # typescript
34 | *.tsbuildinfo
35 | next-env.d.ts
36 |
--------------------------------------------------------------------------------
/examples/pages-router-example/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, run the development server:
6 |
7 | ```bash
8 | npm run dev
9 | # or
10 | yarn dev
11 | # or
12 | pnpm dev
13 | ```
14 |
15 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
16 |
17 | You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file.
18 |
19 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`.
20 |
21 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
22 |
23 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
24 |
25 | ## Learn More
26 |
27 | To learn more about Next.js, take a look at the following resources:
28 |
29 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
30 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
31 |
32 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
33 |
34 | ## Deploy on Vercel
35 |
36 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
37 |
38 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
39 |
--------------------------------------------------------------------------------
/examples/pages-router-example/components/index.tsx:
--------------------------------------------------------------------------------
1 | export { UploadComponent } from "./upload";
2 |
--------------------------------------------------------------------------------
/examples/pages-router-example/components/upload/components/FilePreview.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Status, TStatus } from "./Status";
3 |
4 | const FilePreview: React.FC<{
5 | imgUrl: string | null;
6 | status: TStatus;
7 | awsConsole: string | null;
8 | }> = ({ imgUrl, status, awsConsole }) => {
9 | return (
10 | <>
11 | {imgUrl && status === "success" && (
12 |
17 | )}
18 |
19 | >
20 | );
21 | };
22 |
23 | export { FilePreview };
24 |
--------------------------------------------------------------------------------
/examples/pages-router-example/components/upload/components/Status.tsx:
--------------------------------------------------------------------------------
1 | import { IconUpload } from "./icons/IconUpload";
2 |
3 | export type TStatus = "idle" | "loading" | "error" | "success";
4 |
5 | export function Status({
6 | status,
7 | awsConsoleObjectUrl,
8 | }: {
9 | status: TStatus;
10 | awsConsoleObjectUrl: string | null;
11 | }) {
12 | const isLoading = status === "loading";
13 | const isError = status === "error";
14 | const isSuccess = status === "success";
15 |
16 | return (
17 |
18 | {isLoading && (
19 |
22 | )}
23 |
24 | {isError &&
An error has occured while uploading the file
}
25 |
26 | {isSuccess && awsConsoleObjectUrl && (
27 |
37 | )}
38 |
39 | );
40 | }
41 |
--------------------------------------------------------------------------------
/examples/pages-router-example/components/upload/components/icons/IconUpload.tsx:
--------------------------------------------------------------------------------
1 | export function IconUpload() {
2 | return <>🔗>;
3 | }
4 |
--------------------------------------------------------------------------------
/examples/pages-router-example/components/upload/hooks/useUploadFile.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import { uploadFileToS3 } from "../utils/index";
3 | import { TStatus } from "../components/Status";
4 |
5 | export const useUploadFile = () => {
6 | const [imgUrl, setImgUrl] = useState(null);
7 | const [status, setStatus] = useState("idle");
8 | const [awsConsole, setAwsConsole] = useState(null);
9 |
10 | const handleFileUpload = async (file: File | undefined) => {
11 | if (!file) return;
12 |
13 | try {
14 | setStatus("loading");
15 | const { uploadedImgUrl, awsConsoleUrl } = await uploadFileToS3(file);
16 | setImgUrl(uploadedImgUrl);
17 | setAwsConsole(awsConsoleUrl);
18 | setStatus("success");
19 | } catch {
20 | setStatus("error");
21 | }
22 | };
23 |
24 | return {
25 | imgUrl,
26 | status,
27 | awsConsole,
28 | handleFileUpload,
29 | };
30 | };
31 |
--------------------------------------------------------------------------------
/examples/pages-router-example/components/upload/index.tsx:
--------------------------------------------------------------------------------
1 | import { useUploadFile } from "./hooks/useUploadFile";
2 | import { FilePreview } from "./components/FilePreview";
3 |
4 | export function UploadComponent() {
5 | const { handleFileUpload, imgUrl, status, awsConsole } = useUploadFile();
6 |
7 | return (
8 | <>
9 | handleFileUpload(e.target.files?.[0])}
11 | type="file"
12 | className="py-8"
13 | />
14 |
15 | >
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/examples/pages-router-example/components/upload/utils/index.ts:
--------------------------------------------------------------------------------
1 | async function getPresignedUrl(
2 | objectKey: string,
3 | action: "put" | "get"
4 | ): Promise<{
5 | signedUrl: string;
6 | awsConsoleUrl: string;
7 | }> {
8 | try {
9 | const response = await fetch("/api/generateImgUrl", {
10 | method: "POST",
11 | headers: { "Content-Type": "application/json" },
12 | body: JSON.stringify({
13 | objectKey,
14 | requestType: action,
15 | }),
16 | });
17 |
18 | if (!response.ok) {
19 | const errorData = await response.json();
20 | throw new Error(errorData.message || "Failed to get presigned URL");
21 | }
22 |
23 | return await response.json();
24 | } catch (error) {
25 | console.error("Error getting presigned URL:", error);
26 | throw error;
27 | }
28 | }
29 |
30 | export async function uploadFileToS3(file: File): Promise<{
31 | uploadedImgUrl: string;
32 | awsConsoleUrl: string;
33 | }> {
34 | try {
35 | // Step 1: Get the presigned URL for upload
36 | const { signedUrl: uploadFileUrl, awsConsoleUrl } = await getPresignedUrl(
37 | file.name,
38 | "put"
39 | );
40 | if (!uploadFileUrl) throw Error("[f:uploadFileToS3] undef uploadFileUrl");
41 | if (!awsConsoleUrl) throw Error("[f:uploadFileToS3] undef awsConsoleUrl");
42 |
43 | // Step 2: Upload the file using the presigned URL
44 | const uploadResponse = await fetch(uploadFileUrl, {
45 | method: "PUT",
46 | body: file,
47 | headers: {
48 | "Content-Type": file.type,
49 | },
50 | });
51 |
52 | if (!uploadResponse.ok) {
53 | throw new Error("Failed to upload the file to S3");
54 | }
55 |
56 | console.log("File uploaded successfully to S3 via presigned URL");
57 |
58 | // Step 3: Get the URL of the uploaded file for rendering/viewing
59 | const { signedUrl: uploadedImgUrl } = await getPresignedUrl(
60 | file.name,
61 | "get"
62 | );
63 |
64 | return {
65 | uploadedImgUrl,
66 | awsConsoleUrl,
67 | };
68 | } catch (err) {
69 | console.error("Error occurred while uploading file:", err);
70 | throw err;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/examples/pages-router-example/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const config = {
3 | reactStrictMode: true,
4 | eslint: { ignoreDuringBuilds: true },
5 | typescript: { ignoreBuildErrors: true },
6 | };
7 |
8 | module.exports = config;
9 |
--------------------------------------------------------------------------------
/examples/pages-router-example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example-app",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@aws-sdk/client-s3": "3.405.0",
13 | "@aws-sdk/s3-request-presigner": "3.405.0",
14 | "@storengine/client": "^0.0.3",
15 | "@types/node": "20.4.4",
16 | "@types/react": "18.2.15",
17 | "@types/react-dom": "18.2.7",
18 | "autoprefixer": "10.4.14",
19 | "eslint": "8.45.0",
20 | "eslint-config-next": "13.4.12",
21 | "next": "13.4.12",
22 | "postcss": "8.4.27",
23 | "react": "18.2.0",
24 | "react-dom": "18.2.0",
25 | "tailwindcss": "3.3.3",
26 | "typescript": "5.1.6"
27 | }
28 | }
--------------------------------------------------------------------------------
/examples/pages-router-example/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import '@/styles/globals.css'
2 | import type { AppProps } from 'next/app'
3 |
4 | export default function App({ Component, pageProps }: AppProps) {
5 | return
6 | }
7 |
--------------------------------------------------------------------------------
/examples/pages-router-example/pages/_document.tsx:
--------------------------------------------------------------------------------
1 | import { Html, Head, Main, NextScript } from 'next/document'
2 |
3 | export default function Document() {
4 | return (
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/examples/pages-router-example/pages/api/generateImgUrl.ts:
--------------------------------------------------------------------------------
1 | // pages/api/getPresignedUrl.ts
2 | import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
3 | import {
4 | GetObjectCommand,
5 | PutObjectCommand,
6 | S3Client,
7 | } from "@aws-sdk/client-s3";
8 | import { NextApiRequest, NextApiResponse } from "next";
9 |
10 | export async function getPresignedUrlS3({
11 | bucketName,
12 | objectKey,
13 | client,
14 | requestType,
15 | }: {
16 | bucketName: string;
17 | objectKey: string;
18 | client?: S3Client;
19 | requestType?: "put" | "get";
20 | }): Promise {
21 | if (!client) throw new Error("[presigned-url.ts] client is required");
22 | if (!bucketName) throw new Error("[presigned-url.ts] bucketName is required");
23 | if (!objectKey) throw new Error("[presigned-url.ts] objectKey is required");
24 | if (!requestType)
25 | throw new Error("[presigned-url.ts] requestType is required");
26 |
27 | try {
28 | let command;
29 |
30 | switch (requestType) {
31 | case "get":
32 | command = new GetObjectCommand({
33 | Bucket: bucketName,
34 | Key: objectKey,
35 | });
36 | break;
37 | case "put":
38 | command = new PutObjectCommand({
39 | Bucket: bucketName,
40 | Key: objectKey,
41 | });
42 | break;
43 | default:
44 | throw new Error(
45 | "[presigned-url.ts] Invalid request type. It should be either 'put' or 'get'."
46 | );
47 | }
48 |
49 | const signedUrl = await getSignedUrl(client as any, command as any, {
50 | expiresIn: 3600,
51 | });
52 |
53 | return signedUrl;
54 | } catch (err) {
55 | console.error("Error generating presigned URL:", err);
56 | throw err;
57 | }
58 | }
59 |
60 | const s3Client = new S3Client({
61 | region: `${process.env.AWS_REGION}`,
62 | credentials: {
63 | accessKeyId: `${process.env.AWS_ACCESS_KEY_ID}`,
64 | secretAccessKey: `${process.env.AWS_SECRET_ACCESS_KEY}`,
65 | },
66 | });
67 |
68 | function getAwsConsoleUrl(
69 | bucketName: string,
70 | region: string,
71 | objectKey: string
72 | ): string {
73 | return `https://s3.console.aws.amazon.com/s3/object/${bucketName}?region=${region}&prefix=${objectKey}`;
74 | }
75 |
76 | export default async function handler(
77 | req: NextApiRequest,
78 | res: NextApiResponse
79 | ) {
80 | const { objectKey, requestType } = req.body;
81 |
82 | if (!objectKey) {
83 | res.status(400).json({ error: "Object key is required" });
84 | return;
85 | }
86 |
87 | if (!requestType) {
88 | res.status(400).json({ error: "Request type is required" });
89 | return;
90 | }
91 |
92 | try {
93 | const signedUrl = await getPresignedUrlS3({
94 | bucketName: `${process.env.S3_BUCKET_NAME}`,
95 | objectKey,
96 | requestType,
97 | client: s3Client,
98 | });
99 |
100 | const awsConsoleUrl = getAwsConsoleUrl(
101 | `${process.env.S3_BUCKET_NAME}`,
102 | `${process.env.AWS_REGION}`,
103 | objectKey
104 | );
105 |
106 | res.status(200).json({ signedUrl, awsConsoleUrl });
107 | } catch (err) {
108 | console.error("API Error:", err);
109 | res.status(500).json({ error: "Failed to generate presigned URL." });
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/examples/pages-router-example/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import { Inter } from "next/font/google";
2 | import { UploadComponent } from "../components/upload";
3 |
4 | const inter = Inter({ subsets: ["latin"] });
5 |
6 | export default function Home() {
7 | return (
8 |
11 |
12 | Next.js to AWS S3 upload file example
13 |
14 |
15 |
16 |
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/examples/pages-router-example/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/examples/pages-router-example/styles/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | :root {
6 | --foreground-rgb: 0, 0, 0;
7 | --background-start-rgb: 214, 219, 220;
8 | --background-end-rgb: 255, 255, 255;
9 | }
10 |
11 | @media (prefers-color-scheme: dark) {
12 | :root {
13 | --foreground-rgb: 255, 255, 255;
14 | --background-start-rgb: 0, 0, 0;
15 | --background-end-rgb: 0, 0, 0;
16 | }
17 | }
18 |
19 | body {
20 | color: rgb(var(--foreground-rgb));
21 | }
22 |
--------------------------------------------------------------------------------
/examples/pages-router-example/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | content: [
4 | "./pages/**/*.{js,ts,jsx,tsx,mdx}",
5 | "./components/**/*.{js,ts,jsx,tsx,mdx}",
6 | "./app/**/*.{js,ts,jsx,tsx,mdx}",
7 | ],
8 | theme: {
9 | extend: {
10 | backgroundImage: {
11 | "gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
12 | "gradient-conic":
13 | "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
14 | },
15 | },
16 | },
17 | plugins: [],
18 | };
19 |
--------------------------------------------------------------------------------
/examples/pages-router-example/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "module": "esnext",
12 | "moduleResolution": "bundler",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "jsx": "preserve",
16 | "incremental": true,
17 | "paths": {
18 | "@/*": ["./*"]
19 | }
20 | },
21 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
22 | "exclude": ["node_modules"]
23 | }
24 |
--------------------------------------------------------------------------------
/iam_netrunner_setup.yaml:
--------------------------------------------------------------------------------
1 | Description: This template creates the resources necessary for Netrunner to assume to perform actions on your AWS Account.
2 |
3 | Parameters:
4 | NetrunnerAWSAccountId:
5 | Description: Netrunner AWS account ID allowed to assume the integration IAM role. Do not change!
6 | Type: String
7 | Default: 395261708130
8 |
9 | ExternalParameterId:
10 | Description: External Parameter for securing the Netrunner IAM role. Do not change!
11 | Type: String
12 | Default: ""
13 |
14 | Resources:
15 | NetrunnerEnvironmentRole:
16 | Type: "AWS::IAM::Role"
17 | Properties:
18 | RoleName: NetrunnerIntegrationRole
19 | AssumeRolePolicyDocument:
20 | Version: "2012-10-17"
21 | Statement:
22 | - Effect: Allow
23 | Principal:
24 | AWS:
25 | - !Sub arn:aws:iam::${NetrunnerAWSAccountId}:role/netrunner-forward-lambda-integration-role-prod
26 | Action: "sts:AssumeRole"
27 | Condition:
28 | StringEquals:
29 | "sts:ExternalId": !Ref ExternalParameterId
30 | ManagedPolicyArns:
31 | - "arn:aws:iam::aws:policy/CloudWatchLogsReadOnlyAccess"
32 | - "arn:aws:iam::aws:policy/AmazonS3FullAccess"
33 |
--------------------------------------------------------------------------------