Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
4. [Resource Managers](#-resource-managers)
- [`.collection()`](#collectionresource)
- [`.single()`](#singleresource)
- [`.files`](#files)
5. [Debug](#-debug)
6. [Demo Projects](#-demo-projects)
7. [Contributing](./CONTRIBUTING.md)
Expand Down Expand Up @@ -200,6 +201,55 @@ const updatedHomepage = await homepage.update(
await homepage.delete();
```

### .files

The `files` property provides access to the Strapi Media Library through the Upload plugin. It allows you to retrieve files metadata without directly interacting with the REST API.

#### Methods

- `find(params?: FileQueryParams): Promise<FileListResponse>` - Retrieves a list of file metadata based on optional query parameters
- `findOne(fileId: number): Promise<FileResponse>` - Retrieves the metadata for a single file by its ID

#### Example: Finding Files

```typescript
// Initialize the client
const client = strapi({
baseURL: 'http://localhost:1337/api',
auth: 'your-api-token',
});

// Find all file metadata
const allFiles = await client.files.find();
console.log(allFiles);

// Find file metadata with filtering and sorting
const imageFiles = await client.files.find({
filters: {
mime: { $contains: 'image' }, // Only get image files
name: { $contains: 'avatar' }, // Only get files with 'avatar' in the name
},
sort: ['name:asc'], // Sort by name in ascending order
});
```

#### Example: Finding a Single File

```typescript
// Initialize the client
const client = strapi({
baseURL: 'http://localhost:1337/api',
auth: 'your-api-token',
});

// Find file metadata by ID
const file = await client.files.findOne(1);

console.log(file.name); // The file name
console.log(file.url); // The file URL
console.log(file.mime); // The file MIME type
```

## 🐛 Debug

This section provides guidance on enabling and managing debug logs for the SDK,
Expand Down Expand Up @@ -252,6 +302,7 @@ Below is a list of available namespaces to use:
| `strapi:ct:collection` | Logs interactions with collection-type content managers. |
| `strapi:ct:single` | Logs interactions with single-type content managers. |
| `strapi:utils:url-helper` | Logs URL helper utility operations (e.g., appending query parameters or formatting URLs). |
| `strapi:files` | Logs interactions with the files manager. |

## 🚀 Demo Projects

Expand Down
15 changes: 10 additions & 5 deletions demo/.strapi-app/data/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,23 +30,28 @@
"categories": [
{
"name": "news",
"slug": "news"
"slug": "news",
"image": "what-s-inside-a-black-hole.jpg"
},
{
"name": "tech",
"slug": "tech"
"slug": "tech",
"image": "a-bug-is-becoming-a-meme-on-the-internet.jpg"
},
{
"name": "food",
"slug": "food"
"slug": "food",
"image": "we-love-pizza.jpg"
},
{
"name": "nature",
"slug": "nature"
"slug": "nature",
"image": "beautiful-picture.jpg"
},
{
"name": "story",
"slug": "story"
"slug": "story",
"image": "the-internet-s-own-boy.jpg"
}
],
"authors": [
Expand Down
10 changes: 9 additions & 1 deletion demo/.strapi-app/scripts/seed.js
Original file line number Diff line number Diff line change
Expand Up @@ -484,7 +484,15 @@ async function importAbout() {
*/
async function importCategories() {
for (const category of categories) {
await createEntry({ model: 'category', entry: category });
const image = category.image ? await fileHandler.sync([category.image]) : null;

await createEntry({
model: 'category',
entry: {
...category,
image,
},
});
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@
},
"description": {
"type": "text"
},
"image": {
"type": "media",
"multiple": false,
"required": false,
"allowedTypes": ["images"]
}
}
}
4 changes: 4 additions & 0 deletions demo/.strapi-app/types/generated/contentTypes.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,7 @@ export interface ApiCategoryCategory extends Struct.CollectionTypeSchema {
createdAt: Schema.Attribute.DateTime;
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private;
description: Schema.Attribute.Text;
image: Schema.Attribute.Media<'images'>;
locale: Schema.Attribute.String & Schema.Attribute.Private;
localizations: Schema.Attribute.Relation<'oneToMany', 'api::category.category'> &
Schema.Attribute.Private;
Expand Down Expand Up @@ -885,6 +886,8 @@ export interface PluginUsersPermissionsUser extends Struct.CollectionTypeSchema
publishedAt: Schema.Attribute.DateTime;
resetPasswordToken: Schema.Attribute.String & Schema.Attribute.Private;
role: Schema.Attribute.Relation<'manyToOne', 'plugin::users-permissions.role'>;
strapi_assignee: Schema.Attribute.Relation<'oneToOne', 'admin::user'>;
strapi_stage: Schema.Attribute.Relation<'oneToOne', 'plugin::review-workflows.workflow-stage'>;
updatedAt: Schema.Attribute.DateTime;
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private;
username: Schema.Attribute.String &
Expand All @@ -901,6 +904,7 @@ declare module '@strapi/strapi' {
export interface ContentTypeSchemas {
'admin::api-token': AdminApiToken;
'admin::api-token-permission': AdminApiTokenPermission;
'admin::audit-log': AdminAuditLog;
'admin::permission': AdminPermission;
'admin::role': AdminRole;
'admin::transfer-token': AdminTransferToken;
Expand Down
12 changes: 12 additions & 0 deletions demo/next-server-components/next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,18 @@ const nextConfig: NextConfig = {
hmrRefreshes: true,
},
},

// Allow images from Strapi server
images: {
remotePatterns: [
{
protocol: 'http',
hostname: 'localhost',
port: '1337',
pathname: '/**',
},
],
},
};

export default nextConfig;
195 changes: 195 additions & 0 deletions demo/next-server-components/src/app/category-files/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
import { strapi } from '@strapi/client';
import Image from 'next/image';
import Link from 'next/link';

interface CategoryImage {
id: number;
name: string;
alternativeText: string | null;
caption: string | null;
width: number;
height: number;
formats: Record<string, unknown>;
hash: string;
ext: string;
mime: string;
size: number;
url: string;
previewUrl: string | null;
provider: string;
createdAt: string;
updatedAt: string;
}

interface Category {
id: number;
name: string;
slug: string;
image: CategoryImage | null;
createdAt: string;
updatedAt: string;
}

interface PaginationMeta {
page: number;
pageSize: number;
pageCount: number;
total: number;
}

interface CategoryResponse {
data: Category[];
meta: {
pagination: PaginationMeta;
};
}

function CategoryCard({ category }: { category: Category }) {
const hasImage = category.image;

return (
<div key={category.id} className="border rounded-lg overflow-hidden shadow-md">
<div className="p-4 bg-gray-100">
<h2 className="text-xl font-semibold">{category.name}</h2>
<p className="text-gray-600">Slug: {category.slug}</p>
</div>

{hasImage ? (
<div className="relative h-48 w-full">
<Image
src={`http://localhost:1337${category.image!.url}`}
alt={category.name}
fill
className="object-cover"
/>
<div className="absolute bottom-0 left-0 right-0 bg-black bg-opacity-50 text-white p-2 text-sm">
<p>Image: {category.image!.name}</p>
<p>Size: {formatFileSize(category.image!.size)}</p>
</div>
</div>
) : (
<div className="h-48 w-full bg-gray-200 flex items-center justify-center">
<p className="text-gray-500">No image available</p>
</div>
)}
</div>
);
}

// Helper function to format file sizes in a human-readable format
function formatFileSize(bytes: number): string {
if (bytes < 1024) return `${bytes} B`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
}

// Mock data for build/fallback
const fallbackCategories: Category[] = [
{
id: 1,
name: 'News',
slug: 'news',
image: null,
createdAt: '2023-01-01T00:00:00.000Z',
updatedAt: '2023-01-01T00:00:00.000Z',
},
{
id: 2,
name: 'Tech',
slug: 'tech',
image: null,
createdAt: '2023-01-01T00:00:00.000Z',
updatedAt: '2023-01-01T00:00:00.000Z',
},
{
id: 3,
name: 'Food',
slug: 'food',
image: null,
createdAt: '2023-01-01T00:00:00.000Z',
updatedAt: '2023-01-01T00:00:00.000Z',
},
];

export const revalidate = 60; // Revalidate once per minute

// This page demonstrates interaction with files associated with categories
// It shows how to fetch and display category images
export default async function CategoryFiles() {
let categories = fallbackCategories;
let isError = false;
let errorMessage = '';

try {
// Create the Strapi client instance and get the read-only API token from environment variables
const api_token = process.env.READ_ONLY_TOKEN;

// Only attempt to fetch data if we have a token and we're not in build time
if (api_token) {
const client = strapi({ baseURL: 'http://localhost:1337/api', auth: api_token });

// Create a collection type query manager for the categories
const categoriesApi = client.collection('categories');

// Fetch all categories with their related images
const result = (await categoriesApi.find({
populate: ['image'],
})) as unknown as CategoryResponse;

if (result && result.data) {
categories = result.data;
}
} else {
// No token available, likely in build environment
console.warn('API token not available. Using fallback data.');
isError = true;
errorMessage = 'API token not available. This is likely happening during build time.';
}
} catch (error) {
console.error('Error fetching categories:', error);
isError = true;
errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
}

return (
<div className="container mx-auto px-4 py-8">
<h1 className="text-3xl font-bold mb-6">Categories with Files</h1>
<p className="mb-6">
This example shows how to work with files associated with categories in Strapi.
<br />
<Link href="/" className="text-blue-500 hover:underline">
Back to Home
</Link>
</p>

{isError && (
<div className="bg-yellow-100 border-l-4 border-yellow-500 text-yellow-700 p-4 mb-6">
<p className="font-bold">Note:</p>
<p>
{errorMessage}
<br />
Showing fallback data instead. When running the application with a proper Strapi server,
you&apos;ll see real data with images.
</p>
</div>
)}

<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{categories.map((category) => (
<CategoryCard key={category.id} category={category} />
))}
</div>

<div className="mt-10">
<h2 className="text-2xl font-bold mb-4">About File Handling</h2>
<p className="mb-4">This example demonstrates:</p>
<ul className="list-disc pl-5 mb-6">
<li>Retrieving categories with their related images</li>
<li>Displaying image metadata (name, size, etc.)</li>
<li>Rendering images using Next.js Image component</li>
</ul>
</div>
</div>
);
}
Loading