chore: support deploy in cloudflare with opennext and fix android build

This commit is contained in:
Huang Xin 2025-03-25 02:08:08 +08:00
parent 0c65d44bc9
commit ede37757db
No known key found for this signature in database
GPG key ID: B91E1F13D3BC5EA4
7 changed files with 3269 additions and 44 deletions

View file

@ -84,7 +84,7 @@ jobs:
args: '--target aarch64-pc-windows-msvc --bundles nsis'
runs-on: ${{ matrix.config.os }}
timeout-minutes: 30
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
@ -149,6 +149,9 @@ jobs:
- name: build and upload Android apks
if: matrix.config.release == 'android'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NDK_HOME: ${{ env.ANDROID_HOME }}/ndk/27.0.11902837
run: |
cd apps/readest-app/
rm -rf src-tauri/gen/android
@ -176,8 +179,6 @@ jobs:
gh release upload ${{ needs.get-release.outputs.release_tag }} $universial_apk --clobber
echo "Uploading $arm64_apk to GitHub release"
gh release upload ${{ needs.get-release.outputs.release_tag }} $arm64_apk --clobber
env:
NDK_HOME: ${{ env.ANDROID_HOME }}/ndk/27.0.11902837
- uses: tauri-apps/tauri-action@v0
if: matrix.config.release != 'android'

View file

@ -56,6 +56,7 @@
"@tauri-apps/plugin-shell": "~2.2.0",
"@tauri-apps/plugin-updater": "^2.5.1",
"@zip.js/zip.js": "^2.7.53",
"aws4fetch": "^1.0.20",
"clsx": "^2.1.1",
"cors": "^2.8.5",
"cssbeautify": "^0.3.1",
@ -77,6 +78,7 @@
"zustand": "5.0.1"
},
"devDependencies": {
"@opennextjs/cloudflare": "^0.5.12",
"@tauri-apps/cli": "2.3.1",
"@types/cors": "^2.8.17",
"@types/cssbeautify": "^0.3.5",
@ -99,6 +101,7 @@
"postcss-nested": "^7.0.2",
"raw-loader": "^4.0.2",
"tailwindcss": "^3.4.17",
"typescript": "^5.7.2"
"typescript": "^5.7.2",
"wrangler": "^4.4.0"
}
}

View file

@ -2,8 +2,7 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { corsAllMethods, runMiddleware } from '@/utils/cors';
import { createSupabaseClient } from '@/utils/supabase';
import { validateUserAndToken } from '@/utils/access';
import { DeleteObjectCommand } from '@aws-sdk/client-s3';
import { s3Client } from '@/utils/s3';
import { deleteObject } from '@/utils/r2';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
await runMiddleware(req, res, corsAllMethods);
@ -41,13 +40,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
return res.status(403).json({ error: 'Unauthorized access to the file' });
}
const deleteCommand = new DeleteObjectCommand({
Bucket: process.env['R2_BUCKET_NAME'] || '',
Key: fileKey,
});
try {
await s3Client.send(deleteCommand);
await deleteObject(process.env['R2_BUCKET_NAME'] || '', fileKey);
const { error: deleteError } = await supabase.from('files').delete().eq('id', fileRecord.id);
if (deleteError) {

View file

@ -1,9 +1,7 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { supabase, createSupabaseClient } from '@/utils/supabase';
import { corsAllMethods, runMiddleware } from '@/utils/cors';
import { GetObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import { s3Client } from '@/utils/s3';
import { getDownloadSignedUrl } from '@/utils/r2';
const getUserAndToken = async (authHeader: string | undefined) => {
if (!authHeader) return {};
@ -56,15 +54,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
return res.status(403).json({ error: 'Unauthorized access to the file' });
}
const getCommand = new GetObjectCommand({
Bucket: process.env['R2_BUCKET_NAME'] || '',
Key: fileKey,
});
try {
const downloadUrl = await getSignedUrl(s3Client, getCommand, {
expiresIn: 1800,
});
const bucketName = process.env['R2_BUCKET_NAME'] || '';
const downloadUrl = await getDownloadSignedUrl(bucketName, fileKey, 1800);
res.status(200).json({
downloadUrl,

View file

@ -1,10 +1,8 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { supabase, createSupabaseClient } from '@/utils/supabase';
import { corsAllMethods, runMiddleware } from '@/utils/cors';
import { PutObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import { getStoragePlanData } from '@/utils/access';
import { s3Client } from '@/utils/s3';
import { getUploadSignedUrl } from '@/utils/r2';
const getUserAndToken = async (authHeader: string | undefined) => {
if (!authHeader) return {};
@ -75,19 +73,13 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
if (insertError) return res.status(500).json({ error: insertError.message });
}
const signableHeaders = new Set<string>();
signableHeaders.add('content-length');
const putCommand = new PutObjectCommand({
Bucket: process.env['R2_BUCKET_NAME'] || '',
Key: objectKey,
ContentLength: objSize,
});
try {
const uploadUrl = await getSignedUrl(s3Client, putCommand, {
expiresIn: 1800,
signableHeaders,
});
const uploadUrl = await getUploadSignedUrl(
process.env['R2_BUCKET_NAME'] || '',
objectKey,
objSize,
1800,
);
res.status(200).json({
uploadUrl,

View file

@ -0,0 +1,60 @@
import { AwsClient } from 'aws4fetch';
const getR2Client = () => {
return new AwsClient({
service: 's3',
region: process.env['R2_REGION'] || 'auto',
accessKeyId: process.env['R2_ACCESS_KEY_ID']!,
secretAccessKey: process.env['R2_SECRET_ACCESS_KEY']!,
});
};
const getR2Url = () => {
const R2_ACCOUNT_ID = process.env['R2_ACCOUNT_ID']!;
return `https://${R2_ACCOUNT_ID}.r2.cloudflarestorage.com`;
};
export const getDownloadSignedUrl = async (
bucketName: string,
fileKey: string,
expiresIn: number,
) => {
return (
await getR2Client().sign(
new Request(`${getR2Url()}/${bucketName}/${fileKey}?X-Amz-Expires=${expiresIn}`),
{
aws: { signQuery: true },
},
)
).url.toString();
};
export const getUploadSignedUrl = async (
bucketName: string,
fileKey: string,
contentLength: number,
expiresIn: number,
) => {
return (
await getR2Client().sign(
new Request(
`${getR2Url()}/${bucketName}/${fileKey}?X-Amz-Expires=${expiresIn}&X-Amz-SignedHeaders=content-length`,
{
method: 'PUT',
headers: {
'Content-Length': contentLength.toString(),
},
},
),
{
aws: { signQuery: true },
},
)
).url.toString();
};
export const deleteObject = async (bucketName: string, fileKey: string) => {
return await getR2Client().fetch(`${getR2Url()}/${bucketName}/${fileKey}`, {
method: 'DELETE',
});
};

3195
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff