Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
34 changes: 34 additions & 0 deletions next.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type {NextConfig} from 'next';

const nextConfig: NextConfig = {
typescript: {
ignoreBuildErrors: true,
},
eslint: {
ignoreDuringBuilds: true,
},
Comment on lines +4 to +9

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

Disabling TypeScript and ESLint checks during the build process is highly discouraged for production applications. This can lead to deploying code with type errors or linting issues, which can cause runtime errors and make the codebase harder to maintain. It's recommended to enable these checks to ensure code quality and stability.

Suggested change
typescript: {
ignoreBuildErrors: true,
},
eslint: {
ignoreDuringBuilds: true,
},
typescript: {
ignoreBuildErrors: false,
},
eslint: {
ignoreDuringBuilds: false,
},

images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'placehold.co',
port: '',
pathname: '/**',
},
{
protocol: 'https',
hostname: 'images.unsplash.com',
port: '',
pathname: '/**',
},
{
protocol: 'https',
hostname: 'picsum.photos',
port: '',
pathname: '/**',
},
],
},
};

export default nextConfig;
6 changes: 6 additions & 0 deletions src/ai/dev.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { config } from 'dotenv';
config();

import '@/ai/flows/analyze-photo-for-content-suggestions.ts';
import '@/ai/flows/improve-existing-post.ts';
import '@/ai/flows/generate-content-from-comment.ts';
54 changes: 54 additions & 0 deletions src/ai/flows/analyze-photo-for-content-suggestions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
'use server';
/**
* @fileOverview Analyzes a photo to suggest relevant topics and content ideas for a WordPress post.
*
* - analyzePhotoForContentSuggestions - A function that handles the photo analysis and content suggestion process.
* - AnalyzePhotoForContentSuggestionsInput - The input type for the analyzePhotoForContentSuggestions function.
* - AnalyzePhotoForContentSuggestionsOutput - The return type for the analyzePhotoForContentSuggestions function.
*/

import {ai} from '@/ai/genkit';
import {z} from 'genkit';

const AnalyzePhotoForContentSuggestionsInputSchema = z.object({
photoDataUri: z
.string()
.describe(
"A photo, as a data URI that must include a MIME type and use Base64 encoding. Expected format: 'data:<mimetype>;base64,<encoded_data>'."
),
});
export type AnalyzePhotoForContentSuggestionsInput = z.infer<typeof AnalyzePhotoForContentSuggestionsInputSchema>;

const AnalyzePhotoForContentSuggestionsOutputSchema = z.object({
suggestedTopics: z.array(z.string()).describe('A list of suggested topics for a WordPress post based on the photo.'),
contentIdeas: z.array(z.string()).describe('A list of content ideas for a WordPress post based on the photo.'),
});
export type AnalyzePhotoForContentSuggestionsOutput = z.infer<typeof AnalyzePhotoForContentSuggestionsOutputSchema>;

export async function analyzePhotoForContentSuggestions(input: AnalyzePhotoForContentSuggestionsInput): Promise<AnalyzePhotoForContentSuggestionsOutput> {
return analyzePhotoForContentSuggestionsFlow(input);
}

const prompt = ai.definePrompt({
name: 'analyzePhotoForContentSuggestionsPrompt',
input: {schema: AnalyzePhotoForContentSuggestionsInputSchema},
output: {schema: AnalyzePhotoForContentSuggestionsOutputSchema},
prompt: `You are an expert content strategist for WordPress. You will analyze the photo provided and suggest relevant topics and content ideas for a WordPress post.

Photo: {{media url=photoDataUri}}

Please provide the suggested topics and content ideas in a JSON format.
`,
});

const analyzePhotoForContentSuggestionsFlow = ai.defineFlow(
{
name: 'analyzePhotoForContentSuggestionsFlow',
inputSchema: AnalyzePhotoForContentSuggestionsInputSchema,
outputSchema: AnalyzePhotoForContentSuggestionsOutputSchema,
},
async input => {
const {output} = await prompt(input);
return output!;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using a non-null assertion (!) on the output from the AI prompt is unsafe. If the AI model fails to return an output for any reason (e.g., content filtering, network issues), this will cause a runtime crash. It's better to handle this case gracefully by checking if output is defined and throwing a descriptive error if it's not. This pattern is repeated in other flow files (generate-content-from-comment.ts, improve-existing-post.ts) and should be addressed there as well.

    if (!output) {
      throw new Error('Failed to get a response from the AI model.');
    }
    return output;

}
);
77 changes: 77 additions & 0 deletions src/ai/flows/generate-content-from-comment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// src/ai/flows/generate-content-from-comment.ts
'use server';
/**
* @fileOverview Generates WordPress content (title, body, tags) from a user comment.
*
* - generateContentFromComment - A function that generates WordPress content based on a comment.
* - GenerateContentFromCommentInput - The input type for the generateContentFromComment function.
* - GenerateContentFromCommentOutput - The return type for the generateContentFromComment function.
*/

