first commit
This commit is contained in:
157
app/asc/actions.ts
Normal file
157
app/asc/actions.ts
Normal file
@@ -0,0 +1,157 @@
|
||||
"use server";
|
||||
|
||||
import { appleApiRequest } from "./api";
|
||||
import { revalidatePath } from "next/cache";
|
||||
|
||||
// ─── App Info ───────────────────────────────────────────────────────────────
|
||||
|
||||
export async function getAppDetails(appleId: string) {
|
||||
if (!appleId) return { data: null, error: "Apple ID eksik" };
|
||||
return await appleApiRequest(`/apps/${appleId}?fields[apps]=name,bundleId,sku,primaryLocale,contentRightsDeclaration,isOrEverWasMadeForKids`);
|
||||
}
|
||||
|
||||
// ─── App Store Versions ─────────────────────────────────────────────────────
|
||||
|
||||
export async function getAppStoreVersions(appleId: string) {
|
||||
if (!appleId) return { data: null, error: "Apple ID eksik" };
|
||||
return await appleApiRequest(
|
||||
`/apps/${appleId}/appStoreVersions?filter[platform]=IOS&limit=5&fields[appStoreVersions]=versionString,appStoreState,releaseType,createdDate,downloadable,copyright`
|
||||
);
|
||||
}
|
||||
|
||||
export async function getActiveVersion(appleId: string) {
|
||||
if (!appleId) return { data: null, error: "Apple ID eksik" };
|
||||
return await appleApiRequest(
|
||||
`/apps/${appleId}/appStoreVersions?filter[platform]=IOS&filter[appStoreState]=READY_FOR_SALE,IN_REVIEW,WAITING_FOR_REVIEW,PENDING_DEVELOPER_RELEASE,PREPARE_FOR_SUBMISSION&limit=1&fields[appStoreVersions]=versionString,appStoreState,releaseType,createdDate,copyright`
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Localizations (Metadata) ───────────────────────────────────────────────
|
||||
|
||||
export async function getVersionLocalizations(versionId: string) {
|
||||
if (!versionId) return { data: null, error: "Version ID eksik" };
|
||||
return await appleApiRequest(
|
||||
`/appStoreVersions/${versionId}/appStoreVersionLocalizations?fields[appStoreVersionLocalizations]=locale,name,subtitle,description,keywords,whatsNew,promotionalText,marketingUrl,supportUrl`
|
||||
);
|
||||
}
|
||||
|
||||
export async function updateLocalization(
|
||||
localizationId: string,
|
||||
payload: {
|
||||
name?: string;
|
||||
subtitle?: string;
|
||||
description?: string;
|
||||
keywords?: string;
|
||||
whatsNew?: string;
|
||||
promotionalText?: string;
|
||||
marketingUrl?: string;
|
||||
supportUrl?: string;
|
||||
}
|
||||
) {
|
||||
return await appleApiRequest(`/appStoreVersionLocalizations/${localizationId}`, {
|
||||
method: "PATCH",
|
||||
body: JSON.stringify({
|
||||
data: {
|
||||
type: "appStoreVersionLocalizations",
|
||||
id: localizationId,
|
||||
attributes: payload,
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
// ─── Reviews & Ratings ──────────────────────────────────────────────────────
|
||||
|
||||
export async function getCustomerReviews(appleId: string) {
|
||||
if (!appleId) return { data: null, error: "Apple ID eksik" };
|
||||
return await appleApiRequest(
|
||||
`/apps/${appleId}/customerReviews?sort=-createdDate&limit=10&fields[customerReviews]=rating,title,body,reviewerNickname,createdDate,territory`
|
||||
);
|
||||
}
|
||||
|
||||
export async function respondToReview(reviewId: string, responseBody: string) {
|
||||
// Check existing response first
|
||||
const existing = await appleApiRequest(`/customerReviews/${reviewId}/response`);
|
||||
|
||||
if (existing.data?.data) {
|
||||
// PATCH
|
||||
return await appleApiRequest(`/customerReviewResponses/${existing.data.data.id}`, {
|
||||
method: "PATCH",
|
||||
body: JSON.stringify({
|
||||
data: {
|
||||
type: "customerReviewResponses",
|
||||
id: existing.data.data.id,
|
||||
attributes: { responseBody },
|
||||
},
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
// POST
|
||||
return await appleApiRequest(`/customerReviewResponses`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
data: {
|
||||
type: "customerReviewResponses",
|
||||
attributes: { responseBody },
|
||||
relationships: {
|
||||
review: { data: { type: "customerReviews", id: reviewId } },
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Builds ─────────────────────────────────────────────────────────────────
|
||||
|
||||
export async function getLatestBuilds(appleId: string) {
|
||||
if (!appleId) return { data: null, error: "Apple ID eksik" };
|
||||
return await appleApiRequest(
|
||||
`/builds?filter[app]=${appleId}&limit=10&sort=-uploadedDate&fields[builds]=version,uploadedDate,processingState,usesNonExemptEncryption,minOsVersion`
|
||||
);
|
||||
}
|
||||
|
||||
// ─── TestFlight ─────────────────────────────────────────────────────────────
|
||||
|
||||
export async function getBetaGroups(appleId: string) {
|
||||
if (!appleId) return { data: null, error: "Apple ID eksik" };
|
||||
return await appleApiRequest(
|
||||
`/apps/${appleId}/betaGroups?fields[betaGroups]=name,isInternalGroup,publicLink,publicLinkEnabled,publicLinkLimit,publicLinkLimitEnabled,createdDate`
|
||||
);
|
||||
}
|
||||
|
||||
export async function getBetaTesters(groupId: string) {
|
||||
if (!groupId) return { data: null, error: "Group ID eksik" };
|
||||
return await appleApiRequest(
|
||||
`/betaGroups/${groupId}/betaTesters?fields[betaTesters]=firstName,lastName,email,inviteType,state&limit=25`
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Pricing ────────────────────────────────────────────────────────────────
|
||||
|
||||
export async function getAppPricing(appleId: string) {
|
||||
if (!appleId) return { data: null, error: "Apple ID eksik" };
|
||||
return await appleApiRequest(
|
||||
`/apps/${appleId}/pricePoints?filter[territory]=USA&fields[appPricePoints]=customerPrice,proceeds,territory&limit=5`
|
||||
);
|
||||
}
|
||||
|
||||
// ─── App Store Review Submissions ───────────────────────────────────────────
|
||||
|
||||
export async function getReviewSubmissions(appleId: string) {
|
||||
if (!appleId) return { data: null, error: "Apple ID eksik" };
|
||||
return await appleApiRequest(
|
||||
`/appStoreVersionSubmissions?filter[appStoreVersion.app]=${appleId}&limit=5`
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Analytics Summary (via App Store Version) ──────────────────────────────
|
||||
|
||||
export async function getAppStatus(appleId: string) {
|
||||
if (!appleId) return { data: null, error: "Apple ID eksik" };
|
||||
return await getActiveVersion(appleId);
|
||||
}
|
||||
|
||||
export async function getTestFlightGroups(appleId: string) {
|
||||
return getBetaGroups(appleId);
|
||||
}
|
||||
47
app/asc/api.ts
Normal file
47
app/asc/api.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import * as jose from 'jose';
|
||||
|
||||
const ISSUER_ID = process.env.ASC_ISSUER_ID!;
|
||||
const KEY_ID = process.env.ASC_KEY_ID!;
|
||||
const PRIVATE_KEY = process.env.ASC_PRIVATE_KEY?.replace(/\\n/g, '\n')!;
|
||||
|
||||
/**
|
||||
* App Store Connect API için JWT oluşturur (20 dakika geçerli)
|
||||
*/
|
||||
async function generateJwt() {
|
||||
const privateKey = await jose.importPKCS8(PRIVATE_KEY, 'ES256');
|
||||
|
||||
return await new jose.SignJWT({})
|
||||
.setProtectedHeader({ alg: 'ES256', kid: KEY_ID, typ: 'JWT' })
|
||||
.setIssuer(ISSUER_ID)
|
||||
.setAudience('appstoreconnect-v1')
|
||||
.setIssuedAt()
|
||||
.setExpirationTime('20m')
|
||||
.sign(privateKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apple Store Connect API'ye istek atar
|
||||
*/
|
||||
export async function appleApiRequest(path: string, options: RequestInit = {}) {
|
||||
const token = await generateJwt();
|
||||
|
||||
const url = `https://api.appstoreconnect.apple.com/v1${path}`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
...options,
|
||||
headers: {
|
||||
...options.headers,
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
console.error('Apple API Error:', data);
|
||||
return { data: null, error: data.errors?.[0]?.detail || 'Unknown error' };
|
||||
}
|
||||
|
||||
return { data, error: null };
|
||||
}
|
||||
19
app/asc/utils.ts
Normal file
19
app/asc/utils.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { exec } from "child_process";
|
||||
import { promisify } from "util";
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
export async function runAscCommand(command: string) {
|
||||
// Use environment variables for authentication
|
||||
// The asc CLI usually looks for these or can be passed as flags
|
||||
const authFlags = `--issuer ${process.env.ASC_ISSUER_ID} --key-id ${process.env.ASC_KEY_ID} --key "${process.env.ASC_PRIVATE_KEY?.replace(/\\n/g, '\n')}"`;
|
||||
|
||||
try {
|
||||
const { stdout, stderr } = await execAsync(`asc ${command} ${authFlags}`);
|
||||
if (stderr) console.error("ASC Error:", stderr);
|
||||
return { data: stdout, error: stderr };
|
||||
} catch (error: any) {
|
||||
console.error("Exec Error:", error);
|
||||
return { data: null, error: error.message };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user