import {ai} from '@/ai/genkit';
import {z} from 'genkit';

const GenerateContentFromCommentInputSchema = z.object({
comment: z.string().describe('The comment to generate content from.'),
});
export type GenerateContentFromCommentInput = z.infer<typeof GenerateContentFromCommentInputSchema>;

const GenerateContentFromCommentOutputSchema = z.object({
title: z.string().describe('The title of the WordPress post.'),
body: z.string().describe('The body of the WordPress post.'),
tags: z.array(z.string()).describe('Suggested tags for the WordPress post.'),
});
export type GenerateContentFromCommentOutput = z.infer<typeof GenerateContentFromCommentOutputSchema>;

export async function generateContentFromComment(input: GenerateContentFromCommentInput): Promise<GenerateContentFromCommentOutput> {
return generateContentFromCommentFlow(input);
}

const prompt = ai.definePrompt({
name: 'generateContentFromCommentPrompt',
input: {schema: GenerateContentFromCommentInputSchema},
output: {schema: GenerateContentFromCommentOutputSchema},
prompt: `You are an AI assistant that generates WordPress content based on user comments.

Based on the following comment, generate a title, body, and suggested tags for a WordPress post.

Comment: {{{comment}}}

The title should be concise and engaging.
The body should be informative and well-written.
The tags should be relevant and helpful for SEO.

Ensure that the title, body and tags are appropriate and professional.
`,config: {
safetySettings: [
{
category: 'HARM_CATEGORY_HATE_SPEECH',
threshold: 'BLOCK_ONLY_HIGH',
},
{
category: 'HARM_CATEGORY_DANGEROUS_CONTENT',
threshold: 'BLOCK_NONE',
},
Comment on lines +51 to +54

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The safety setting for HARM_CATEGORY_DANGEROUS_CONTENT is set to BLOCK_NONE, which means the AI could potentially generate content that is dangerous or promotes harmful acts. This is a significant security risk. Unless there is a specific reason to allow this type of content, it's highly recommended to set a more restrictive threshold, such as BLOCK_MEDIUM_AND_ABOVE. This same configuration is present in src/ai/flows/improve-existing-post.ts and should also be reviewed.

{
category: 'HARM_CATEGORY_HARASSMENT',
threshold: 'BLOCK_MEDIUM_AND_ABOVE',
},
{
category: 'HARM_CATEGORY_SEXUALLY_EXPLICIT',
threshold: 'BLOCK_LOW_AND_ABOVE',
},
],
},
});

const generateContentFromCommentFlow = ai.defineFlow(
{
name: 'generateContentFromCommentFlow',
inputSchema: GenerateContentFromCommentInputSchema,
outputSchema: GenerateContentFromCommentOutputSchema,
},
async input => {
const {output} = await prompt(input);
return output!;
}
);
77 changes: 77 additions & 0 deletions src/ai/flows/improve-existing-post.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
'use server';

/**
* @fileOverview Rewrites an existing blog post to match a desired style, generating a new title and tags.
*
* - improveExistingPost - A function that handles the rewriting process.
* - ImproveExistingPostInput - The input type for the improveExistingPost function.
* - ImproveExistingPostOutput - The return type for the improveExistingPost function.
*/

import {ai} from '@/ai/genkit';
import {z} from 'genkit';

const ImproveExistingPostInputSchema = z.object({
existingPost: z.string().describe('The existing blog post content.'),
desiredStyle: z.string().describe('The desired style for the blog post.'),
});
export type ImproveExistingPostInput = z.infer<typeof ImproveExistingPostInputSchema>;

const ImproveExistingPostOutputSchema = z.object({
title: z.string().describe('The new, improved title for the blog post.'),
body: z.string().describe('The rewritten blog post content.'),
tags: z.array(z.string()).describe('A list of relevant tags for the rewritten post.'),
});
export type ImproveExistingPostOutput = z.infer<typeof ImproveExistingPostOutputSchema>;

export async function improveExistingPost(input: ImproveExistingPostInput): Promise<ImproveExistingPostOutput> {
return improveExistingPostFlow(input);
}

const prompt = ai.definePrompt({
name: 'improveExistingPostPrompt',
input: {schema: ImproveExistingPostInputSchema},
output: {schema: ImproveExistingPostOutputSchema},
prompt: `You are an expert blog post writer. You will rewrite the existing blog post to match the desired style.
You must also generate a new, catchy title for the post and a list of relevant tags.

Existing Blog Post:
{{{existingPost}}}

Desired Style:
{{{desiredStyle}}}

Respond with the rewritten post body, a new title, and new tags in the specified JSON format.`,
config: {
safetySettings: [
{
category: 'HARM_CATEGORY_HATE_SPEECH',
threshold: 'BLOCK_ONLY_HIGH',
},
{
category: 'HARM_CATEGORY_DANGEROUS_CONTENT',
threshold: 'BLOCK_NONE',
},
{
category: 'HARM_CATEGORY_HARASSMENT',
threshold: 'BLOCK_MEDIUM_AND_ABOVE',
},
{
category: 'HARM_CATEGORY_SEXUALLY_EXPLICIT',
threshold: 'BLOCK_LOW_AND_ABOVE',
},
],
},
});

const improveExistingPostFlow = ai.defineFlow(
{
name: 'improveExistingPostFlow',
inputSchema: ImproveExistingPostInputSchema,
outputSchema: ImproveExistingPostOutputSchema,
},
async input => {
const {output} = await prompt(input);
return output!;
}
);
7 changes: 7 additions & 0 deletions src/ai/genkit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import {genkit} from 'genkit';
import {googleAI} from '@genkit-ai/google-genai';

export const ai = genkit({
plugins: [googleAI()],
model: 'googleai/gemini-2.5-flash',
});
5 changes: 5 additions & 0 deletions src/app/admin/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { ReactNode } from 'react';

export default function AdminLayout({ children }: { children: ReactNode }) {
return <div className="min-h-screen bg-background text-foreground">{children}</div>;
}
59 changes: 59 additions & 0 deletions src/app/admin/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
'use client';

import { ArrowLeft, Shield, UserCog } from 'lucide-react';
import Link from 'next/link';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';

export default function AdminPage() {
return (
<div className="container mx-auto py-8">
<div className="flex items-center justify-between mb-6">
<div className="flex items-center gap-4">
<Shield className="h-8 w-8 text-primary" />
<h1 className="text-3xl font-bold">User Management</h1>
</div>
<Button asChild variant="outline">
<Link href="/">
<ArrowLeft className="mr-2 h-4 w-4" />
Back to App
</Link>
</Button>
</div>

<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<UserCog className="h-6 w-6" />
Admin Panel Functionality
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<p className="text-muted-foreground">
This page is a placeholder for user management functionality. Listing all users from the client is disabled by default for security reasons.
</p>
<div className="rounded-lg border border-blue-500/50 bg-blue-50 p-4 text-blue-900 dark:bg-blue-950 dark:text-blue-200">
<h3 className="font-semibold">How to Implement This Securely</h3>
<p className="mt-2 text-sm">
To build a secure admin panel, you should use Firebase's server-side features:
</p>
<ol className="mt-2 list-decimal pl-5 text-sm space-y-1">
<li>
<strong>Custom Claims:</strong> Assign an `admin: true` custom claim to trusted users using the Firebase Admin SDK (typically in a Cloud Function).
</li>
<li>
<strong>Security Rules:</strong> Update your Firestore security rules to only allow users with the `admin: true` claim to list all documents in the `/users` collection.
</li>
<li>
<strong>Backend Logic:</strong> For advanced user management (like deleting users or changing roles), create secure Cloud Functions that can be called from this admin panel.
</li>
</ol>
</div>
<p className="text-sm text-muted-foreground">
This ensures that only authorized administrators can view and manage user data, protecting the privacy of your users.
</p>
</CardContent>
</Card>
</div>
);
}
Binary file added src/app/favicon.ico
Binary file not shown.
Loading