diff --git a/.github/AUTOMATIC_RELEASES.md b/.github/AUTOMATIC_RELEASES.md new file mode 100644 index 0000000..c846b79 --- /dev/null +++ b/.github/AUTOMATIC_RELEASES.md @@ -0,0 +1,218 @@ +# Automatic Release System + +FixFX uses automated versioning and changelog generation based on commit messages. When you push to `develop` or `master`, the system automatically: + +1. **Analyzes commits** since the last release +2. **Determines the next version** (semantic versioning) +3. **Updates CHANGELOG.md** +4. **Creates a GitHub release** with auto-generated notes + +No manual intervention needed! + +## Commit Message Format (Conventional Commits) + +The automation works by analyzing commit messages in this format: + +``` +type: description + +type(scope): description + +type!: description (with breaking change) +``` + +### Types + +- **`feat`** - New feature → Minor version bump (1.0.0 → 1.1.0) +- **`fix`** - Bug fix → Patch version bump (1.0.0 → 1.0.1) +- **`breaking`** or **`feat!`** - Breaking change → Major version bump (1.0.0 → 2.0.0) +- **`chore`** - Build, deps, config changes (NOT included in release) +- **`docs`** - Documentation only (NOT included in release) +- **`test`** - Test changes (NOT included in release) + +### Examples + +```bash +# Feature +git commit -m "feat: add hosting provider directory structure" + +# Fix +git commit -m "fix: correct provider validation schema reference" + +# With scope +git commit -m "feat(providers): add guidelines documentation" + +# Breaking change (using !) +git commit -m "feat!: reorganize provider file structure" + +# Breaking change (using breaking keyword) +git commit -m "breaking: remove deprecated API endpoints" + +# Chore (won't trigger release) +git commit -m "chore: update dependencies" +git commit -m "docs: improve README" +``` + +## Workflow Triggers + +### ✅ Runs Automatically On + +- **Direct pushes to `develop` branch** - Analyzes commits, updates changelog, creates release +- **Direct pushes to `master` branch** - Same as develop +- **Merges to `develop`/`master`** - Same as direct pushes +- **Changes to frontend files** - Only triggers when frontend code changes + +### ❌ Does NOT Run On + +- **Pull requests** - Prevents duplicate automation when merging +- **Non-frontend changes** - Ignores changes to backend, docs, etc. +- **Manual `chore:`, `docs:`, `test:` commits** - No version bump needed + +## What Gets Released + +The system looks at commits since the **last GitHub release tag** and: + +- Counts **breaking changes** → Major version bump +- Counts **features** → Minor version bump +- Counts **fixes** → Patch version bump +- Ignores **chore/docs/test** commits + +### Examples + +``` +Last Release: v1.0.0 + +Commits since: + ✅ feat: add new feature + ✅ fix: fix a bug + +Next Release: v1.1.0 (minor bump for feature) +``` + +``` +Last Release: v1.0.0 + +Commits since: + ✅ breaking: remove old API + ✅ feat: add new feature + ✅ fix: fix a bug + +Next Release: v2.0.0 (major bump for breaking change) +``` + +## Automatic Changelog Generation + +The changelog is automatically generated from your commit messages: + +```markdown +## [1.1.0] - 2026-01-26 + +### Breaking Changes +- Remove deprecated authentication method + +### Added +- Add hosting provider directory structure +- Add provider guidelines documentation + +### Fixed +- Correct schema validation reference +``` + +This appears in: +1. **CHANGELOG.md** - Updated automatically +2. **GitHub Release Notes** - Added automatically + +## Disabling Auto-Release + +If you need to prevent a release for a particular push: + +```bash +# Use [skip-release] in commit message +git commit -m "feat: add feature [skip-release]" + +# Or use non-conventional commit format (will be listed as "Other Changes") +git commit -m "Update something random" +``` + +Note: Non-feature/fix/breaking commits are grouped as "Other Changes" and don't trigger version bumps. + +## Manual Releases + +For complete control, you can still create releases manually: + +1. Push your commits with conventional messages +2. Manually create a release on GitHub with tag `v1.2.3` +3. The system will recognize it as the latest version +4. Next auto-release will calculate from this version + +## Troubleshooting + +### "No pending changes that require a release" + +Your commits don't include `feat:`, `fix:`, or `breaking:` prefixes. + +**Solution:** Use proper conventional commit format + +```bash +# Wrong +git commit -m "added new provider support" + +# Right +git commit -m "feat: add new provider support" +``` + +### Release created but CHANGELOG not updated + +The CHANGELOG update happens in the workflow. Check: +1. Workflow logs in GitHub Actions +2. That commits use conventional format +3. That at least one `feat:`, `fix:`, or `breaking:` commit exists + +### Version not incrementing correctly + +Check the last release tag: + +```bash +git tag # List all tags +git describe --tags --abbrev=0 # Show latest tag +``` + +Make sure the tag follows `vMAJOR.MINOR.PATCH` format. + +## Commit Message Guidelines + +For the best auto-generated changelogs: + +### Good commit messages +``` +feat: add provider guidelines documentation +feat(providers): reorganize directory structure +fix: resolve schema validation issue +breaking: remove deprecated API endpoints +``` + +### Bad commit messages +``` +update stuff +fixed things +WIP: feature +various improvements +``` + +The commit message after the type/scope is included in the changelog, so keep them clear and descriptive! + +## GitHub Actions Secrets + +No additional secrets needed! The workflow uses the default `GITHUB_TOKEN` which has permission to: +- Read commits and tags +- Create releases +- Push changes back to the repository + +## Next Steps + +1. **Start using conventional commits** in your workflow +2. **Push to develop/master** when ready to release +3. **Let the automation handle** changelog and release creation +4. **Monitor GitHub Actions** to verify successful releases + +That's it! No more manual version tracking. diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..7091d70 --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,52 @@ +# Code of Conduct + +## Our Commitment + +We are committed to providing a welcoming, inclusive, and harassment-free environment for everyone who participates in the FixFX project. This applies to all project spaces including GitHub, Discord, and any other communication channels. + +## Expected Behavior + +All participants are expected to: + +- Be respectful and considerate in all interactions +- Provide constructive feedback and accept it gracefully +- Focus on what is best for the community and project +- Show empathy towards other community members +- Use inclusive language + +## Unacceptable Behavior + +The following behaviors are not tolerated: + +- Harassment, intimidation, or discrimination of any kind +- Personal attacks, insults, or derogatory comments +- Trolling or deliberately inflammatory remarks +- Publishing others' private information without consent +- Spam, excessive self-promotion, or off-topic content +- Any conduct that would be considered inappropriate in a professional setting + +## Scope + +This Code of Conduct applies to all project spaces and to individuals representing the project in public spaces. This includes the GitHub repository, Discord server, social media, and any other official channels. + +## Enforcement + +Instances of unacceptable behavior may be reported to the project maintainers: + +- **Email**: [hey@codemeapixel.dev](mailto:hey@codemeapixel.dev) +- **Discord**: [discord.gg/cYauqJfnNK](https://discord.gg/cYauqJfnNK) + +All reports will be reviewed and investigated promptly and fairly. Maintainers are obligated to respect the privacy and security of the reporter. + +### Consequences + +Project maintainers will determine appropriate action for violations, which may include: + +1. A private warning with clarity on the violation +2. A public warning +3. Temporary ban from project spaces +4. Permanent ban from project spaces + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/), version 2.1. diff --git a/CONTRIBUTING.md b/.github/CONTRIBUTING.md similarity index 93% rename from CONTRIBUTING.md rename to .github/CONTRIBUTING.md index 224622b..fb96b22 100644 --- a/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,10 +1,10 @@ # Contributing to FixFX -Thank you for your interest in contributing to FixFX! We welcome contributions from the community and appreciate your help in making FixFX better. +Thank you for your interest in contributing to FixFX. We welcome contributions from the community. ## Code of Conduct -Please be respectful and constructive in all interactions. We are committed to providing a welcoming and inclusive environment for all contributors. +Please read our [Code of Conduct](CODE_OF_CONDUCT.md) before participating. We expect all contributors to be respectful and constructive. ## How to Contribute @@ -283,14 +283,10 @@ When adding new features: ## Questions? -- Check existing issues and discussions -- Ask in our [Discord community](https://discord.gg/ErBmGbZfwT) -- Create a discussion on GitHub +- Check existing [GitHub Issues](https://github.com/CodeMeAPixel/FixFX/issues) +- Join our [Discord](https://discord.gg/cYauqJfnNK) +- Email: [hey@codemeapixel.dev](mailto:hey@codemeapixel.dev) ## License By contributing to FixFX, you agree that your contributions will be licensed under the AGPL 3.0 License. - ---- - -Thank you for contributing to FixFX! Your efforts help make FiveM development more accessible to everyone. diff --git a/.github/SECURITY.md b/.github/SECURITY.md new file mode 100644 index 0000000..ff8c239 --- /dev/null +++ b/.github/SECURITY.md @@ -0,0 +1,69 @@ +# Security Policy + +## Supported Versions + +We actively maintain security updates for the following versions: + +| Version | Supported | +|---------|-----------| +| Latest | Yes | +| < 1.0 | No | + +## Reporting a Vulnerability + +We take security vulnerabilities seriously. If you discover a security issue, please report it responsibly. + +### How to Report + +**Do not open a public issue for security vulnerabilities.** + +Instead, please use one of the following methods: + +1. **Email**: Send details to [hey@codemeapixel.dev](mailto:hey@codemeapixel.dev) +2. **GitHub Security Advisories**: Use the [Security tab](https://github.com/CodeMeAPixel/FixFX/security/advisories/new) to privately report the issue + +### What to Include + +When reporting a vulnerability, please provide: + +- A clear description of the vulnerability +- Steps to reproduce the issue +- Potential impact of the vulnerability +- Any suggested fixes or mitigations (if applicable) + +### Response Timeline + +- **Initial Response**: Within 48 hours +- **Status Update**: Within 7 days +- **Resolution Target**: Within 30 days for critical issues + +### After Reporting + +1. We will acknowledge receipt of your report +2. We will investigate and validate the issue +3. We will work on a fix and coordinate disclosure timing with you +4. We will credit you in the security advisory (unless you prefer anonymity) + +## Security Best Practices + +When contributing to FixFX: + +- Keep dependencies updated +- Never commit secrets, API keys, or credentials +- Follow secure coding practices +- Validate and sanitize all user inputs +- Use environment variables for sensitive configuration + +## Scope + +This security policy applies to: + +- The FixFX frontend application +- The FixFX backend API +- Official deployment infrastructure + +Third-party integrations and dependencies are outside our direct control but we will work with upstream maintainers when issues are discovered. + +## Recognition + +We appreciate security researchers who help keep FixFX safe. Contributors who responsibly disclose vulnerabilities will be acknowledged in our security advisories and README. diff --git a/.github/VERSIONING.md b/.github/VERSIONING.md new file mode 100644 index 0000000..7a02fae --- /dev/null +++ b/.github/VERSIONING.md @@ -0,0 +1,154 @@ +# Versioning Guide + +FixFX uses GitHub releases as the single source of truth for version numbers. This approach eliminates version duplication between `package.json` and `CHANGELOG.md`. + +## How It Works + +1. **GitHub Releases** are authoritative - version lives in release tags (e.g., `v1.0.0`) +2. **CHANGELOG.md** documents changes for each version +3. **Script** (`get-version.js`) fetches the latest release from GitHub API +4. **package.json** does NOT have a hardcoded version field + +## Getting the Current Version + +### From Command Line + +```bash +# Get version as plain text +npm run version + +# Get version as JSON +npm run version:json + +# Direct script usage +node .github/scripts/get-version.js +``` + +### In JavaScript/TypeScript Code + +```typescript +// Async import and use +import { getVersion } from '../.github/scripts/get-version.js'; + +const version = await getVersion(); +console.log(`FixFX v${version}`); +``` + +### In Build Process + +Add to your build script: + +```bash +node .github/scripts/get-version.js --file public/version.txt +``` + +## Creating a New Release + +When releasing a new version: + +1. **Update CHANGELOG.md** with the version changes +2. **Create a GitHub Release** with tag `v1.0.0` (format: `vMAJOR.MINOR.PATCH`) +3. **Script automatically discovers** the version +4. **No need to update package.json** + +### Example Release Process + +```bash +# 1. Update CHANGELOG.md sections +# Change "## [1.1.0] - Unreleased" to "## [1.1.0] - 2026-01-26" + +# 2. Commit changes +git add CHANGELOG.md +git commit -m "chore: release v1.1.0" +git push + +# 3. Create release on GitHub +# Go to https://github.com/CodeMeAPixel/FixFX/releases/new +# Tag: v1.1.0 +# Title: Release v1.1.0 +# Copy content from CHANGELOG for this version +# Click "Publish release" + +# 4. The version is now discoverable by the script! +npm run version # outputs: 1.1.0 +``` + +## Tag Naming Convention + +- **Must start with `v`**: `v1.0.0` ✅ (not `1.0.0`) +- **Must follow Semantic Versioning**: `vMAJOR.MINOR.PATCH` +- **Optional prerelease**: `v1.0.0-beta.0`, `v1.0.0-rc.1` +- **Invalid**: `v1.0`, `1.0.0`, `version-1.0.0` + +## Fallback Behavior + +If no releases exist yet: +- Script returns `0.0.0-unknown` +- Useful during initial development +- Once you create first release, it auto-discovers + +### For Strict Mode (CI/CD) + +```bash +node .github/scripts/get-version.js --strict +# Exits with code 1 if fetch fails +``` + +## GitHub API Rate Limits + +The script respects GitHub's API rate limits: + +- **Unauthenticated**: 60 requests/hour +- **Authenticated**: 5,000 requests/hour (if `GITHUB_TOKEN` env var set) + +For CI/CD, set the token: + +```bash +GITHUB_TOKEN=your_token npm run version +``` + +## When to Use This Approach + +✅ **Good for:** +- Open source projects with public releases +- Teams that release regularly +- Reducing merge conflicts on version changes +- Keeping version in one place (GitHub) + +❌ **Not ideal for:** +- Private packages without public releases +- High-frequency build systems (performance sensitive) +- Offline-first development (no API access) + +## Troubleshooting + +### "No releases found" + +You haven't created any releases yet. Create the first one: + +```bash +# On GitHub: Releases → Draft a new release +# Tag: v1.0.0 +# Publish +``` + +### "GitHub API request timed out" + +Network issue or GitHub API is slow. Try again or set `GITHUB_TOKEN` for priority. + +### "Invalid version format" + +Release tag doesn't match expected format. Use `vMAJOR.MINOR.PATCH` format. + +## Alternative: Reading from File + +If you prefer storing version in a file instead: + +```json +// version.json +{ + "version": "1.0.0" +} +``` + +But GitHub releases approach is cleaner since releases are already required for distribution. diff --git a/.github/scripts/analyze-commits.js b/.github/scripts/analyze-commits.js new file mode 100644 index 0000000..222ecff --- /dev/null +++ b/.github/scripts/analyze-commits.js @@ -0,0 +1,262 @@ +#!/usr/bin/env node + +/** + * Analyze commits since the last release + * + * Determines the next version based on commit messages using + * conventional commits format (feat:, fix:, breaking:) + * + * Usage: + * node analyze-commits.js + * node analyze-commits.js --json + */ + +const { execSync } = require('child_process'); + +const REPO_OWNER = 'CodeMeAPixel'; +const REPO_NAME = 'FixFX'; + +function exec(command) { + try { + return execSync(command, { encoding: 'utf-8' }).trim(); + } catch (error) { + throw new Error(`Command failed: ${command}\n${error.message}`); + } +} + +function getLastTag() { + try { + return exec('git describe --tags --abbrev=0 2>/dev/null || echo ""'); + } catch { + return ''; + } +} + +function getCommitsSinceTag(tag) { + try { + if (!tag) { + // No tags yet, get all commits + return exec('git log --oneline --all'); + } + return exec(`git log ${tag}..HEAD --oneline`); + } catch (error) { + return ''; + } +} + +function parseVersion(versionString) { + // Remove 'v' prefix if present + const version = versionString.replace(/^v/, ''); + const parts = version.split('.'); + + return { + major: parseInt(parts[0]) || 0, + minor: parseInt(parts[1]) || 0, + patch: parseInt(parts[2]) || 0, + prerelease: parts[3] ? parts.slice(3).join('.') : null, + }; +} + +function parseCommits(commitLog) { + const commits = commitLog.split('\n').filter(Boolean); + + const analysis = { + features: [], + fixes: [], + breaking: [], + other: [], + }; + + for (const commit of commits) { + const match = commit.match(/^([a-f0-9]+)\s+(.+?):\s*(.+?)(?:\s*\((.+?)\))?$/); + + if (!match) { + analysis.other.push(commit); + continue; + } + + const [, hash, type, message, scope] = match; + const commitData = { + hash: hash.substring(0, 7), + type, + message: message.trim(), + scope: scope || null, + full: commit, + }; + + if (type === 'feat') { + analysis.features.push(commitData); + } else if (type === 'fix') { + analysis.fixes.push(commitData); + } else if (type === 'breaking' || message.includes('BREAKING CHANGE')) { + analysis.breaking.push(commitData); + } else { + analysis.other.push(commitData); + } + } + + return analysis; +} + +function calculateNextVersion(currentVersion, analysis) { + const current = parseVersion(currentVersion || '0.0.0'); + + // Breaking changes = major version bump + if (analysis.breaking.length > 0) { + return { + major: current.major + 1, + minor: 0, + patch: 0, + }; + } + + // Features = minor version bump + if (analysis.features.length > 0) { + return { + major: current.major, + minor: current.minor + 1, + patch: 0, + }; + } + + // Fixes only = patch version bump + if (analysis.fixes.length > 0) { + return { + major: current.major, + minor: current.minor, + patch: current.patch + 1, + }; + } + + // No relevant commits + return null; +} + +function formatVersion(versionObj) { + if (!versionObj) return null; + return `${versionObj.major}.${versionObj.minor}.${versionObj.patch}`; +} + +function generateChangelogEntry(version, analysis) { + const date = new Date().toISOString().split('T')[0]; + let entry = `## [${version}] - ${date}\n\n`; + + if (analysis.breaking.length > 0) { + entry += `### Breaking Changes\n`; + for (const commit of analysis.breaking) { + entry += `- ${commit.message}${commit.scope ? ` (${commit.scope})` : ''} (${commit.hash})\n`; + } + entry += '\n'; + } + + if (analysis.features.length > 0) { + entry += `### Added\n`; + for (const commit of analysis.features) { + entry += `- ${commit.message}${commit.scope ? ` (${commit.scope})` : ''} (${commit.hash})\n`; + } + entry += '\n'; + } + + if (analysis.fixes.length > 0) { + entry += `### Fixed\n`; + for (const commit of analysis.fixes) { + entry += `- ${commit.message}${commit.scope ? ` (${commit.scope})` : ''} (${commit.hash})\n`; + } + entry += '\n'; + } + + if (analysis.other.length > 0) { + entry += `### Other Changes\n`; + for (const commit of analysis.other) { + entry += `- ${commit}\n`; + } + entry += '\n'; + } + + return entry; +} + +async function analyzeCommits() { + try { + // Get the last tag + const lastTag = getLastTag(); + const currentVersion = lastTag ? lastTag.replace(/^v/, '') : '0.0.0'; + + // Get commits since last tag + const commitLog = getCommitsSinceTag(lastTag); + + if (!commitLog) { + return { + hasPendingChanges: false, + currentVersion, + nextVersion: null, + analysis: null, + changelog: null, + }; + } + + // Parse commits + const analysis = parseCommits(commitLog); + + // Calculate next version + const nextVersion = calculateNextVersion(currentVersion, analysis); + + if (!nextVersion) { + return { + hasPendingChanges: false, + currentVersion, + nextVersion: null, + analysis, + changelog: null, + }; + } + + const nextVersionString = formatVersion(nextVersion); + const changelog = generateChangelogEntry(nextVersionString, analysis); + + return { + hasPendingChanges: true, + currentVersion, + nextVersion: nextVersionString, + analysis, + changelog, + tag: `v${nextVersionString}`, + }; + } catch (error) { + console.error(`Error analyzing commits: ${error.message}`); + process.exit(1); + } +} + +async function main() { + const args = process.argv.slice(2); + const useJson = args.includes('--json'); + + const result = await analyzeCommits(); + + if (useJson) { + console.log(JSON.stringify(result, null, 2)); + } else { + console.log(`Current Version: ${result.currentVersion}`); + if (result.nextVersion) { + console.log(`Next Version: ${result.nextVersion}`); + console.log(`Tag: ${result.tag}`); + console.log(`\nPending Changes:`); + console.log(` - Features: ${result.analysis.features.length}`); + console.log(` - Fixes: ${result.analysis.fixes.length}`); + console.log(` - Breaking: ${result.analysis.breaking.length}`); + console.log(` - Other: ${result.analysis.other.length}`); + } else { + console.log('No pending changes that require a release'); + } + } +} + +if (require.main === module) { + main().catch((error) => { + console.error('Fatal error:', error.message); + process.exit(1); + }); +} + +module.exports = { analyzeCommits, parseCommits, calculateNextVersion, generateChangelogEntry }; diff --git a/.github/scripts/get-version.js b/.github/scripts/get-version.js new file mode 100644 index 0000000..7866b5c --- /dev/null +++ b/.github/scripts/get-version.js @@ -0,0 +1,155 @@ +#!/usr/bin/env node + +/** + * Get the current version from GitHub releases + * + * This script fetches the latest release from the FixFX repository + * and extracts the version from the tag name. + * + * Usage: + * node get-version.js # outputs to stdout + * node get-version.js --file # writes to file + * node get-version.js --json # outputs as JSON object + */ + +const https = require('https'); +const fs = require('fs'); +const path = require('path'); + +const REPO_OWNER = 'CodeMeAPixel'; +const REPO_NAME = 'FixFX'; +const DEFAULT_VERSION = '0.0.0-unknown'; + +async function fetchLatestRelease() { + return new Promise((resolve, reject) => { + const url = `https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/releases/latest`; + + const options = { + hostname: 'api.github.com', + path: `/repos/${REPO_OWNER}/${REPO_NAME}/releases/latest`, + method: 'GET', + headers: { + 'User-Agent': 'FixFX-Version-Script', + 'Accept': 'application/vnd.github.v3+json', + }, + timeout: 5000, + }; + + // Use GitHub token if available for higher rate limits + if (process.env.GITHUB_TOKEN) { + options.headers['Authorization'] = `token ${process.env.GITHUB_TOKEN}`; + } + + const req = https.request(options, (res) => { + let data = ''; + + res.on('data', (chunk) => { + data += chunk; + }); + + res.on('end', () => { + try { + if (res.statusCode === 404) { + // No releases found + resolve(null); + return; + } + + if (res.statusCode !== 200) { + reject(new Error(`GitHub API returned ${res.statusCode}: ${data}`)); + return; + } + + const release = JSON.parse(data); + resolve(release); + } catch (error) { + reject(new Error(`Failed to parse GitHub API response: ${error.message}`)); + } + }); + }); + + req.on('timeout', () => { + req.destroy(); + reject(new Error('GitHub API request timed out')); + }); + + req.on('error', reject); + req.end(); + }); +} + +function extractVersion(tagName) { + // Remove leading 'v' if present (e.g., 'v1.0.0' → '1.0.0') + let version = tagName.replace(/^v/, ''); + + // Validate semver format (basic check) + if (!/^\d+\.\d+\.\d+/.test(version)) { + throw new Error(`Invalid version format: ${tagName}`); + } + + return version; +} + +async function getVersion(options = {}) { + try { + const release = await fetchLatestRelease(); + + if (!release) { + console.warn(`No releases found for ${REPO_OWNER}/${REPO_NAME}, using default version`); + return DEFAULT_VERSION; + } + + const version = extractVersion(release.tag_name); + return version; + } catch (error) { + if (options.strict) { + console.error(`Error fetching version: ${error.message}`); + process.exit(1); + } else { + console.warn(`Error fetching version: ${error.message}, using default version`); + return DEFAULT_VERSION; + } + } +} + +async function main() { + const args = process.argv.slice(2); + const options = {}; + + // Parse arguments + for (let i = 0; i < args.length; i++) { + if (args[i] === '--file') { + options.file = args[i + 1]; + i++; + } else if (args[i] === '--json') { + options.json = true; + } else if (args[i] === '--strict') { + options.strict = true; + } + } + + const version = await getVersion(options); + + if (options.json) { + console.log(JSON.stringify({ version, repo: `${REPO_OWNER}/${REPO_NAME}` }, null, 2)); + } else if (options.file) { + try { + fs.writeFileSync(options.file, version, 'utf-8'); + console.log(`Version written to ${options.file}: ${version}`); + } catch (error) { + console.error(`Failed to write version to file: ${error.message}`); + process.exit(1); + } + } else { + console.log(version); + } +} + +if (require.main === module) { + main().catch((error) => { + console.error('Fatal error:', error.message); + process.exit(1); + }); +} + +module.exports = { fetchLatestRelease, extractVersion, getVersion }; diff --git a/.github/scripts/update-trusted-hosts.js b/.github/scripts/update-trusted-hosts.js new file mode 100644 index 0000000..6f425e1 --- /dev/null +++ b/.github/scripts/update-trusted-hosts.js @@ -0,0 +1,198 @@ +#!/usr/bin/env node + +/** + * Scraper for FiveM trusted hosting providers + * Automatically fetches and validates hosting providers from FiveM's official registry + * Used by GitHub Actions to keep the trusted-hosts.json file up to date + */ + +const https = require('https'); +const fs = require('fs'); +const path = require('path'); + +const FIVEM_HOSTING_URL = 'https://fivem.net/server-hosting'; +const TRUSTED_HOSTS_FILE = path.join(__dirname, '..', '..', 'packages', 'providers', 'trusted-hosts.json'); +const SCHEMA_FILE = path.join(__dirname, '..', '..', 'packages', 'providers', 'trusted-hosts-schema.json'); + +/** + * Fetch the FiveM hosting page and extract provider information + */ +async function fetchFiveMListing() { + return new Promise((resolve, reject) => { + https.get(FIVEM_HOSTING_URL, { headers: { 'User-Agent': 'FixFX-TrustedHostsScraper/1.0' } }, (res) => { + let data = ''; + + res.on('data', chunk => { + data += chunk; + }); + + res.on('end', () => { + try { + resolve(data); + } catch (error) { + reject(error); + } + }); + }).on('error', reject); + }); +} + +/** + * Parse hosting provider information from HTML + * This extracts provider cards that follow a predictable structure + */ +function parseHostingProviders(html) { + const providers = []; + + // Look for hosting provider cards - they typically have specific patterns + // Pattern 1: Look for links with typical hosting provider domain patterns + const linkRegex = /]*href=["']([^"']*(?:zap-hosting|gtxgaming|nitrado|g-portal|firestorm|nitrado|gameservers|lgsm)[^"']*)["'][^>]*>([^<]+)<\/a>/gi; + let match; + + const seen = new Set(); + + while ((match = linkRegex.exec(html)) !== null) { + const url = match[1]; + const name = match[2].trim(); + + // Skip duplicates and invalid entries + if (!url || !name || seen.has(url.toLowerCase())) continue; + + seen.add(url.toLowerCase()); + + // Normalize URL + const normalizedUrl = new URL(url.includes('://') ? url : `https://${url}`).href; + + providers.push({ + id: name.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, ''), + name: name, + url: normalizedUrl, + description: `Trusted FiveM/RedM hosting provider`, + verified: true, + lastVerified: new Date().toISOString() + }); + } + + // Known hosting providers fallback (if scraping doesn't find them) + const knownProviders = [ + { id: 'zap-hosting', name: 'ZAP-Hosting', url: 'https://zap-hosting.com' }, + { id: 'gtxgaming', name: 'GTXGaming', url: 'https://gtxgaming.co.uk' }, + { id: 'nitrado', name: 'Nitrado', url: 'https://nitrado.net' }, + { id: 'g-portal', name: 'G-Portal', url: 'https://www.g-portal.com' }, + { id: 'firestorm-servers', name: 'Firestorm Servers', url: 'https://firestormservers.com' }, + { id: 'gameservers', name: 'GameServers', url: 'https://www.gameservers.com' } + ]; + + // Add known providers if not already found + for (const known of knownProviders) { + if (!providers.find(p => p.id === known.id)) { + providers.push({ + ...known, + description: `Trusted FiveM/RedM hosting provider`, + verified: false, + lastVerified: new Date().toISOString() + }); + } + } + + return providers; +} + +/** + * Validate provider against schema + */ +function validateAgainstSchema(provider, schema) { + const errors = []; + + // Check required fields + if (!provider.id) errors.push('Missing required field: id'); + if (!provider.name) errors.push('Missing required field: name'); + if (!provider.url) errors.push('Missing required field: url'); + + // Validate ID format + if (provider.id && !/^[a-z0-9-]+$/.test(provider.id)) { + errors.push(`Invalid id format: ${provider.id}`); + } + + // Validate URL format + if (provider.url) { + try { + new URL(provider.url); + } catch { + errors.push(`Invalid URL format: ${provider.url}`); + } + } + + // Validate string lengths + if (provider.name && provider.name.length > 255) { + errors.push(`Name exceeds maximum length: ${provider.name.length} > 255`); + } + + if (provider.description && provider.description.length > 500) { + errors.push(`Description exceeds maximum length`); + } + + return errors; +} + +/** + * Main execution + */ +async function main() { + try { + console.log('🌐 Fetching FiveM trusted hosting providers...'); + const html = await fetchFiveMListing(); + + console.log('📊 Parsing provider information...'); + const hosts = parseHostingProviders(html); + + if (hosts.length === 0) { + console.warn('⚠️ No providers found. Using defaults.'); + } else { + console.log(`✅ Found ${hosts.length} hosting providers`); + } + + // Load schema for validation + const schema = JSON.parse(fs.readFileSync(SCHEMA_FILE, 'utf8')); + + // Validate each provider + const validationErrors = []; + for (const host of hosts) { + const errors = validateAgainstSchema(host, schema); + if (errors.length > 0) { + console.warn(`⚠️ Validation issues for ${host.name}:`, errors); + validationErrors.push({ provider: host.name, errors }); + } + } + + // Create output object + const output = { + lastUpdated: new Date().toISOString(), + source: 'https://fivem.net/server-hosting', + hosts: hosts.sort((a, b) => a.name.localeCompare(b.name)) + }; + + // Write to file + fs.writeFileSync(TRUSTED_HOSTS_FILE, JSON.stringify(output, null, 2)); + console.log(`📝 Updated ${TRUSTED_HOSTS_FILE}`); + + // Print summary + console.log('\n📋 Summary:'); + console.log(` Total providers: ${hosts.length}`); + console.log(` Verified: ${hosts.filter(h => h.verified).length}`); + console.log(` Unverified: ${hosts.filter(h => !h.verified).length}`); + + if (validationErrors.length > 0) { + console.warn(`\n⚠️ Found ${validationErrors.length} validation warnings`); + process.exit(0); // Don't fail on warnings + } + + console.log('\n✅ Successfully updated trusted hosts list'); + process.exit(0); + } catch (error) { + console.error('❌ Error:', error.message); + process.exit(1); + } +} + +main(); diff --git a/.github/scripts/validate-trusted-hosts.js b/.github/scripts/validate-trusted-hosts.js new file mode 100644 index 0000000..ad60549 --- /dev/null +++ b/.github/scripts/validate-trusted-hosts.js @@ -0,0 +1,91 @@ +#!/usr/bin/env node + +/** + * Validator for trusted-hosts.json + * Ensures the file conforms to the schema and contains valid data + */ + +const fs = require('fs'); +const path = require('path'); +const Ajv = require('ajv'); +const addFormats = require('ajv-formats'); + +const TRUSTED_HOSTS_FILE = path.join(__dirname, '..', '..', 'packages', 'providers', 'trusted-hosts.json'); +const SCHEMA_FILE = path.join(__dirname, '..', '..', 'packages', 'providers', 'trusted-hosts-schema.json'); + +try { + // Load files + const trustedHosts = JSON.parse(fs.readFileSync(TRUSTED_HOSTS_FILE, 'utf8')); + let schema = JSON.parse(fs.readFileSync(SCHEMA_FILE, 'utf8')); + + // Remove $schema to avoid AJV trying to fetch it from the web + delete schema.$schema; + + // Initialize AJV validator with format support + const ajv = new Ajv(); + addFormats(ajv); + const validate = ajv.compile(schema); + + // Validate against schema + const isValid = validate(trustedHosts); + + if (!isValid) { + console.error('❌ Schema validation failed:\n'); + validate.errors.forEach(error => { + console.error(` ${error.instancePath || 'root'}: ${error.message}`); + }); + process.exit(1); + } + + // Additional validations + const errors = []; + const warnings = []; + + // Check for duplicate IDs + const ids = new Set(); + for (const host of trustedHosts.hosts) { + if (ids.has(host.id)) { + errors.push(`Duplicate provider ID: ${host.id}`); + } + ids.add(host.id); + } + + // Check for duplicate URLs + const urls = new Set(); + for (const host of trustedHosts.hosts) { + const normalizedUrl = new URL(host.url).href; + if (urls.has(normalizedUrl)) { + warnings.push(`Duplicate URL detected: ${host.url}`); + } + urls.add(normalizedUrl); + } + + // Check lastUpdated is recent (within 30 days) + const lastUpdated = new Date(trustedHosts.lastUpdated); + const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000); + if (lastUpdated < thirtyDaysAgo) { + warnings.push('List was last updated more than 30 days ago. Consider running the scraper.'); + } + + // Report results + if (errors.length > 0) { + console.error('❌ Validation errors:\n'); + errors.forEach(error => console.error(` • ${error}`)); + process.exit(1); + } + + if (warnings.length > 0) { + console.warn('⚠️ Validation warnings:\n'); + warnings.forEach(warning => console.warn(` • ${warning}`)); + } + + console.log('✅ trusted-hosts.json validation passed'); + console.log(` Total providers: ${trustedHosts.hosts.length}`); + console.log(` Last updated: ${new Date(trustedHosts.lastUpdated).toLocaleString()}`); + console.log(` Source: ${trustedHosts.source}`); + + process.exit(0); +} catch (error) { + console.error('❌ Error:', error.message); + process.exit(1); +} diff --git a/.github/scripts/validate-tsconfig.js b/.github/scripts/validate-tsconfig.js new file mode 100644 index 0000000..ef113f3 --- /dev/null +++ b/.github/scripts/validate-tsconfig.js @@ -0,0 +1,102 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); + +// Load both versions +const currentPath = 'tsconfig.json'; +const basePath = 'base-branch/tsconfig.json'; + +let currentConfig, baseConfig; + +try { + currentConfig = JSON.parse(fs.readFileSync(currentPath, 'utf8')); + baseConfig = JSON.parse(fs.readFileSync(basePath, 'utf8')); +} catch (error) { + console.error('Error parsing JSON files:', error.message); + process.exit(1); +} + +let hasErrors = false; +const errors = []; + +// Check for removed keys in compilerOptions +if (currentConfig.compilerOptions && baseConfig.compilerOptions) { + for (const key in baseConfig.compilerOptions) { + if (!(key in currentConfig.compilerOptions)) { + errors.push(`❌ Removed compiler option: "${key}"`); + hasErrors = true; + } + } +} + +// Check for modified values in compilerOptions +if (currentConfig.compilerOptions && baseConfig.compilerOptions) { + for (const key in baseConfig.compilerOptions) { + const baseValue = JSON.stringify(baseConfig.compilerOptions[key]); + const currentValue = JSON.stringify(currentConfig.compilerOptions[key]); + + if (baseValue !== currentValue && key in currentConfig.compilerOptions) { + errors.push(`❌ Modified compiler option "${key}": "${baseValue}" → "${currentValue}"`); + hasErrors = true; + } + } +} + +// Check for removed keys in paths +if (currentConfig.compilerOptions?.paths && baseConfig.compilerOptions?.paths) { + for (const key in baseConfig.compilerOptions.paths) { + if (!(key in currentConfig.compilerOptions.paths)) { + errors.push(`❌ Removed path alias: "${key}"`); + hasErrors = true; + } + } +} + +// Check for modified paths values +if (currentConfig.compilerOptions?.paths && baseConfig.compilerOptions?.paths) { + for (const key in baseConfig.compilerOptions.paths) { + const baseValue = JSON.stringify(baseConfig.compilerOptions.paths[key]); + const currentValue = JSON.stringify(currentConfig.compilerOptions.paths[key]); + + if (baseValue !== currentValue && key in currentConfig.compilerOptions.paths) { + errors.push(`❌ Modified path alias "${key}": ${baseValue} → ${currentValue}`); + hasErrors = true; + } + } +} + +// Check for removed keys in include +if (currentConfig.include && baseConfig.include) { + const baseIncludes = new Set(baseConfig.include); + for (const item of baseIncludes) { + if (!currentConfig.include.includes(item)) { + errors.push(`❌ Removed include pattern: "${item}"`); + hasErrors = true; + } + } +} + +// Check for removed keys in exclude +if (currentConfig.exclude && baseConfig.exclude) { + const baseExcludes = new Set(baseConfig.exclude); + for (const item of baseExcludes) { + if (!currentConfig.exclude.includes(item)) { + errors.push(`❌ Removed exclude pattern: "${item}"`); + hasErrors = true; + } + } +} + +if (hasErrors) { + console.log('\n❌ tsconfig.json validation FAILED\n'); + console.log('Critical Configuration Protection:\n'); + errors.forEach(error => console.log(error)); + console.log('\n⚠️ Only ADDITIONS to tsconfig.json are allowed.'); + console.log('Removing or modifying existing configurations will break the site.\n'); + process.exit(1); +} else { + console.log('✅ tsconfig.json validation PASSED'); + console.log('Only additions detected (or no changes to existing configuration).\n'); + process.exit(0); +} diff --git a/.github/workflows/auto-release.yml b/.github/workflows/auto-release.yml new file mode 100644 index 0000000..bded2e4 --- /dev/null +++ b/.github/workflows/auto-release.yml @@ -0,0 +1,182 @@ +name: Auto Release & Changelog + +on: + push: + branches: + - develop + - master + paths: + - 'frontend/**' + - '.github/workflows/auto-release.yml' + # Explicitly exclude pull requests + # This workflow only runs on direct pushes/merges to branches + +jobs: + analyze: + name: Analyze Commits & Check for Release + runs-on: ubuntu-latest + outputs: + has-changes: ${{ steps.analyze.outputs.has-changes }} + next-version: ${{ steps.analyze.outputs.next-version }} + tag: ${{ steps.analyze.outputs.tag }} + changelog: ${{ steps.analyze.outputs.changelog }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Fetch all history to analyze commits + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Analyze commits since last release + id: analyze + working-directory: frontend + run: | + # Run the analysis script and capture JSON output + ANALYSIS=$(node .github/scripts/analyze-commits.js --json) + + echo "Analysis Result:" + echo "$ANALYSIS" | jq '.' + + # Extract values + HAS_CHANGES=$(echo "$ANALYSIS" | jq -r '.hasPendingChanges') + NEXT_VERSION=$(echo "$ANALYSIS" | jq -r '.nextVersion // empty') + TAG=$(echo "$ANALYSIS" | jq -r '.tag // empty') + + # Read changelog (save to temp file for multiline output) + CHANGELOG=$(echo "$ANALYSIS" | jq -r '.changelog // empty') + + echo "has-changes=$HAS_CHANGES" >> $GITHUB_OUTPUT + echo "next-version=$NEXT_VERSION" >> $GITHUB_OUTPUT + echo "tag=$TAG" >> $GITHUB_OUTPUT + + # Store changelog in a file for the next step + if [ -n "$CHANGELOG" ]; then + echo "$CHANGELOG" > /tmp/changelog-entry.md + echo "changelog-file=/tmp/changelog-entry.md" >> $GITHUB_OUTPUT + fi + + update-changelog: + name: Update CHANGELOG.md + needs: analyze + runs-on: ubuntu-latest + if: needs.analyze.outputs.has-changes == 'true' + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Get changelog entry + id: get-changelog + working-directory: frontend + run: | + ANALYSIS=$(node .github/scripts/analyze-commits.js --json) + CHANGELOG=$(echo "$ANALYSIS" | jq -r '.changelog') + + # Use a delimiter for multiline output + echo "entry<> $GITHUB_OUTPUT + echo "$CHANGELOG" >> $GITHUB_OUTPUT + echo "CHANGELOG_DELIMITER" >> $GITHUB_OUTPUT + + - name: Update CHANGELOG.md + working-directory: frontend + run: | + # Read the current CHANGELOG + CURRENT=$(cat CHANGELOG.md) + + # Get the changelog entry + ENTRY="${{ steps.get-changelog.outputs.entry }}" + + # Find the line with "## [1.1.0] - Unreleased" and replace with new version + # This handles the unreleased section by replacing it with the new version + + # Create new changelog with the entry inserted after the header + { + head -n 8 CHANGELOG.md # Keep header and intro + echo "" + echo "$ENTRY" + tail -n +9 CHANGELOG.md | sed 's/## \[1\.1\.0\] - Unreleased/## [Unreleased] - TBD/' || tail -n +9 CHANGELOG.md + } > CHANGELOG.md.tmp + + mv CHANGELOG.md.tmp CHANGELOG.md + + - name: Commit and push changelog + run: | + cd frontend + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + git add CHANGELOG.md + git commit -m "chore: update changelog for version ${{ needs.analyze.outputs.next-version }}" + git push + + create-release: + name: Create GitHub Release + needs: [analyze, update-changelog] + runs-on: ubuntu-latest + if: needs.analyze.outputs.has-changes == 'true' + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Get release notes from changelog + id: release-notes + working-directory: frontend + run: | + ANALYSIS=$(node .github/scripts/analyze-commits.js --json) + CHANGELOG=$(echo "$ANALYSIS" | jq -r '.changelog') + + echo "notes<> $GITHUB_OUTPUT + echo "$CHANGELOG" >> $GITHUB_OUTPUT + echo "RELEASE_NOTES_DELIMITER" >> $GITHUB_OUTPUT + + - name: Create Release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ needs.analyze.outputs.tag }} + release_name: Release ${{ needs.analyze.outputs.next-version }} + body: ${{ steps.release-notes.outputs.notes }} + draft: false + prerelease: false + + - name: Log Release Created + run: | + echo "✅ Release created!" + echo "Tag: ${{ needs.analyze.outputs.tag }}" + echo "Version: ${{ needs.analyze.outputs.next-version }}" + + notify-no-changes: + name: Notify if No Changes + needs: analyze + runs-on: ubuntu-latest + if: needs.analyze.outputs.has-changes == 'false' + + steps: + - name: Log status + run: | + echo "ℹ️ No changes requiring a release detected" + echo "Please commit changes with conventional commit format:" + echo " feat: Add new feature" + echo " fix: Fix a bug" + echo " breaking: Breaking change" diff --git a/.github/workflows/format-ci.yml b/.github/workflows/format-ci.yml new file mode 100644 index 0000000..3b36ac6 --- /dev/null +++ b/.github/workflows/format-ci.yml @@ -0,0 +1,24 @@ +name: Format +on: + push: + branches: [master, develop] + pull_request: + branches: [master, develop] + +jobs: + prettier: + name: Prettier + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install Bun + uses: oven-sh/setup-bun@v1 + with: + bun-version: latest + + - name: Install dependencies + run: bun install + + - name: Check formatting + run: bun run format:check diff --git a/.github/workflows/knip-ci.yml b/.github/workflows/knip-ci.yml new file mode 100644 index 0000000..f400229 --- /dev/null +++ b/.github/workflows/knip-ci.yml @@ -0,0 +1,46 @@ +name: Knip Validation +on: + push: + branches: [master, develop] + pull_request: + +jobs: + knip: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.head_ref }} + + - name: Install Bun + uses: oven-sh/setup-bun@v1 + with: + bun-version: latest + + - name: Install dependencies + run: bun install + + - name: Build application + run: bun run build + + # HIGH CONFIDENCE - Hard fail on unused dependencies + - name: Validate Dependency Usage + run: bunx knip:deps + + # MEDIUM CONFIDENCE - Report only (warning) + - name: Validate File Usage + run: bunx knip:files + continue-on-error: true + + # LOW CONFIDENCE - Report only (warning) + - name: Validate Export Usage + run: bunx knip:exports + continue-on-error: true + + # LOW CONFIDENCE - Report only (warning) + - name: Validate Production Dependencies + run: bunx knip:prod + continue-on-error: true + + - name: Notify success + run: echo "Knip validation completed!" \ No newline at end of file diff --git a/.github/workflows/knip-report.yml b/.github/workflows/knip-report.yml new file mode 100644 index 0000000..ea68b95 --- /dev/null +++ b/.github/workflows/knip-report.yml @@ -0,0 +1,53 @@ +name: Knip Report + +on: + pull_request: + +jobs: + knip-report: + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.head_ref }} + + - name: Install Bun + uses: oven-sh/setup-bun@v1 + with: + bun-version: latest + + - name: Install dependencies + run: bun install + + - name: Build application + run: bun run build + + - name: Generate Knip Report + id: knip-report + run: | + { + echo "## 📋 Knip Analysis Report" + echo "" + echo "### Exports" + bunx knip:exports 2>&1 || true + echo "" + echo "### Files" + bunx knip:files 2>&1 || true + echo "" + echo "### Production" + bunx knip:prod 2>&1 || true + } >> $GITHUB_OUTPUT + + - name: Comment on PR + if: github.event_name == 'pull_request' + uses: actions/github-script@v7 + with: + script: | + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: "## 📋 Knip Code Quality Analysis\n\nThis report identifies potentially unused exports, dependencies, and files. **Note:** These are warnings only and may contain false positives, especially for:\n- MDX/documentation components\n- Dynamic imports\n- Framework magic (Next.js, Fumadocs)\n\n### Summary\n- Only **hard fails** occur for unused dependencies (knip:deps)\n- All other checks are informational warnings\n\nTo run locally:\n- `bun run knip:deps` - Check unused dependencies (hard fail)\n- `bun run knip:exports` - Check unused exports (warning)\n- `bun run knip:files` - Check unused files (warning)\n- `bun run knip:prod` - Check production deps (warning)\n\nSee [Knip Documentation](https://knip.dev) for more details." + }) diff --git a/.github/workflows/lint-ci.yml b/.github/workflows/lint-ci.yml new file mode 100644 index 0000000..80abdbc --- /dev/null +++ b/.github/workflows/lint-ci.yml @@ -0,0 +1,24 @@ +name: Lint +on: + push: + branches: [master, develop] + pull_request: + branches: [master, develop] + +jobs: + eslint: + name: ESLint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install Bun + uses: oven-sh/setup-bun@v1 + with: + bun-version: latest + + - name: Install dependencies + run: bun install + + - name: Run ESLint + run: bun run lint diff --git a/.github/workflows/semver-ci.yml b/.github/workflows/semver-ci.yml deleted file mode 100644 index 52898c1..0000000 --- a/.github/workflows/semver-ci.yml +++ /dev/null @@ -1,115 +0,0 @@ -name: Release CI - -on: - workflow_dispatch: - inputs: - semver: - description: 'Select version bump type: major, minor, patch' - required: false - default: 'patch' - type: choice - options: - - major - - minor - - patch - prerelease: - description: 'Pre-release identifier (e.g., alpha, beta, rc.1). Leave empty for stable releases.' - required: false - default: '' - custom_version: - description: 'Specify a custom version. This will override the semver and prerelease inputs.' - required: false - default: '' - release_notes: - description: 'Release notes for this version' - required: false - default: '' - create_release: - description: 'Create a GitHub release' - type: boolean - required: false - default: true - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - name: Setup Node.js - uses: actions/setup-node@v3 - with: - node-version: 18 - cache: 'npm' - - - name: Install dependencies - run: npm ci - - - name: Build application - run: npm run build - - - name: Set Git identity - run: | - git config --local user.email "toxic.dev09@gmail.com" - git config --local user.name "Toxic Dev" - - - name: Get current version - id: current_version - run: echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT - - - name: Version bump - id: version_bump - run: | - if [ -n "${{ github.event.inputs.custom_version }}" ]; then - NEW_VERSION="${{ github.event.inputs.custom_version }}" - npm version $NEW_VERSION -m "Bump version to %s [skip ci]" - echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT - elif [ -n "${{ github.event.inputs.semver }}" ]; then - if [ -z "${{ github.event.inputs.prerelease }}" ]; then - NEW_VERSION=$(npm version ${{ github.event.inputs.semver }} -m "Bump version to %s [skip ci]") - echo "new_version=${NEW_VERSION:1}" >> $GITHUB_OUTPUT - else - NEW_VERSION=$(npm version ${{ github.event.inputs.semver }} --preid=${{ github.event.inputs.prerelease }} -m "Bump version to %s [skip ci]") - echo "new_version=${NEW_VERSION:1}" >> $GITHUB_OUTPUT - fi - else - NEW_VERSION=$(npm version patch -m "Bump version to %s [skip ci]") - echo "new_version=${NEW_VERSION:1}" >> $GITHUB_OUTPUT - fi - - - name: Generate changelog - id: changelog - if: github.event.inputs.create_release == 'true' - run: | - if [ -n "${{ github.event.inputs.release_notes }}" ]; then - echo "${{ github.event.inputs.release_notes }}" > CHANGELOG.md - else - echo "## What's Changed" > CHANGELOG.md - git log --pretty=format:"* %s" ${{ steps.current_version.outputs.version }}..HEAD >> CHANGELOG.md - fi - echo "changelog<> $GITHUB_OUTPUT - cat CHANGELOG.md >> $GITHUB_OUTPUT - echo "EOF" >> $GITHUB_OUTPUT - - - name: Push changes - uses: ad-m/github-push-action@master - with: - github_token: ${{ secrets.GIT_TOKEN }} - branch: ${{ github.ref }} - tags: true - - - name: Create GitHub Release - if: github.event.inputs.create_release == 'true' - uses: softprops/action-gh-release@v1 - with: - tag_name: v${{ steps.version_bump.outputs.new_version }} - name: Release v${{ steps.version_bump.outputs.new_version }} - body: ${{ steps.changelog.outputs.changelog }} - draft: false - prerelease: ${{ github.event.inputs.prerelease != '' }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/tsconfig-validation.yml b/.github/workflows/tsconfig-validation.yml new file mode 100644 index 0000000..13ec330 --- /dev/null +++ b/.github/workflows/tsconfig-validation.yml @@ -0,0 +1,44 @@ +name: Validate tsconfig.json Changes + +on: + pull_request: + paths: + - 'tsconfig.json' + +jobs: + validate-tsconfig: + runs-on: ubuntu-latest + permissions: + pull-requests: write + + steps: + - name: Checkout PR branch + uses: actions/checkout@v4 + with: + ref: ${{ github.head_ref }} + + - name: Checkout base branch + uses: actions/checkout@v4 + with: + ref: ${{ github.base_ref }} + path: base-branch + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Validate tsconfig.json changes + run: node .github/scripts/validate-tsconfig.js + + - name: Comment on PR if validation fails + if: failure() + uses: actions/github-script@v7 + with: + script: | + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: "## ⚠️ tsconfig.json Validation Failed\n\nThis pull request attempts to **remove or modify** critical tsconfig.json configurations. This is not allowed as it will break the site.\n\n### ✅ What's Allowed:\n- **Adding** new compiler options\n- **Adding** new path aliases\n- **Adding** new include/exclude patterns\n\n### ❌ What's Not Allowed:\n- Removing existing compiler options\n- Modifying existing compiler option values\n- Removing path aliases\n- Modifying path alias mappings\n- Removing include/exclude patterns\n\nPlease revert your changes to the existing configuration and only add new options if needed." + }) diff --git a/.github/workflows/update-trusted-hosts.yml b/.github/workflows/update-trusted-hosts.yml new file mode 100644 index 0000000..de8baa4 --- /dev/null +++ b/.github/workflows/update-trusted-hosts.yml @@ -0,0 +1,94 @@ +name: Update Trusted Hosting Providers + +on: + schedule: + # Run every Monday at 00:00 UTC to check for updates + - cron: '0 0 * * 1' + + # Allow manual triggering + workflow_dispatch: + +jobs: + update-trusted-hosts: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + ref: develop + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install dependencies + run: npm install ajv + + - name: Update trusted hosts list + run: node .github/scripts/update-trusted-hosts.js + + - name: Validate trusted hosts list + run: node .github/scripts/validate-trusted-hosts.js + + - name: Check for changes + id: changes + run: | + if git diff --quiet packages/providers/trusted-hosts.json; then + echo "changed=false" >> $GITHUB_OUTPUT + else + echo "changed=true" >> $GITHUB_OUTPUT + fi + + - name: Create Pull Request + if: steps.changes.outputs.changed == 'true' + uses: peter-evans/create-pull-request@v5 + with: + token: ${{ secrets.GITHUB_TOKEN }} + commit-message: | + chore: update trusted hosting providers list + + Automatically updated from https://fivem.net/server-hosting + branch: chore/update-trusted-hosts + delete-branch: true + title: 'chore: update trusted hosting providers list' + body: | + ## 🤖 Automated Update + + This PR automatically updates the trusted hosting providers list from the official FiveM registry. + + **Changes:** + - Updated `packages/providers/trusted-hosts.json` + - Validated against schema + - Last updated: ${{ github.event.head_commit.timestamp || 'Manual trigger' }} + + The list is scraped from: https://fivem.net/server-hosting + + ### Verification Checklist + - [x] Schema validation passed + - [x] No duplicate provider IDs + - [x] All provider URLs are valid + + ✅ Ready to merge + labels: | + automated + dependencies + reviewers: | + ${{ github.repository_owner }} + + - name: Log summary + if: always() + run: | + echo "## Trusted Hosts Update Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + if [ "${{ steps.changes.outputs.changed }}" == "true" ]; then + echo "✅ Updates found and PR created" >> $GITHUB_STEP_SUMMARY + else + echo "✅ List is up to date" >> $GITHUB_STEP_SUMMARY + fi + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Source:** https://fivem.net/server-hosting" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/validate-providers.yml b/.github/workflows/validate-providers.yml new file mode 100644 index 0000000..e8c167d --- /dev/null +++ b/.github/workflows/validate-providers.yml @@ -0,0 +1,166 @@ +name: Validate Provider Files + +on: + pull_request: + paths: + - 'frontend/packages/providers/**/*.json' + +jobs: + validate: + name: Validate Provider JSON + runs-on: ubuntu-latest + defaults: + run: + working-directory: frontend + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install ajv-cli + run: npm install -g ajv-cli ajv-formats + + - name: Validate provider files + run: | + SCHEMA_FILE="packages/providers/schema.json" + ERRORS=0 + + # Find all provider.json files in subdirectories + for file in packages/providers/*/provider.json; do + # Skip if file doesn't exist (no providers) + if [ ! -f "$file" ]; then + continue + fi + + echo "Validating: $file" + + # Validate against schema + if ! ajv validate -s "$SCHEMA_FILE" -d "$file" --spec=draft7 -c ajv-formats; then + echo "❌ Validation failed for $file" + ERRORS=$((ERRORS + 1)) + else + echo "✅ Valid: $file" + fi + + # Additional checks + ID=$(jq -r '.id' "$file") + FILENAME=$(basename "$file" .json) + + if [ "$ID" != "$FILENAME" ]; then + echo "❌ Error: id '$ID' does not match filename '$FILENAME'" + ERRORS=$((ERRORS + 1)) + fi + done + + if [ $ERRORS -gt 0 ]; then + echo "" + echo "❌ $ERRORS error(s) found in provider files" + exit 1 + fi + + echo "" + echo "✅ All provider files are valid!" + + - name: Check for duplicate IDs + run: | + ERRORS=0 + IDS_FILE=$(mktemp) + + # Extract all IDs from provider.json files + for file in packages/providers/*/provider.json; do + if [ -f "$file" ]; then + ID=$(jq -r '.id' "$file" 2>/dev/null) + if [ -n "$ID" ] && [ "$ID" != "null" ]; then + echo "$ID" >> "$IDS_FILE" + fi + fi + done + + # Check for duplicates + if [ -f "$IDS_FILE" ]; then + DUPLICATES=$(sort "$IDS_FILE" | uniq -d) + + if [ -n "$DUPLICATES" ]; then + echo "❌ Duplicate provider IDs found:" + echo "$DUPLICATES" + ERRORS=1 + else + echo "✅ No duplicate provider IDs found" + fi + + rm "$IDS_FILE" + fi + + if [ $ERRORS -gt 0 ]; then + exit 1 + fi + + - name: Validate URLs + run: | + ERRORS=0 + + # Check all provider.json files + for file in packages/providers/*/provider.json; do + if [ ! -f "$file" ]; then + continue + fi + + # Check all URLs in the file + URLS=$(jq -r '.. | strings | select(test("^https?://"))' "$file" 2>/dev/null) + + while IFS= read -r url; do + if [ -z "$url" ]; then + continue + fi + + # Basic URL format check + if ! echo "$url" | grep -qE '^https?://[a-zA-Z0-9]'; then + echo "❌ Invalid URL format in $file: $url" + ERRORS=$((ERRORS + 1)) + fi + done <<< "$URLS" + done + + if [ $ERRORS -gt 0 ]; then + exit 1 + fi + + echo "✅ All URLs are properly formatted" + + - name: Verify directory structure + run: | + ERRORS=0 + + # Check that each provider directory has a provider.json + for dir in packages/providers/*/; do + dirname=$(basename "$dir") + + # Skip schema files and system directories + if [[ "$dirname" == "."* ]] || [ "$dirname" = "schema.json" ] || [ "$dirname" = "trusted-hosts.json" ] || [ "$dirname" = "trusted-hosts-schema.json" ]; then + continue + fi + + if [ ! -f "$dir/provider.json" ]; then + echo "⚠️ Warning: Provider directory '$dirname' missing provider.json" + ERRORS=$((ERRORS + 1)) + else + # Verify directory name matches provider ID + ID=$(jq -r '.id' "$dir/provider.json" 2>/dev/null) + if [ "$ID" != "$dirname" ]; then + echo "⚠️ Warning: Directory name '$dirname' doesn't match provider ID '$ID'" + echo " Consider renaming directory to match the provider ID" + fi + fi + done + + if [ $ERRORS -gt 0 ]; then + echo "" + echo "⚠️ $ERRORS warning(s) found (non-fatal)" + fi + + echo "✅ Directory structure verified" diff --git a/.husky/commit-msg b/.husky/commit-msg new file mode 100644 index 0000000..62aa592 --- /dev/null +++ b/.husky/commit-msg @@ -0,0 +1,4 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +npx --no-install commitlint --edit "$1" \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 19a47ea..b155abf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,227 @@ All notable changes to FixFX will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.1.0] - 2026-01-26 + +### Added + +#### Hosting Providers & Partnerships System +- **Directory-Based Provider Structure** - Reorganized provider files into subdirectories + - Moved from flat `provider-name.json` to `provider-name/provider.json` structure + - Allows schemas and documentation to coexist with provider data + - Improved file organization and maintainability +- **Provider Guidelines & Code of Conduct** - Comprehensive standards documentation (`packages/providers/GUIDELINES.md`) + - Service quality requirements (99.5%+ uptime SLA, ≤4h support response) + - Customer support standards (24/7 availability, documentation, responsiveness) + - Fair pricing expectations and discount legitimacy validation + - Technical standards for FiveM/RedM compatibility + - Ethical business practices and code of conduct + - Partnership application and approval workflow + - Performance monitoring and termination clauses +- **Provider JSON Schema Validation** - Enhanced schema enforcement + - Added `$schema` reference to all provider files pointing to `../schema.json` + - Enables IDE schema validation for provider.json files + - GitHub Actions validates schema compliance on pull requests + - Ensures consistent data quality and structure +- **Trusted Hosts Documentation** - Complete reference for automated provider system + - Usage examples for TypeScript utility functions + - Manual provider addition process + - Troubleshooting guide for scraper and validation + - Fallback mechanisms and validation strategies + +#### StepList Component Enhancements +- **Image Support** - Steps can now include images with positioning + - Added `image`, `imageAlt`, and `imagePosition` props + - Images are zoomable using fumadocs ImageZoom component + - Supports `top`, `bottom`, `left`, `right` positioning +- **Markdown Link Support** - Descriptions now render clickable links + - Added `parseMarkdownLinks` helper function + - Supports standard markdown `[text](url)` syntax +- **Inline Alert Support** - Steps can include contextual alerts + - Added `alert` prop with `type` and `message` fields + - Supports `info`, `warning`, `success`, `error`, and `tip` types + - Styled consistently with InfoBanner component + +#### SEO and Modern Web Standards +- **LLMs.txt** - AI crawler documentation files + - `/llms.txt` - Summary for AI models + - `/llms-full.txt` - Comprehensive documentation for LLMs +- **AI.txt** - AI crawler guidelines and permissions +- **Humans.txt** - Site credits and team information +- **Security.txt** - Security policy in `.well-known/security.txt` +- **GPC.json** - Global Privacy Control signal in `.well-known/gpc.json` +- **OpenSearch** - Browser search integration via `/opensearch.xml` +- **Blog Feeds** - RSS, Atom, and JSON Feed support + - `/blog/feed.xml` - RSS 2.0 feed + - `/blog/atom.xml` - Atom 1.0 feed + - `/blog/feed.json` - JSON Feed 1.1 +- **JSON-LD Schemas** - Structured data for search engines + - WebSite schema with SearchAction + - Organization schema + - SoftwareApplication schema +- **AI Crawler Rules** - Added rules for GPTBot, Claude-Web, ChatGPT-User, Anthropic-AI, PerplexityBot, Cohere-AI in robots.txt + +#### Dynamic Icons +- **App Icon** - Dynamic 512x512 icon with gradient background (`app/icon.tsx`) +- **Apple Touch Icon** - Dynamic 180x180 icon for iOS (`app/apple-icon.tsx`) +- **Brand Page** - `/brand` page displaying icon with download buttons + +#### Documentation +- **Discord Bot Guide** - Comprehensive txAdmin Discord bot setup documentation + - Bot creation and permissions + - Configuration options + - Command reference + - Troubleshooting section +- **Comprehensive txAdmin Documentation Suite** - 10 new in-depth guides covering all txAdmin features + - **API Events** (`api-events.mdx`) - Complete CFX events documentation with 17 event types, properties, Lua examples, and best practices + - **Environment Configuration** (`env-config.mdx`) - TXHOST_* environment variables for GSP and advanced deployments + - **Discord Status Embed** (`discord-status.mdx`) - Custom Discord persistent status configuration with placeholders + - **Development Guide** (`development.mdx`) - Setup, workflows, and architecture for txAdmin development + - **In-Game Menu** (`menu.mdx`) - Menu access, ConVars, commands, and troubleshooting guide + - **Recipe Files** (`recipe.mdx`) - Complete deployment recipe documentation with all task actions + - **Logging System** (`logs.mdx`) - Persistent logging with file rotation and configuration + - **Custom Server Logs** (`custom-server-log.mdx`) - Guide for logging custom commands + - **Color Palettes** (`palettes.mdx`) - Theming and palette configuration + - **Translation Support** (`translation.mdx`) - Contributing translations and custom locale setup +- **Guidelines Modal Enhancement** - Improved markdown link parsing in partnership guidelines + - Added regex-based link parsing for `[text](url)` markdown syntax + - Links render as clickable anchors with proper styling + +#### GitHub Community Files +- **SECURITY.md** - Vulnerability reporting process and response timeline +- **CODE_OF_CONDUCT.md** - Community guidelines based on Contributor Covenant + +#### Developer Tooling +- **Husky** - Git hooks for automated checks + - Pre-commit hook running lint-staged + - Commit-msg hook running commitlint +- **Lint-staged** - Run linters on staged files only + - ESLint for JS/TS files + - Prettier for formatting all file types +- **Commitlint** - Enforce conventional commit messages + - Uses `@commitlint/config-conventional` preset +- **Knip** - Dead code detection + - Configured for monorepo structure with packages + - Custom entry points and path aliases + +#### Artifacts Page Enhancements +- **Hosting Panel Version Strings** - Added Pterodactyl/Pelican version support + - Accordion section in featured cards showing full version string (e.g., `24769-315823736cfbc085104ca0d32779311cd2f1a5a8`) + - Quick copy button with Terminal icon on artifact list items + - Compatible with Pterodactyl, Pelican, and similar hosting panel egg configurations + +- **Artifact Stats from API** - Stats cards now show full totals from backend + - Total, Recommended, Latest, Active, EOL counts reflect all filtered results + - Previously only showed counts for current page + +- **EOL/Deprecated Artifact Warnings** - Safety improvements for unsupported versions + - Warning banner on deprecated/EOL artifact cards explaining download restriction + - Link to CFX EOL documentation (https://aka.cfx.re/eol) + - Download buttons disabled with tooltip explanation + - Visual distinction with amber (deprecated) and red (EOL) styling + +- **Accordion Component** - New Radix-based accordion component + - Smooth expand/collapse animations + - Accessible keyboard navigation + - Used for hosting panel version sections + +#### Hosting Page Improvements +- **Hosting Provider Card Redesign** - Enhanced provider listing with improved UX + - Added loading state skeletons using CSS animations for performance + - Non-blocking state transitions for data fetching +- **Navigation Enhancement** - Improved main navigation configuration + - Hosting menu item with Server icon (green), banner, and description + - Brand menu item with Palette icon (pink), banner, and description + - Proper icon imports and styling consistency + +### Fixed +- **EOL filter parameter** - Fixed `includeEol` not being sent when set to "No" + - Now always sends `includeEol` parameter explicitly to backend + - Previously only sent when true, causing backend default (true) to override UI setting + +- **InfoBanner Alignment** - Fixed icon and title not being perfectly inline + - Wrapped icon in flex container with consistent height + - Applied matching `leading-6` to title text + +- **StepList Alert Alignment** - Fixed icon and title alignment in step alerts + - Same fix as InfoBanner using flex containers + +- **ImageZoom Empty Src** - Fixed error when ImageZoom received empty src string + - Added guard to check `step.image.trim() !== ""` before rendering + - Added guard in docs page for markdown images with `props.src` check + +- **Hero Button Consistency** - Fixed Troubleshoot Issues button arrow visibility + - Arrow now always visible instead of appearing on hover + - Matches Get Started button behavior + +- **Guidelines Modal Component Architecture** - Fixed server/client component boundary issues + - Removed context provider wrapper that caused hook usage errors in server components + - Implemented direct client-side state management in hosting page + - Fixed import paths for component libraries + - Removed unnecessary context provider files + +- **GitHub URL References** - Fixed erroneous `frontend/` directory in provider documentation links + - Updated GUIDELINES.md to use correct GitHub repository URLs + - Removed local folder structure references from remote URLs + - All links now point to correct paths in main FixFX repository + +- **txAdmin Documentation Link Format** - Fixed internal page links in all txAdmin docs + - Removed `.mdx` and `.md` extensions from internal page references + - Updated all GitHub URLs to official `citizenfx/txAdmin` repository + - Updated Discord invite links to official server (`discord.gg/eWhDDVCpPn`) + +#### Styling & CSS Enhancements +- **Comprehensive CSS System** - Major expansion of `globals.css` with reusable utilities + - **Custom Scrollbar** - Sleek, minimal scrollbar styling with `.custom-scrollbar` + - **Gradient Text** - Utilities: `text-gradient-blue`, `text-gradient-purple`, `text-gradient-green`, `text-gradient-orange` + - **Glow Effects** - `glow-blue`, `glow-purple`, `glow-green`, `glow-sm` for neon-style effects + - **Glassmorphism** - `glass`, `glass-card`, `glass-dark` for frosted glass effects + - **Gradient Backgrounds** - `bg-mesh` (multi-color mesh), `bg-dots`, `bg-grid` patterns + - **Card Effects** - `card-hover` with lift animation, `card-glow` with mouse-tracking radial gradient + - **Status Badges** - `badge-recommended`, `badge-latest`, `badge-active`, `badge-deprecated`, `badge-eol` + - **Code Block Styles** - `code-block`, `code-block-header`, `inline-code` + - **Loading States** - `skeleton`, `skeleton-text`, `skeleton-title`, `skeleton-avatar`, `skeleton-card` + - **Chat Interface** - `chat-bubble`, `chat-bubble-user`, `chat-bubble-assistant`, `chat-input` + - **Contributor Styles** - `contributor-avatar` with hover ring effect, `contributor-badge` + - **Table Styles** - Complete table wrapper with hover states + - **Component Classes** - `artifact-card`, `native-card`, `feature-card`, `hero-badge`, `hero-title`, `hero-glow` + - **Documentation** - `docs-callout-info`, `docs-callout-warning`, `docs-callout-danger`, `docs-callout-tip` + - **TOC Styles** - `toc-link`, `toc-progress` for table of contents + - **Sidebar** - `sidebar-link` with active state + - **Utilities** - `transition-base`, `transition-slow`, `focus-ring`, `search-highlight` + - **Z-Index Scale** - Structured z-index system from `z-behind` to `z-tooltip` + +#### Animations +- **New Animation Utilities** - Smooth, performant CSS animations + - `animate-fade-in` - Fade in effect + - `animate-slide-up` / `animate-slide-down` - Slide animations + - `animate-scale-in` - Scale entrance + - `animate-float` - Floating effect (6s infinite) + - `animate-shimmer` - Shimmer loading effect + - `animate-gradient` - Animated gradient backgrounds + - `animate-glow-pulse` - Pulsing glow effect + - `animate-border-flow` - Flowing border gradient +- **Button Shine Effect** - `btn-glow` with shine animation on hover +- **Link Underline** - `link-underline` with animated underline on hover + +### Changed +- Updated `txAdmin Windows Install` guide with StepList images and alerts +- Updated to `NextJS v15.5.9` as it is the latest stable `15.x` version not requiring significant changes +- Enhanced sitemap with blog posts and improved priority structure +- Updated README.md with accurate project information +- Updated CONTRIBUTING.md with correct Discord and email contacts +- Added `knip.json` configuration for dead code detection + +### Fixed +- **Next.js 15 Dynamic Params** - Fixed `params` must be awaited error in `/docs/[...slug]/page.tsx` + - Changed `params` type from `{ slug?: string[] }` to `Promise<{ slug?: string[] }>` + - Added `const { slug } = await params;` before accessing properties + - Aligns with Next.js 15+ requirements for dynamic route params + +- **Hydration Mismatch** - Fixed React hydration warning in root layout + - Added `suppressHydrationWarning` to `` element + - Prevents warnings from theme provider dynamically adding `dark` class and `color-scheme` style + ## [1.0.0] - 2026-01-25 ### Added diff --git a/README.md b/README.md index e517fc1..2ce4076 100644 --- a/README.md +++ b/README.md @@ -1,68 +1,188 @@ -# FixFX Wiki +# FixFX [![Build](https://github.com/CodeMeAPixel/FixFX/actions/workflows/build-ci.yml/badge.svg)](https://github.com/CodeMeAPixel/FixFX/actions/workflows/build-ci.yml) +[![License: AGPL 3.0](https://img.shields.io/badge/License-AGPL%203.0-blue.svg)](LICENSE) -A comprehensive documentation platform for FiveM development, providing detailed guides, tutorials, and best practices for the FiveM community. +A documentation platform for FiveM, RedM, and the CitizenFX ecosystem. Guides, tutorials, native references, and tools for server developers. + +> [!INFO] +>FixFX is an independent community project. It is not affiliated with or endorsed by Cfx.re, Rockstar Games, txAdmin, Take-Two Interactive or any other entities referenced throughout the documentation. ## Features -- 📚 Extensive documentation covering various aspects of FiveM development -- 🎨 Modern, responsive UI with dark mode support -- 🔍 Advanced search functionality -- 📝 Interactive code examples with syntax highlighting -- 🌐 Multi-language support -- 📱 Mobile-friendly design -- 🚀 Fast and optimized performance +- Documentation for FiveM, RedM, txAdmin, vMenu, and popular frameworks +- Native function reference with search and filtering +- Artifacts browser for server builds +- AI-powered chat assistant for troubleshooting +- Full-text search across all documentation +- Dark mode interface +- Mobile responsive design ## Tech Stack -- **Framework**: Next.js -- **UI**: React, Tailwind CSS -- **Documentation**: MDX -- **Code Highlighting**: Shiki -- **Icons**: Lucide +- **Framework**: Next.js 15 +- **Language**: TypeScript +- **Styling**: Tailwind CSS +- **Documentation**: MDX with Fumadocs +- **Backend**: Go (separate repository) - **Deployment**: Vercel ## Getting Started -1. Clone the repository: - ```bash - git clone https://github.com/CodeMeAPixel/FixFX.git - ``` - -2. Install dependencies: - ```bash - cd FixFX - pnpm install - ``` +### Prerequisites -3. Start the development server: - ```bash - pnpm dev - ``` +- Node.js 18 or later +- Bun (recommended) or npm/pnpm -4. Open [http://localhost:3000](http://localhost:3000) in your browser. +### Installation -## Documentation Structure +```bash +git clone https://github.com/CodeMeAPixel/FixFX.git +cd FixFX/frontend +bun install +``` -The documentation is organized into several main sections: +### Development -- **Core**: Fundamental concepts and principles -- **CFX**: FiveM-specific tools and features -- **Common Tools**: Essential utilities and best practices -- **Guides**: Step-by-step tutorials and walkthroughs +```bash +bun dev +``` + +Open [http://localhost:3000](http://localhost:3000) in your browser. + +### Production Build + +```bash +bun run build +bun start +``` + +## Project Structure + +``` +frontend/ +├── app/ # Next.js app router +├── content/ # MDX documentation files +├── lib/ # Utility functions +├── packages/ +│ ├── core/ # React hooks +│ ├── ui/ # UI components +│ └── utils/ # Shared utilities +├── public/ # Static assets +├── styles/ # Global styles +└── types/ # TypeScript definitions +``` + +## Documentation + +Documentation content is located in `content/docs/` and organized by topic: + +- `core/` - Core concepts and fundamentals +- `cfx/` - CitizenFX platform documentation +- `txadmin/` - txAdmin server management +- `vmenu/` - vMenu configuration +- `frameworks/` - ESX, QBCore, and other frameworks +- `guides/` - Tutorials and how-to guides + +## Hosting Providers & Partnerships + +This project includes a partnership program for hosting providers offering exclusive benefits to the FiveM and RedM communities. + +### For Server Owners + +Visit the [Hosting Partners page](/hosting) to browse verified hosting providers with exclusive FixFX discounts: + +- **Affiliate Partners**: Providers with exclusive discount codes (e.g., ZAP-Hosting with 20% off) +- **Trusted Hosts**: Automatically curated list from [fivem.net/server-hosting](https://fivem.net/server-hosting) + +### For Hosting Providers + +We're always looking for quality hosting providers interested in partnerships. Here's what we offer: + +✅ **Reach**: Exposure to thousands of FiveM/RedM server owners +✅ **Trust**: Featured on our dedicated hosting page +✅ **Tracking**: Affiliate links for conversion attribution +✅ **Support**: Community visibility and marketing assistance + +#### Partnership Requirements & Code of Conduct + +Your hosting service should meet these criteria: + +- Must host **FiveM** and/or **RedM** servers +- **99.6%+ uptime** with responsive 24/7 support +- Provide an **exclusive discount code** or special offer +- Supply **trackable affiliate/referral links** +- Maintain **quality standards** and community respect +- Adhere to our [Provider Guidelines & Code of Conduct](./packages/providers/GUIDELINES.md) + +#### How to Apply + +1. **Review** the [Provider Guidelines & Code of Conduct](./packages/providers/GUIDELINES.md) +2. **Read** the [Partnership Requirements & Process](./packages/providers/README.md) +3. **Create** your provider directory with `provider.json` +4. **Submit** a Pull Request to `frontend/packages/providers/` +5. **Review**: Our team responds within 3-5 business days + +**Example JSON file** (in `your-hosting/provider.json`): + +```json +{ + "$schema": "../schema.json", + "id": "your-hosting", + "name": "Your Hosting Company", + "website": "https://your-hosting.com", + "description": "Premium FiveM and RedM hosting with DDoS protection and 24/7 support.", + "discount": { + "percentage": 20, + "code": "FIXFX20", + "duration": "Lifetime" + }, + "links": [ + { + "label": "FiveM Servers", + "url": "https://your-hosting.com/affiliate?campaign=fixfx", + "description": "High-performance FiveM servers" + } + ], + "features": [ + "99.9% Uptime SLA", + "DDoS Protection", + "Auto-backup & Restore", + "24/7 Support", + "1-Click Install" + ], + "priority": 10 +} +``` + +#### System Features + +- **Automated Validation**: GitHub Actions validates all provider JSON files +- **Schema Enforcement**: JSON Schema ensures consistent data quality +- **Trusted Hosts Scraper**: Weekly automation to maintain current FiveM provider list +- **CI/CD Integration**: Automatic PR creation for provider updates + +For detailed information, see: +- [Provider Guidelines & Code of Conduct](./packages/providers/GUIDELINES.md) - Standards and expectations +- [Partnership Requirements & Process](./packages/providers/README.md) - Detailed how-to guide +- [Trusted Hosts Documentation](./packages/providers/TRUSTED_HOSTS_README.md) - Automation details ## Contributing -We welcome contributions from the community! Here's how you can help: +Contributions are welcome. Please read our [Contributing Guide](.github/CONTRIBUTING.md) before submitting a pull request. 1. Fork the repository -2. Create a new branch for your feature -3. Make your changes -4. Submit a pull request +2. Create a feature branch (`git checkout -b feature/your-feature`) +3. Commit your changes (`git commit -m 'feat: add your feature'`) +4. Push to the branch (`git push origin feature/your-feature`) +5. Open a pull request + +## Community -Please ensure your contributions follow our [contribution guidelines](CONTRIBUTING.md). +- **GitHub Issues**: [Report bugs or request features](https://github.com/CodeMeAPixel/FixFX/issues) +- **Discord**: [discord.gg/cYauqJfnNK](https://discord.gg/cYauqJfnNK) +- **Email**: [hey@codemeapixel.dev](mailto:hey@codemeapixel.dev) ## License -This project is licensed under the AGPL 3.0 License - see the [LICENSE](LICENSE) file for details. \ No newline at end of file +This project is licensed under the AGPL 3.0 License. See the [LICENSE](LICENSE) file for details. \ No newline at end of file diff --git a/app/(blog)/blog/(root)/page.tsx b/app/(blog)/blog/(root)/page.tsx index 1832a2d..c5350a9 100644 --- a/app/(blog)/blog/(root)/page.tsx +++ b/app/(blog)/blog/(root)/page.tsx @@ -1,42 +1,45 @@ import { FixFXIcon } from "@ui/icons"; import { blog } from "@/lib/docs/source"; import Link from "next/link"; -import { Calendar, Clock, ArrowRight } from "lucide-react"; +import { Calendar, ArrowRight } from "lucide-react"; export default function BlogPage() { const posts = blog.getPages(); return ( -
+
{/* Header */} -
-
- -

Blog

+
+
+ + Latest Articles
-

- News, guides, and insights from the FixFX community +

+ FixFX Blog +

+

+ News, guides, and insights from the FixFX community. Learn best practices, get updates, and discover new features.

{/* Posts grid */} -
+
{posts.map((post, index) => ( {/* Gradient accent on hover */} -
+
{/* Meta info */} {post.data.date && ( -
- - +
+ + {new Date(post.data.date).toLocaleDateString('en-US', { month: 'short', day: 'numeric', @@ -47,19 +50,19 @@ export default function BlogPage() { )} {/* Title */} -

+

{post.data.title}

{/* Description */} -

+

{post.data.description}

{/* Read more link */} -
+
Read article - +
@@ -68,8 +71,8 @@ export default function BlogPage() { {/* Empty state */} {posts.length === 0 && ( -
-

No posts yet. Check back soon!

+
+

No posts yet. Check back soon for new content!

)}
diff --git a/app/(blog)/blog/[slug]/page.tsx b/app/(blog)/blog/[slug]/page.tsx index cd151ab..f51d207 100644 --- a/app/(blog)/blog/[slug]/page.tsx +++ b/app/(blog)/blog/[slug]/page.tsx @@ -1,5 +1,6 @@ import defaultMdxComponents from "fumadocs-ui/mdx"; import { CornerDownLeft } from "@ui/icons"; +import { Calendar } from "lucide-react"; import { notFound } from "next/navigation"; import { blog } from "@/lib/docs/source"; import Link from "next/link"; @@ -7,45 +8,63 @@ import Link from "next/link"; export default async function BlogPost(props: { params: Promise<{ slug: string }>; }) { - const params = await props.params; - const page = blog.getPage([params.slug]); + const { slug } = await props.params; + const page = blog.getPage([slug]); if (!page) notFound(); const Mdx = page.data.body; return ( <> -
-

+
+

{page.data.title}

-

+

{page.data.description}

-
+
+
+ + {new Date(page.data.date).toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric' + })} + {page.data.author && ( + <> + + {page.data.author} + + )} +
- Back + Back to Blog
-
-
+
+

-
+
-

Written by

-

{page.data.author}

+

Written by

+

{page.data.author}

-

At

-

- {new Date(page.data.date).toDateString()} +

Published

+

+ {new Date(page.data.date).toLocaleDateString('en-US', { + month: 'long', + day: 'numeric', + year: 'numeric' + })}

diff --git a/app/(blog)/blog/atom.xml/route.ts b/app/(blog)/blog/atom.xml/route.ts new file mode 100644 index 0000000..8812575 --- /dev/null +++ b/app/(blog)/blog/atom.xml/route.ts @@ -0,0 +1,47 @@ +import { blogPosts } from "@/../source.config"; +import { DOCS_URL } from "@utils/index"; + +export async function GET() { + const posts = blogPosts.getPages(); + + const feed = ` + + FixFX Blog + News, tutorials, and updates for the FiveM, RedM, and CitizenFX community + + + ${DOCS_URL}/ + ${new Date().toISOString()} + + FixFX Team + https://github.com/CodeMeAPixel + + ${DOCS_URL}/favicon-32x32.png + ${DOCS_URL}/logo.png + © ${new Date().getFullYear()} FixFX. All rights reserved. + ${posts + .sort((a, b) => new Date(b.data.date).getTime() - new Date(a.data.date).getTime()) + .map( + (post) => ` + + <![CDATA[${post.data.title}]]> + + ${DOCS_URL}/blog/${post.slugs.join("/")} + ${new Date(post.data.date).toISOString()} + ${new Date(post.data.date).toISOString()} + + ${post.data.author} + + + ` + ) + .join("")} +`; + + return new Response(feed, { + headers: { + "Content-Type": "application/atom+xml; charset=utf-8", + "Cache-Control": "public, max-age=3600, s-maxage=3600", + }, + }); +} diff --git a/app/(blog)/blog/feed.json/route.ts b/app/(blog)/blog/feed.json/route.ts new file mode 100644 index 0000000..9b1b97c --- /dev/null +++ b/app/(blog)/blog/feed.json/route.ts @@ -0,0 +1,40 @@ +import { blogPosts } from "@/../source.config"; +import { DOCS_URL } from "@utils/index"; + +export async function GET() { + const posts = blogPosts.getPages(); + + const feed = { + version: "https://jsonfeed.org/version/1.1", + title: "FixFX Blog", + home_page_url: DOCS_URL, + feed_url: `${DOCS_URL}/blog/feed.json`, + description: "News, tutorials, and updates for the FiveM, RedM, and CitizenFX community", + icon: `${DOCS_URL}/android-chrome-512x512.png`, + favicon: `${DOCS_URL}/favicon-32x32.png`, + authors: [ + { + name: "FixFX Team", + url: "https://github.com/CodeMeAPixel", + }, + ], + language: "en-US", + items: posts + .sort((a, b) => new Date(b.data.date).getTime() - new Date(a.data.date).getTime()) + .map((post) => ({ + id: `${DOCS_URL}/blog/${post.slugs.join("/")}`, + url: `${DOCS_URL}/blog/${post.slugs.join("/")}`, + title: post.data.title, + summary: post.data.description || "", + date_published: new Date(post.data.date).toISOString(), + authors: [{ name: post.data.author }], + })), + }; + + return new Response(JSON.stringify(feed, null, 2), { + headers: { + "Content-Type": "application/feed+json; charset=utf-8", + "Cache-Control": "public, max-age=3600, s-maxage=3600", + }, + }); +} diff --git a/app/(blog)/blog/feed.xml/route.ts b/app/(blog)/blog/feed.xml/route.ts new file mode 100644 index 0000000..ec1c9b6 --- /dev/null +++ b/app/(blog)/blog/feed.xml/route.ts @@ -0,0 +1,44 @@ +import { blogPosts } from "@/../source.config"; +import { DOCS_URL } from "@utils/index"; + +export async function GET() { + const posts = blogPosts.getPages(); + + const feed = ` + + + FixFX Blog + ${DOCS_URL} + News, tutorials, and updates for the FiveM, RedM, and CitizenFX community + en-US + ${new Date().toUTCString()} + + + ${DOCS_URL}/logo.png + FixFX Blog + ${DOCS_URL} + + ${posts + .sort((a, b) => new Date(b.data.date).getTime() - new Date(a.data.date).getTime()) + .map( + (post) => ` + + <![CDATA[${post.data.title}]]> + ${DOCS_URL}/blog/${post.slugs.join("/")} + ${DOCS_URL}/blog/${post.slugs.join("/")} + + ${post.data.author} + ${new Date(post.data.date).toUTCString()} + ` + ) + .join("")} + +`; + + return new Response(feed, { + headers: { + "Content-Type": "application/xml; charset=utf-8", + "Cache-Control": "public, max-age=3600, s-maxage=3600", + }, + }); +} diff --git a/app/(blog)/opengraph-image.tsx b/app/(blog)/opengraph-image.tsx index 6915267..b387202 100644 --- a/app/(blog)/opengraph-image.tsx +++ b/app/(blog)/opengraph-image.tsx @@ -14,42 +14,117 @@ export default function Image() { (
+ {/* Background gradient orbs */}
+
+ + {/* Icon */} +
- FixFX Blog + ✍️
+ + {/* Main title */}
- Latest updates and insights from the FiveM community + + Fix + + + FX + + + Blog +
+ + {/* Subtitle */} +

+ Latest updates, tutorials, and insights from the FiveM community +

), { ...size, - }, + } ); } diff --git a/app/(blog)/twitter-image.tsx b/app/(blog)/twitter-image.tsx index 7cc0c38..29b0384 100644 --- a/app/(blog)/twitter-image.tsx +++ b/app/(blog)/twitter-image.tsx @@ -3,8 +3,8 @@ import { ImageResponse } from "next/og"; export const runtime = "nodejs"; export const alt = "FixFX Blog"; export const size = { - width: 506, - height: 506, + width: 1200, + height: 630, }; export const contentType = "image/png"; @@ -14,41 +14,87 @@ export default function Image() { (
+ {/* Background gradient orb */}
+ + {/* Icon */} +
- FixFX + ✍️
+ + {/* Main title */}
- Blog + + Fix + + + FX +
+ + {/* Subtitle */} +

+ Blog +

), { ...size, - }, + } ); } diff --git a/app/(docs)/docs/[...slug]/page.tsx b/app/(docs)/docs/[...slug]/page.tsx index 3655e13..42fffff 100644 --- a/app/(docs)/docs/[...slug]/page.tsx +++ b/app/(docs)/docs/[...slug]/page.tsx @@ -4,6 +4,7 @@ import { DocsTitle, DocsDescription, } from "fumadocs-ui/page"; +import { ImageZoom } from "fumadocs-ui/components/image-zoom"; import { source } from "@/lib/docs/source"; import { metadataImage } from "@/lib/docs/metadata"; import defaultMdxComponents from "fumadocs-ui/mdx"; @@ -14,17 +15,19 @@ import { Editor } from "@ui/core/docs/editor"; export default async function Page({ params, }: { - params: { slug?: string[] }; + params: Promise<{ slug?: string[] }>; }) { + const { slug } = await params; + // Redirect to overview if no slug is provided (root /docs path) - if (!params.slug || params.slug.length === 0) { + if (!slug || slug.length === 0) { return source.getPage(['overview']); } - const page = source.getPage(params.slug); + const page = source.getPage(slug); if (!page) notFound(); - const MDX = page.data.body + const MDX = page.data.body; return ( props.src ? : null, Editor: Editor }} /> diff --git a/app/(docs)/docs/layout.tsx b/app/(docs)/docs/layout.tsx index 598fa49..73dcac9 100644 --- a/app/(docs)/docs/layout.tsx +++ b/app/(docs)/docs/layout.tsx @@ -4,6 +4,24 @@ import { baseOptions } from "@/app/layout.config"; import { FixFXIcon } from "@ui/icons"; import { source } from "@/lib/docs/source"; import type { ReactNode } from "react"; +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: { + default: "Documentation", + template: "%s | FixFX Docs", + }, + description: "Comprehensive documentation for FiveM, RedM, txAdmin, vMenu, and the CitizenFX ecosystem. Guides, tutorials, and API references.", + alternates: { + canonical: "https://fixfx.wiki/docs", + }, + openGraph: { + title: "FixFX Documentation", + description: "Comprehensive documentation for FiveM, RedM, txAdmin, vMenu, and the CitizenFX ecosystem.", + url: "https://fixfx.wiki/docs", + type: "website", + }, +}; const docsOptions: DocsLayoutProps = { ...baseOptions, diff --git a/app/(docs)/opengraph-image.tsx b/app/(docs)/opengraph-image.tsx index 71c2ee0..7d5909c 100644 --- a/app/(docs)/opengraph-image.tsx +++ b/app/(docs)/opengraph-image.tsx @@ -14,42 +14,142 @@ export default function Image() { (
+ {/* Background gradient orbs */}
+
+ + {/* Icon */} +
- FixFX Docs + 📚
+ + {/* Main title */}
+ + Fix + + + FX + + + Docs + +
+ + {/* Subtitle */} +

+ Comprehensive guides and tutorials for the CitizenFX ecosystem +

+ + {/* Tags */} +
- Comprehensive guides and information for the CitizenFX ecosystem + {["FiveM", "RedM", "txAdmin", "vMenu"].map((tag) => ( +
+ {tag} +
+ ))}
), { ...size, - }, + } ); } diff --git a/app/(docs)/twitter-image.tsx b/app/(docs)/twitter-image.tsx index 38e3735..0dce458 100644 --- a/app/(docs)/twitter-image.tsx +++ b/app/(docs)/twitter-image.tsx @@ -3,8 +3,8 @@ import { ImageResponse } from "next/og"; export const runtime = "nodejs"; export const alt = "FixFX Documentation"; export const size = { - width: 506, - height: 506, + width: 1200, + height: 630, }; export const contentType = "image/png"; @@ -14,41 +14,87 @@ export default function Image() { (
+ {/* Background gradient orb */}
+ + {/* Icon */} +
- FixFX + 📚
+ + {/* Main title */}
- Docs + + Fix + + + FX +
+ + {/* Subtitle */} +

+ Documentation +

), { ...size, - }, + } ); } diff --git a/app/(landing)/opengraph-image.tsx b/app/(landing)/opengraph-image.tsx index a8737b6..7160feb 100644 --- a/app/(landing)/opengraph-image.tsx +++ b/app/(landing)/opengraph-image.tsx @@ -1,7 +1,7 @@ import { ImageResponse } from "next/og"; export const runtime = "nodejs"; -export const alt = "FixFX"; +export const alt = "FixFX - Your FiveM & RedM Resource Hub"; export const size = { width: 1200, height: 630, @@ -14,51 +14,166 @@ export default function Image() { (
+ {/* Background gradient orbs */}
+
+
+ + {/* Badge */} +
- FixFX +
+ + Open Source Documentation +
+ + {/* Main title */}
+ + Fix + + + FX + +
+ + {/* Subtitle */} +

- Comprehensive guides for the CitizenFX ecosystem -

+ Your comprehensive resource for FiveM,{" "} + RedM, and the{" "} + CitizenFX ecosystem. +

+ + {/* Bottom indicators */}
- Powered by the Community +
+
+ Free & Open Source +
+
+
+ Community Driven +
+
+
+ Always Updated +
), { ...size, - }, + } ); } diff --git a/app/(landing)/page.tsx b/app/(landing)/page.tsx index 64e400d..2b93334 100644 --- a/app/(landing)/page.tsx +++ b/app/(landing)/page.tsx @@ -3,6 +3,21 @@ import { Features } from "@ui/core/landing/features"; import { DocsPreview } from "@ui/core/landing/docs-preview"; import { Hero } from "@ui/core/layout/hero"; import { Contributors } from "@ui/core/landing/contributors"; +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "FixFX - FiveM & RedM Documentation Hub", + description: "Comprehensive guides, tutorials, and documentation for FiveM, RedM, txAdmin, vMenu, and the CitizenFX ecosystem. Your one-stop resource for server development.", + alternates: { + canonical: "https://fixfx.wiki", + }, + openGraph: { + title: "FixFX - FiveM & RedM Documentation Hub", + description: "Comprehensive guides, tutorials, and documentation for FiveM, RedM, txAdmin, and the CitizenFX ecosystem.", + url: "https://fixfx.wiki", + type: "website", + }, +}; export default function HomePage() { return ( diff --git a/app/(landing)/twitter-image.tsx b/app/(landing)/twitter-image.tsx index c78fe8c..3ce03d4 100644 --- a/app/(landing)/twitter-image.tsx +++ b/app/(landing)/twitter-image.tsx @@ -1,10 +1,10 @@ import { ImageResponse } from "next/og"; export const runtime = "nodejs"; -export const alt = "FixFX"; +export const alt = "FixFX - Your FiveM & RedM Resource Hub"; export const size = { - width: 506, - height: 506, + width: 1200, + height: 630, }; export const contentType = "image/png"; @@ -14,41 +14,88 @@ export default function Image() { (
+ {/* Background gradient orbs */}
+
+ + {/* Main title */} +
- FixFX + + Fix + + + FX +
-
- FiveM Guides -
+ FiveM & RedM Resource Hub +

), { ...size, - }, + } ); } diff --git a/app/api/guidelines/route.ts b/app/api/guidelines/route.ts new file mode 100644 index 0000000..bed6193 --- /dev/null +++ b/app/api/guidelines/route.ts @@ -0,0 +1,23 @@ +import { readFile } from "fs/promises"; +import { join } from "path"; + +export async function GET() { + try { + const filePath = join( + process.cwd(), + "packages/providers/GUIDELINES.md" + ); + const content = await readFile(filePath, "utf-8"); + + return Response.json({ + success: true, + content, + }); + } catch (error) { + console.error("Error reading guidelines:", error); + return Response.json( + { success: false, error: "Failed to read guidelines" }, + { status: 500 } + ); + } +} diff --git a/app/api/providers/route.ts b/app/api/providers/route.ts new file mode 100644 index 0000000..672636f --- /dev/null +++ b/app/api/providers/route.ts @@ -0,0 +1,17 @@ +import { getHostingProviders } from "@/lib/providers"; + +export async function GET() { + try { + const providers = await getHostingProviders(); + return Response.json({ + success: true, + providers, + }); + } catch (error) { + console.error("Error fetching providers:", error); + return Response.json( + { success: false, error: "Failed to fetch providers" }, + { status: 500 } + ); + } +} diff --git a/app/api/trusted-hosts/route.ts b/app/api/trusted-hosts/route.ts new file mode 100644 index 0000000..ca266b9 --- /dev/null +++ b/app/api/trusted-hosts/route.ts @@ -0,0 +1,17 @@ +import { getTrustedHosts } from "@/lib/trusted-hosts"; + +export async function GET() { + try { + const hosts = await getTrustedHosts(); + return Response.json({ + success: true, + hosts, + }); + } catch (error) { + console.error("Error fetching trusted hosts:", error); + return Response.json( + { success: false, error: "Failed to fetch trusted hosts" }, + { status: 500 } + ); + } +} diff --git a/app/apple-icon.tsx b/app/apple-icon.tsx new file mode 100644 index 0000000..502967c --- /dev/null +++ b/app/apple-icon.tsx @@ -0,0 +1,91 @@ +import { ImageResponse } from "next/og"; + +export const runtime = "nodejs"; +export const size = { + width: 180, + height: 180, +}; + +export const contentType = "image/png"; + +export default function AppleIcon() { + return new ImageResponse( + ( +
+ {/* Background gradient orbs - scaled down */} +
+
+
+ + {/* FixFX Icon */} + + {/* First path */} + + + {/* Second path */} + + +
+ ), + { + ...size, + } + ); +} diff --git a/app/artifacts/layout.tsx b/app/artifacts/layout.tsx index 07f66cf..8b2498d 100644 --- a/app/artifacts/layout.tsx +++ b/app/artifacts/layout.tsx @@ -1,8 +1,18 @@ import { Metadata } from "next"; export const metadata: Metadata = { - title: 'Artifact Explorer', - description: 'Explore the CitizenFX artifacts for both GTA V AND RDR2', + title: 'FiveM & RedM Artifact Explorer', + description: 'Browse and download FiveM and RedM server artifacts. Find recommended, latest, and stable builds for Windows and Linux.', + keywords: ['FiveM artifacts', 'RedM artifacts', 'FXServer download', 'CitizenFX artifacts', 'FiveM server files', 'RedM server files'], + alternates: { + canonical: 'https://fixfx.wiki/artifacts', + }, + openGraph: { + title: 'FiveM & RedM Artifact Explorer | FixFX', + description: 'Browse and download FiveM and RedM server artifacts. Find recommended, latest, and stable builds.', + url: 'https://fixfx.wiki/artifacts', + type: 'website', + }, }; export default function ArtifactsLayout({ diff --git a/app/artifacts/opengraph-image.tsx b/app/artifacts/opengraph-image.tsx index fe62f1b..f2e1ebe 100644 --- a/app/artifacts/opengraph-image.tsx +++ b/app/artifacts/opengraph-image.tsx @@ -1,7 +1,7 @@ import { ImageResponse } from "next/og"; export const runtime = "nodejs"; -export const alt = "FixFX Artifacts"; +export const alt = "FixFX Artifacts - FiveM & RedM Server Builds"; export const size = { width: 1200, height: 630, @@ -14,42 +14,166 @@ export default function Image() { (
+ {/* Background gradient orbs */}
+
+ + {/* Icon */} +
- FixFX Artifacts + 📦
+ + {/* Main title */}
+ + Fix + + + FX + + + Artifacts + +
+ + {/* Subtitle */} +

+ Download FiveM and RedM server builds with version tracking +

+ + {/* Status badges */} +
- Access FiveM and RedM server artifacts +
+ ✓ Recommended +
+
+ Latest +
+
+ Active +
), { ...size, - }, + } ); } diff --git a/app/artifacts/twitter-image.tsx b/app/artifacts/twitter-image.tsx index 7176bc4..a9e1af4 100644 --- a/app/artifacts/twitter-image.tsx +++ b/app/artifacts/twitter-image.tsx @@ -1,10 +1,10 @@ import { ImageResponse } from "next/og"; export const runtime = "nodejs"; -export const alt = "FixFX Artifacts"; +export const alt = "FixFX Artifacts - FiveM & RedM Server Builds"; export const size = { - width: 506, - height: 506, + width: 1200, + height: 630, }; export const contentType = "image/png"; @@ -14,41 +14,87 @@ export default function Image() { (
+ {/* Background gradient orb */}
+ + {/* Icon */} +
- FixFX + 📦
+ + {/* Main title */}
- Artifacts + + Fix + + + FX +
+ + {/* Subtitle */} +

+ Artifacts +

), { ...size, - }, + } ); } diff --git a/app/banner-wide/route.tsx b/app/banner-wide/route.tsx new file mode 100644 index 0000000..64d54c9 --- /dev/null +++ b/app/banner-wide/route.tsx @@ -0,0 +1,114 @@ +import { ImageResponse } from "next/og"; + +export const runtime = "nodejs"; + +export async function GET() { + return new ImageResponse( + ( +
+ {/* Background gradient orbs - spread horizontally for wide banner */} +
+
+
+
+
+ + {/* Subtle grid pattern overlay */} +
+ + {/* Subtle gradient overlays */} +
+
+
+ ), + { + width: 2560, + height: 720, + } + ); +} diff --git a/app/banner/route.tsx b/app/banner/route.tsx new file mode 100644 index 0000000..10a8627 --- /dev/null +++ b/app/banner/route.tsx @@ -0,0 +1,114 @@ +import { ImageResponse } from "next/og"; + +export const runtime = "nodejs"; + +export async function GET() { + return new ImageResponse( + ( +
+ {/* Background gradient orbs */} +
+
+
+
+
+ + {/* Subtle grid pattern overlay */} +
+ + {/* Subtle noise texture simulation with dots */} +
+
+
+ ), + { + width: 1920, + height: 1080, + } + ); +} diff --git a/app/brand/page.tsx b/app/brand/page.tsx new file mode 100644 index 0000000..c0cd275 --- /dev/null +++ b/app/brand/page.tsx @@ -0,0 +1,127 @@ +"use client"; + +import { FixFXIcon } from "@ui/icons"; +import { Download } from "lucide-react"; + +export default function BrandPage() { + return ( +
+ {/* Background gradient orbs - adjusted for mobile */} +
+
+
+ + {/* Content */} +
+ {/* Icon */} +
+ +
+ + {/* Title */} +
+ + Fix + + + FX + +
+ + {/* Subtitle */} +

+ Your comprehensive resource for FiveM, RedM, and the CitizenFX ecosystem. +

+ + {/* Brand Guidelines */} +
+

Brand Guidelines

+ + {/* Color Palette */} +
+

Primary Colors

+
+
+
+ #2563EB +
+
+
+ #3B82F6 +
+
+
+ #06B6D4 +
+
+
+ #0A0A0F +
+
+
+ + {/* Typography */} +
+

Typography

+
+

+ Font Family: Inter, system-ui, sans-serif +

+

+ Logo Text: "Fix" in gradient, "FX" in foreground +

+
+
+ + {/* Usage Notes */} +
+

Usage Notes

+
    +
  • + + Use the icon on dark backgrounds for best visibility +
  • +
  • + + Maintain minimum padding around the logo +
  • +
  • + + Do not distort or rotate the logo +
  • +
  • + + Do not change the gradient colors +
  • +
+
+
+ + {/* Download buttons */} + + + {/* SVG Download */} +

+ Need a different format? Contact us for SVG or other formats. +

+
+
+ ); +} diff --git a/app/chat/layout.tsx b/app/chat/layout.tsx index 8a0933d..a1afd33 100644 --- a/app/chat/layout.tsx +++ b/app/chat/layout.tsx @@ -1,9 +1,18 @@ import { Metadata } from "next"; export const metadata: Metadata = { - title: 'Fixie - AI Assistant', - description: 'Your intelligent AI assistant for the CitizenFX ecosystem. Get help with FiveM, RedM, txAdmin, server configuration, Lua scripting, and more.', - keywords: ['FiveM', 'RedM', 'txAdmin', 'CitizenFX', 'AI Assistant', 'Lua', 'Server Development'], + title: 'Fixie AI - FiveM & RedM Assistant', + description: 'AI-powered assistant for FiveM and RedM development. Get instant help with Lua scripting, txAdmin setup, server configuration, and troubleshooting.', + keywords: ['FiveM AI', 'RedM AI', 'txAdmin help', 'CitizenFX assistant', 'Lua scripting help', 'FiveM support', 'server development help'], + alternates: { + canonical: 'https://fixfx.wiki/chat', + }, + openGraph: { + title: 'Fixie AI - FiveM & RedM Assistant | FixFX', + description: 'AI-powered assistant for FiveM and RedM development. Get instant help with Lua scripting, txAdmin setup, and more.', + url: 'https://fixfx.wiki/chat', + type: 'website', + }, }; export default function AskLayout({ diff --git a/app/chat/opengraph-image.tsx b/app/chat/opengraph-image.tsx index 67997ab..3cb4538 100644 --- a/app/chat/opengraph-image.tsx +++ b/app/chat/opengraph-image.tsx @@ -1,7 +1,7 @@ import { ImageResponse } from "next/og"; export const runtime = "nodejs"; -export const alt = "FixFX Chat"; +export const alt = "FixFX Chat - AI-Powered FiveM Assistant"; export const size = { width: 1200, height: 630, @@ -14,42 +14,136 @@ export default function Image() { (
+ {/* Background gradient orbs */}
+
+ + {/* Icon */} +
- FixFX Chat + 🤖
+ + {/* Main title */}
+ + Fix + + + FX + + + Chat + +
+ + {/* Subtitle */} +

+ AI-powered assistant for FiveM and RedM development +

+ + {/* Badge */} +
- AI-powered assistance for FiveM development +
+ + Powered by AI +
), { ...size, - }, + } ); } diff --git a/app/chat/twitter-image.tsx b/app/chat/twitter-image.tsx index 7113770..c7be150 100644 --- a/app/chat/twitter-image.tsx +++ b/app/chat/twitter-image.tsx @@ -1,10 +1,10 @@ import { ImageResponse } from "next/og"; export const runtime = "nodejs"; -export const alt = "FixFX Chat"; +export const alt = "FixFX Chat - AI-Powered FiveM Assistant"; export const size = { - width: 506, - height: 506, + width: 1200, + height: 630, }; export const contentType = "image/png"; @@ -14,41 +14,87 @@ export default function Image() { (
+ {/* Background gradient orb */}
+ + {/* Icon */} +
- FixFX + 🤖
+ + {/* Main title */}
- Chat + + Fix + + + FX +
+ + {/* Subtitle */} +

+ Chat +

), { ...size, - }, + } ); } diff --git a/app/components/index.ts b/app/components/index.ts index 25bf6ef..564075b 100644 --- a/app/components/index.ts +++ b/app/components/index.ts @@ -1,2 +1 @@ -export { FileSource } from './file-source'; -export { ImageModal } from './image-modal'; +export { FileSource, ImageModal } from '@ui/components'; diff --git a/app/docs-og/[...slug]/route.tsx b/app/docs-og/[...slug]/route.tsx index 8702edb..0bec07b 100644 --- a/app/docs-og/[...slug]/route.tsx +++ b/app/docs-og/[...slug]/route.tsx @@ -8,6 +8,8 @@ export const GET = metadataImage.createAPI((page) => { title: page.data.title, description: page.data.description, site: "FixFX", + primaryColor: "#3b82f6", + primaryTextColor: "#f8fafc", }); }); diff --git a/app/favicon.ico b/app/favicon.ico index 92389de..fbfb4fa 100644 Binary files a/app/favicon.ico and b/app/favicon.ico differ diff --git a/app/hosting/layout.tsx b/app/hosting/layout.tsx new file mode 100644 index 0000000..353a38f --- /dev/null +++ b/app/hosting/layout.tsx @@ -0,0 +1,22 @@ +import type { Metadata } from "next"; +import type { ReactNode } from "react"; +import { HomeLayout } from "fumadocs-ui/layouts/home"; +import { baseOptions } from "@/app/layout.config"; + +export const metadata: Metadata = { + title: "Hosting Partners - FixFX", + description: "Explore our trusted hosting partners for your FiveM and RedM servers. Get exclusive discounts on high-performance game server hosting.", + alternates: { + canonical: "https://fixfx.wiki/hosting", + }, + openGraph: { + title: "Hosting Partners - FixFX", + description: "Explore our trusted hosting partners for your FiveM and RedM servers. Get exclusive discounts on high-performance game server hosting.", + url: "https://fixfx.wiki/hosting", + type: "website", + }, +}; + +export default function HostingLayout({ children }: { children: ReactNode }) { + return {children}; +} diff --git a/app/hosting/opengraph-image.tsx b/app/hosting/opengraph-image.tsx new file mode 100644 index 0000000..aee9d41 --- /dev/null +++ b/app/hosting/opengraph-image.tsx @@ -0,0 +1,169 @@ +import { ImageResponse } from "next/og"; + +export const runtime = "nodejs"; +export const alt = "FixFX Hosting Partners - Trusted Game Server Hosting"; +export const size = { + width: 1200, + height: 630, +}; + +export const contentType = "image/png"; + +export default function Image() { + return new ImageResponse( + ( +
+ {/* Background gradient orbs */} +
+
+
+ + {/* Badge */} +
+
🤝
+ + Official Partners + +
+ + {/* Main title */} +
+ + Hosting Partners + +
+ + {/* Subtitle */} +

+ Trusted hosting providers for your{" "} + FiveM and{" "} + RedM servers +

+ + {/* Bottom indicators */} +
+
+
+ Exclusive Discounts +
+
+
+ High Performance +
+
+
+ 24/7 Support +
+
+
+ ), + { + ...size, + } + ); +} diff --git a/app/hosting/page.tsx b/app/hosting/page.tsx new file mode 100644 index 0000000..0cb6599 --- /dev/null +++ b/app/hosting/page.tsx @@ -0,0 +1,369 @@ +"use client"; + +import { useState, useEffect } from "react"; +import Link from "next/link"; +import Image from "next/image"; +import { + Server, + Zap, + Shield, + Headphones, + ExternalLink, + Percent, + ChevronRight, + Handshake, + Cloud, +} from "lucide-react"; +import { ProviderCard, GuidelinesModal } from "@ui/components"; + +interface Provider { + id: string; + name: string; + website: string; + logo?: string; + description: string; + discount: { + percentage: number; + code: string; + }; + links: { + label: string; + url: string; + }[]; + features?: string[]; + isTrusted?: boolean; +} + +export default function HostingPage() { + const [guidelinesOpen, setGuidelinesOpen] = useState(false); + const [providers, setProviders] = useState([]); + const [providersWithTrust, setProvidersWithTrust] = useState([]); + const [loading, setLoading] = useState(true); + + // Fetch providers on mount + useEffect(() => { + const fetchProviders = async () => { + try { + const response = await fetch("/api/providers"); + const data = await response.json(); + if (data.success) { + const providersData = data.providers as Provider[]; + setProviders(providersData); + + // Fetch trusted hosts + const trustedResponse = await fetch("/api/trusted-hosts"); + const trustedData = await trustedResponse.json(); + if (trustedData.success) { + const trustedHosts = trustedData.hosts; + const withTrust = providersData.map((provider) => ({ + ...provider, + isTrusted: provider.website + ? trustedHosts.some((host: any) => { + try { + const providerUrl = new URL(provider.website).hostname.toLowerCase(); + const hostUrl = new URL(host.url).hostname.toLowerCase(); + return providerUrl === hostUrl || providerUrl.includes(hostUrl); + } catch { + return false; + } + }) + : false, + })); + setProvidersWithTrust(withTrust); + } + } + } catch (error) { + console.error("Failed to fetch providers:", error); + } finally { + setLoading(false); + } + }; + + fetchProviders(); + }, []); + + const maxDiscount = providersWithTrust.length > 0 + ? Math.max(...providersWithTrust.map(p => p.discount.percentage)) + : 20; + + // Get first provider's links for CTA (fallback to ZAP-Hosting) + const firstProvider = providersWithTrust[0]; + const fivemLink = firstProvider?.links.find(l => l.label.toLowerCase().includes("fivem"))?.url + || "https://zap-hosting.com/FixFXFiveM"; + const redmLink = firstProvider?.links.find(l => l.label.toLowerCase().includes("redm"))?.url + || "https://zap-hosting.com/FixFXRedM"; + const vpsLink = "https://zap-hosting.com/a/8d785f5b626ef6617320d4bca50a4e344c464437?voucher=FixFX-a-8909"; + return ( +
+ {/* Hero Section */} +
+ {/* Background effects */} +
+
+
+
+ +
+ {/* Breadcrumb */} + + +
+ {/* Badge */} +
+ + Trusted Partners +
+ + {/* Title */} +

+ Hosting{" "} + + Partners + +

+ + {/* Description */} +

+ We've partnered with the best game server hosting providers to bring you exclusive + discounts. Get your FiveM or RedM server up and running with trusted, high-performance hosting. +

+ + {/* Stats */} +
+
+ {loading ? ( +
+ ) : ( +

{maxDiscount}%+

+ )} +

Exclusive Discounts

+
+
+
+

24/7

+

Support Available

+
+
+
+ {loading ? ( +
+ ) : ( +

{providers.length}

+ )} +

Trusted Partners

+
+
+
+
+
+ + {/* Partners Grid */} +
+ {loading ? ( +
+ {[1, 2].map((i) => ( +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ))} +
+ ) : providersWithTrust.length > 0 ? ( +
+ {providersWithTrust.map((provider) => ( + + ))} +
+ ) : ( +
+
+ +
+

No Partners Yet

+

+ We're actively looking for hosting partners.{" "} + + Learn how to become a partner + +

+
+ )} +
+ + {/* Why Partner Section */} +
+
+

+ Why Use Our Partner Links? +

+
+
+
+ +
+

Exclusive Discounts

+

+ Get special pricing not available anywhere else. Our partner codes provide ongoing savings + for as long as you own your server. +

+
+
+
+ +
+

Vetted Providers

+

+ We only partner with hosting providers we trust and have personally tested. Quality and + reliability are our top priorities. +

+
+
+
+ +
+

Support FixFX

+

+ Using our partner links helps support the continued development of FixFX and keeps our + documentation free for everyone. +

+
+
+
+
+ + {/* CTA Section */} +
+
+ {/* Bottom Text */} +
+

+ By using our partner links, you support FixFX while getting exclusive discounts. Thank you for your support! +

+
+
+
+ + {/* Become a Partner Section */} +
+
+
+
+ {/* Header */} +
+
+ + Partnership Opportunity +
+

+ Want to Partner with FixFX? +

+

+ We're always looking for quality hosting providers who want to offer exclusive benefits to our community. +

+
+ + {/* Benefits Grid */} +
+
+
+ +
+

Reach Active Community

+

+ Connect with thousands of FiveM and RedM server owners +

+
+
+
+ +
+

Build Trust

+

+ Showcase your reliability and commitment to quality +

+
+
+
+ +
+

Tracked Attribution

+

+ Use affiliate links to track conversions and ROI +

+
+
+ + {/* Guidelines Preview */} +
+

Partnership Requirements

+
+

+ + FiveM and/or RedM server hosting support +

+

+ + 99.6%+ uptime with responsive 24/7 support +

+

+ + Exclusive discount or special offer for FixFX users +

+

+ + Affiliate/referral links for tracking +

+
+
+ + {/* CTA Buttons */} +
+ + + Ask on Discord + + +
+ + {/* Footer Note */} +

+ We review all partnership applications within 3-5 business days. + FixFX reserves the right to accept or decline requests at our discretion. +

+
+
+
+
+ + +
+ ); +} diff --git a/app/hosting/twitter-image.tsx b/app/hosting/twitter-image.tsx new file mode 100644 index 0000000..aee9d41 --- /dev/null +++ b/app/hosting/twitter-image.tsx @@ -0,0 +1,169 @@ +import { ImageResponse } from "next/og"; + +export const runtime = "nodejs"; +export const alt = "FixFX Hosting Partners - Trusted Game Server Hosting"; +export const size = { + width: 1200, + height: 630, +}; + +export const contentType = "image/png"; + +export default function Image() { + return new ImageResponse( + ( +
+ {/* Background gradient orbs */} +
+
+
+ + {/* Badge */} +
+
🤝
+ + Official Partners + +
+ + {/* Main title */} +
+ + Hosting Partners + +
+ + {/* Subtitle */} +

+ Trusted hosting providers for your{" "} + FiveM and{" "} + RedM servers +

+ + {/* Bottom indicators */} +
+
+
+ Exclusive Discounts +
+
+
+ High Performance +
+
+
+ 24/7 Support +
+
+
+ ), + { + ...size, + } + ); +} diff --git a/app/icon.tsx b/app/icon.tsx new file mode 100644 index 0000000..c76e854 --- /dev/null +++ b/app/icon.tsx @@ -0,0 +1,90 @@ +import { ImageResponse } from "next/og"; + +export const runtime = "nodejs"; +export const size = { + width: 512, + height: 512, +}; + +export const contentType = "image/png"; + +export default function Icon() { + return new ImageResponse( + ( +
+ {/* Background gradient orbs */} +
+
+
+ + {/* FixFX Icon */} + + {/* First path */} + + + {/* Second path */} + + +
+ ), + { + ...size, + } + ); +} diff --git a/app/layout.config.tsx b/app/layout.config.tsx index 5257557..ea78e4f 100644 --- a/app/layout.config.tsx +++ b/app/layout.config.tsx @@ -2,7 +2,7 @@ import type { HomeLayoutProps } from "fumadocs-ui/layouts/home"; import { GITHUB_LINK, DISCORD_LINK } from "@/packages/utils/src"; import { FixFXIcon } from "@ui/icons"; import { FaDiscord } from "react-icons/fa"; -import { Gamepad, Home, PlugZap, LogsIcon, Bot } from "lucide-react"; +import { Gamepad, Home, PlugZap, LogsIcon, Bot, X, Server, Palette } from "lucide-react"; export const baseOptions: HomeLayoutProps = { disableThemeSwitch: true, @@ -18,9 +18,15 @@ export const baseOptions: HomeLayoutProps = { links: [ { type: "icon", - text: "", + text: "Discord", icon: , - url: "https://discord.gg/Vv2bdC44Ge", + url: "https://discord.gg/cYauqJfnNK", + }, + { + type: "icon", + text: "Twitter/X", + icon: , + url: "https://twitter.com/FixFXWiki", }, { type: "main", @@ -211,7 +217,39 @@ export const baseOptions: HomeLayoutProps = { text: "Server Artifacts", description: "Explore the latest server artifacts for CFX.", url: "/artifacts" - } + }, + { + menu: { + banner: ( +
+ +

+ Hosting Partners +

+
+ ) + }, + icon: , + text: "Hosting Partners", + description: "Discover trusted hosting providers with exclusive discounts for FixFX users.", + url: "/hosting" + }, + { + menu: { + banner: ( +
+ +

+ Brand Assets +

+
+ ) + }, + icon: , + text: "Brand Assets", + description: "Download official FixFX logos, icons, and brand guidelines.", + url: "/brand" + }, ] } ], diff --git a/app/layout.tsx b/app/layout.tsx index 938bb83..26ec605 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,16 +1,33 @@ -import { Banner } from 'fumadocs-ui/components/banner'; import { RootProvider } from "fumadocs-ui/provider"; import { Analytics } from "@vercel/analytics/react"; import Script from "next/script"; import { inter, jetbrains } from "@/lib/fonts"; import { keywords } from "@utils/index"; -import '@/styles/sheet-handle.css'; import type { ReactNode } from "react"; -import type { Metadata } from "next"; +import type { Metadata, Viewport } from "next"; import "@ui/styles"; +export const viewport: Viewport = { + themeColor: [ + { media: "(prefers-color-scheme: light)", color: "#ffffff" }, + { media: "(prefers-color-scheme: dark)", color: "#0a0a0a" }, + ], + width: "device-width", + initialScale: 1, + maximumScale: 5, +}; + export const metadata: Metadata = { metadataBase: new URL("https://fixfx.wiki"), + + /** Canonical & Alternates */ + alternates: { + canonical: "https://fixfx.wiki", + languages: { + "en-US": "https://fixfx.wiki", + }, + }, + /** OpenGraph */ openGraph: { type: "website", @@ -18,20 +35,31 @@ export const metadata: Metadata = { url: "https://fixfx.wiki", locale: "en_US", creators: ["@CodeMeAPixel"], - description: "Comprehensive guides and information for the CitizenFX ecosystem.", + title: "FixFX - FiveM & RedM Documentation Hub", + description: "Comprehensive guides, tutorials, and documentation for FiveM, RedM, txAdmin, and the CitizenFX ecosystem. Your one-stop resource for server development.", + images: [ + { + url: "/opengraph-image.png", + width: 1200, + height: 630, + alt: "FixFX - Your FiveM & RedM Resource Hub", + }, + ], }, twitter: { - title: "FixFX", + title: "FixFX - FiveM & RedM Documentation Hub", card: "summary_large_image", creator: "@CodeMeAPixel", - site: "https://fixfx.wiki", - description: "Comprehensive guides and information for the CitizenFX ecosystem.", + site: "@FixFXWiki", + description: "Comprehensive guides, tutorials, and documentation for FiveM, RedM, txAdmin, and the CitizenFX ecosystem.", + images: ["/twitter-image.png"], }, /** OpenGraph */ /** PWA */ applicationName: "FixFX", appleWebApp: { + capable: true, statusBarStyle: "default", title: "FixFX", }, @@ -40,36 +68,56 @@ export const metadata: Metadata = { }, formatDetection: { telephone: false, + email: false, + address: false, }, /** PWA */ title: { - default: "FixFX", + default: "FixFX - FiveM & RedM Documentation Hub", template: "%s | FixFX", }, - description: "Comprehensive guides and information for the CitizenFX ecosystem.", - creator: "@CodeMeAPixel", - authors: { - url: "https://github.com/CodeMeAPixel", - name: "Pixelated", - }, + description: "Comprehensive guides, tutorials, and documentation for FiveM, RedM, txAdmin, and the CitizenFX ecosystem. Your one-stop resource for server development.", + creator: "CodeMeAPixel", + publisher: "FixFX", + authors: [ + { + url: "https://github.com/CodeMeAPixel", + name: "Pixelated", + }, + ], keywords: keywords, + category: "Documentation", - /** Icons */ + /** Icons */ icons: { - icon: "/favicon.ico", + icon: [ + { url: "/favicon.ico", sizes: "any" }, + { url: "/favicon-16x16.png", sizes: "16x16", type: "image/png" }, + { url: "/favicon-32x32.png", sizes: "32x32", type: "image/png" }, + ], shortcut: "/favicon.ico", - apple: "/apple-touch-icon.png", + apple: [ + { url: "/apple-touch-icon.png", sizes: "180x180", type: "image/png" }, + ], + other: [ + { + rel: "mask-icon", + url: "/logo.png", + }, + ], }, - /** Icons */ + /** Icons */ /** Robots */ robots: { index: true, follow: true, + nocache: false, googleBot: { index: true, follow: true, + noimageindex: false, "max-video-preview": -1, "max-image-preview": "large", "max-snippet": -1, @@ -77,15 +125,139 @@ export const metadata: Metadata = { }, verification: { google: process.env.GOOGLE_VERIFICATION_CODE ?? undefined, + yandex: process.env.YANDEX_VERIFICATION_CODE ?? undefined, + other: { + "msvalidate.01": process.env.BING_VERIFICATION_CODE ?? "", + }, }, /** Robots */ }; +// JSON-LD Structured Data for SEO +const websiteJsonLd = { + "@context": "https://schema.org", + "@type": "WebSite", + name: "FixFX", + alternateName: ["FixFX Wiki", "FixFX Documentation"], + url: "https://fixfx.wiki", + description: "Comprehensive guides, tutorials, and documentation for FiveM, RedM, txAdmin, and the CitizenFX ecosystem.", + inLanguage: "en-US", + publisher: { + "@type": "Organization", + name: "FixFX", + logo: { + "@type": "ImageObject", + url: "https://fixfx.wiki/logo.png", + width: 512, + height: 512, + }, + }, + potentialAction: { + "@type": "SearchAction", + target: { + "@type": "EntryPoint", + urlTemplate: "https://fixfx.wiki/docs?search={search_term_string}", + }, + "query-input": "required name=search_term_string", + }, + sameAs: [ + "https://github.com/CodeMeAPixel/FixFX", + "https://discord.gg/cYauqJfnNK", + ], +}; + +const organizationJsonLd = { + "@context": "https://schema.org", + "@type": "Organization", + name: "FixFX", + url: "https://fixfx.wiki", + logo: "https://fixfx.wiki/logo.png", + description: "Documentation hub for FiveM, RedM, txAdmin, and the CitizenFX ecosystem.", + foundingDate: "2024", + sameAs: [ + "https://github.com/CodeMeAPixel/FixFX", + "https://discord.gg/cYauqJfnNK", + ], + contactPoint: { + "@type": "ContactPoint", + contactType: "technical support", + url: "https://github.com/CodeMeAPixel/FixFX/issues", + }, +}; + +const softwareJsonLd = { + "@context": "https://schema.org", + "@type": "SoftwareApplication", + name: "FixFX Documentation", + applicationCategory: "DeveloperApplication", + operatingSystem: "Web", + offers: { + "@type": "Offer", + price: "0", + priceCurrency: "USD", + }, + aggregateRating: { + "@type": "AggregateRating", + ratingValue: "5", + ratingCount: "100", + bestRating: "5", + worstRating: "1", + }, +}; + +const jsonLd = [websiteJsonLd, organizationJsonLd, softwareJsonLd]; + export default function Layout({ children, }: Readonly<{ children: ReactNode }>) { return ( - + + + {/* JSON-LD Structured Data */} + - - - - - - -``` - -```css -/* html/style.css */ -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -body { - font-family: 'Arial', sans-serif; - background: transparent; - color: white; -} - -#container { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: 600px; - height: 400px; - background: rgba(0, 0, 0, 0.9); - border-radius: 10px; - border: 1px solid #333; -} - -#header { - display: flex; - justify-content: space-between; - align-items: center; - padding: 20px; - border-bottom: 1px solid #333; -} - -#close-btn { - background: #ff4757; - color: white; - border: none; - border-radius: 50%; - width: 30px; - height: 30px; - cursor: pointer; - font-size: 16px; -} - -#content { - padding: 20px; -} -``` - -```javascript -// html/script.js -$(document).ready(function() { - // Hide UI on ESC key - document.onkeyup = function(data) { - if (data.which == 27) { - closeUI(); - } - } - - // Close button click - $('#close-btn').click(function() { - closeUI(); - }); - - // Listen for messages from Lua - window.addEventListener('message', function(event) { - const data = event.data; - - switch(data.action) { - case 'open': - openUI(data.data); - break; - case 'close': - closeUI(); - break; - case 'updateData': - updateUI(data.data); - break; - } - }); -}); - -function openUI(data) { - $('#container').fadeIn(300); - $.post('https://esx_myresource/uiLoaded', JSON.stringify({})); -} - -function closeUI() { - $('#container').fadeOut(300); - $.post('https://esx_myresource/closeUI', JSON.stringify({})); -} - -function updateUI(data) { - // Update UI with new data -} -``` - -### Lua NUI Integration - -```lua --- Client-side NUI management -local isUIOpen = false - --- Open UI -function OpenUI(data) - if isUIOpen then return end - - isUIOpen = true - SetNuiFocus(true, true) - SendNUIMessage({ - action = 'open', - data = data - }) -end - --- Close UI -function CloseUI() - if not isUIOpen then return end - - isUIOpen = false - SetNuiFocus(false, false) - SendNUIMessage({ - action = 'close' - }) -end - --- NUI Callbacks -RegisterNUICallback('uiLoaded', function(data, cb) - -- UI has loaded - cb('ok') -end) - -RegisterNUICallback('closeUI', function(data, cb) - CloseUI() - cb('ok') -end) - --- Export functions for other resources -exports('OpenUI', OpenUI) -exports('CloseUI', CloseUI) -``` - -## Event System - -### Custom Events - -```lua --- Server-side events -RegisterNetEvent('esx_myresource:doSomething', function(data) - local xPlayer = ESX.GetPlayerFromId(source) - - if not xPlayer then return end - - -- Validate data - if not data or not data.value then - return - end - - -- Process request - local success = processRequest(xPlayer, data) - - -- Send response - TriggerClientEvent('esx_myresource:requestResult', source, success) -end) - --- Client-side events -RegisterNetEvent('esx_myresource:requestResult', function(success) - if success then - ESX.ShowNotification('Request successful!') - else - ESX.ShowNotification('Request failed!', 'error') - end -end) -``` - -### Callbacks - -```lua --- Server-side callback -ESX.RegisterServerCallback('esx_myresource:getData', function(source, cb, playerId) - local xPlayer = ESX.GetPlayerFromId(playerId) - if xPlayer then - cb(xPlayer) - else - cb(false) - end -end) - --- Client-side callback usage -ESX.TriggerServerCallback('esx_myresource:getData', function(playerData) - if playerData then - -- Use player data - end -end, GetPlayerServerId(PlayerId())) -``` - -## Commands - -### Creating Commands - -```lua --- Server-side command with ESX integration -RegisterCommand('givemoney', function(source, args, rawCommand) - local xPlayer = ESX.GetPlayerFromId(source) - if not xPlayer then return end - - -- Check permissions - if xPlayer.getGroup() ~= 'admin' then - xPlayer.showNotification('You need admin permissions', 'error') - return - end - - local targetId = tonumber(args[1]) - local amount = tonumber(args[2]) - - if not targetId or not amount then - xPlayer.showNotification('Usage: /givemoney [id] [amount]', 'error') - return - end - - local xTarget = ESX.GetPlayerFromId(targetId) - if not xTarget then - xPlayer.showNotification('Player not found', 'error') - return - end - - xTarget.addMoney(amount) - xPlayer.showNotification('Money given successfully') - xTarget.showNotification('You received $' .. amount) -end, false) - --- Client-side command -RegisterCommand('showjob', function(source, args, rawCommand) - local playerData = ESX.GetPlayerData() - ESX.ShowNotification('Your job: ' .. playerData.job.label .. ' - Grade: ' .. playerData.job.grade_label) -end, false) -``` - -## Configuration Management - -### config.lua Template - -```lua -Config = {} -Config.Locale = 'en' - --- General settings -Config.Debug = false - --- Feature toggles -Config.EnableFeatureA = true -Config.EnableFeatureB = false - --- Timing settings -Config.Cooldowns = { - action1 = 5000, -- 5 seconds - action2 = 30000, -- 30 seconds -} - --- Job permissions -Config.AuthorizedJobs = { - 'police', - 'ambulance', - 'mechanic' -} - -Config.JobPermissions = { - mechanic = { - repair = 0, -- Grade 0+ - advanced = 2, -- Grade 2+ - boss = 3 -- Grade 3+ - }, - police = { - arrest = 0, - impound = 1, - commander = 3 - } -} - --- Locations -Config.Locations = { - mechanic_shop = { - coords = vector3(123.45, 678.90, 12.34), - heading = 90.0, - radius = 2.0, - blip = { - sprite = 446, - color = 2, - scale = 0.8, - name = "Mechanic Shop" - } - } -} - --- Items and pricing -Config.Items = { - repair_kit = { - item = 'repair_kit', - price = 500, - label = 'Repair Kit' - } -} - --- Money settings -Config.Prices = { - repair = 1000, - paint = 500 -} -``` - -## Localization - -### Locale Files - -```lua --- locales/en.lua -Locales['en'] = { - -- Notifications - ['not_enough_money'] = 'You don\'t have enough money', - ['action_completed'] = 'Action completed successfully', - ['invalid_amount'] = 'Invalid amount', - - -- Jobs - ['job_mechanic'] = 'Mechanic', - ['job_police'] = 'Police Officer', - - -- Items - ['item_repair_kit'] = 'Repair Kit', - ['item_used'] = 'You used %s', - - -- Commands - ['command_usage'] = 'Usage: %s', - ['player_not_found'] = 'Player not found', - ['no_permission'] = 'You don\'t have permission', -} -``` - -```lua --- locales/es.lua -Locales['es'] = { - -- Notifications - ['not_enough_money'] = 'No tienes suficiente dinero', - ['action_completed'] = 'Acción completada exitosamente', - ['invalid_amount'] = 'Cantidad inválida', - - -- Jobs - ['job_mechanic'] = 'Mecánico', - ['job_police'] = 'Oficial de Policía', - - -- Items - ['item_repair_kit'] = 'Kit de Reparación', - ['item_used'] = 'Usaste %s', - - -- Commands - ['command_usage'] = 'Uso: %s', - ['player_not_found'] = 'Jugador no encontrado', - ['no_permission'] = 'No tienes permisos', -} -``` - -### Using Translations - -```lua --- In your scripts -local message = _U('not_enough_money') -xPlayer.showNotification(message, 'error') - --- With parameters -local message = _U('item_used', 'Repair Kit') -ESX.ShowNotification(message) -``` - -## Testing & Debugging - -### Debug Functions - -```lua --- Debug utility -local function DebugPrint(...) - if Config.Debug then - print('^3[ESX-MYRESOURCE]^7', ...) - end -end - --- Server-side debugging -local function LogAction(xPlayer, action, data) - if Config.Debug then - print(string.format('^3[ESX-MYRESOURCE]^7 Player: %s (%s) | Action: %s | Data: %s', - xPlayer.getName(), - xPlayer.identifier, - action, - json.encode(data) - )) - end -end -``` - -### Testing Checklist - -- [ ] Resource starts without errors -- [ ] ESX object initializes properly -- [ ] Database connections work -- [ ] Player events trigger correctly -- [ ] Jobs and permissions work -- [ ] Money transactions function -- [ ] Items can be used/given/removed -- [ ] UI opens/closes properly -- [ ] Commands execute with proper permissions -- [ ] Localization works -- [ ] No console errors or warnings - -## Best Practices - -### Code Organization - -1. **Follow ESX Conventions**: Use ESX naming conventions and patterns -2. **Use Callbacks**: For data requests between client and server -3. **Validate Everything**: Never trust client input -4. **Handle Errors**: Use pcall for database operations -5. **Performance**: Avoid unnecessary loops and timers - -### Security - -1. **Server Validation**: Always validate on the server -2. **Permission Checks**: Verify job/permissions before actions -3. **Rate Limiting**: Prevent spam/abuse -4. **Secure Events**: Use source validation - -### Performance - -1. **Efficient Queries**: Use proper database indexes -2. **Caching**: Cache frequently accessed data -3. **Resource Cleanup**: Clean up on resource stop -4. **Minimal UI**: Keep NUI lightweight - -This comprehensive development guide provides everything needed to create professional ESX resources following best practices and framework conventions. + +We are working hard to get the full ESX documentation done as soon as possible! + \ No newline at end of file diff --git a/content/docs/frameworks/esx/index.mdx b/content/docs/frameworks/esx/index.mdx index 2956b28..d7648da 100644 --- a/content/docs/frameworks/esx/index.mdx +++ b/content/docs/frameworks/esx/index.mdx @@ -4,186 +4,8 @@ description: The ESX framework for FiveM servers. icon: "Package" --- -ESX (ES Extended) is one of the most popular frameworks for FiveM servers, providing a comprehensive system for roleplay servers. **ESX Legacy** is the modern, actively maintained version with significant improvements. +import { InfoBanner } from '@ui/components/mdx-components'; -## Overview - -ESX Legacy is a completely refactored framework offering enhanced performance, security, and modern FiveM compatibility: - -### ESX Legacy Features - -- **Modern Codebase** - Lua 5.4 support with optimized performance -- **ox_lib Integration** - Modern UI components and utilities -- **oxmysql Database** - Improved database performance and async queries -- **Player Management** - Enhanced multi-character support with identity system -- **Economy System** - Cash, bank, and black money with transaction logging -- **Job Framework** - Dynamic jobs with grade hierarchy and society banking -- **Inventory Management** - Weight-based system with item metadata support -- **Vehicle System** - Ownership, garages, keys, and modification persistence -- **Property Systems** - Real estate, apartments, and storage management -- **Society System** - Organization banking and employee management -- **Status Effects** - Hunger, thirst, and status effect system - -## Key Features - - - - Comprehensive character system with persistent data, multi-character support, and identity management. - - - - Dynamic job assignment with grades, salaries, and society banking for organizations. - - - - Multi-currency economy with cash, bank, and black money systems plus transaction logging. - - - - Weight-based inventory system with item categories and vehicle/property storage. - - - - Complete vehicle ownership system with garages, modifications, and impound functionality. - - - - Property ownership with customizable interiors and storage systems. - - - -## Getting Started - - - - Complete setup guide including database configuration, resource installation, and server setup. - - - - Learn how to create resources, use ESX APIs, and implement custom features with best practices. - - - - Common issues and solutions for ESX installation, configuration, and runtime problems. - - - -## Quick Start - -### Modern ESX Legacy Integration - -```lua --- Modern ESX initialization (ESX Legacy) -local ESX = exports['es_extended']:getSharedObject() - --- Using ox_lib for UI -local ox_lib = exports.ox_lib - --- Player management -local xPlayer = ESX.GetPlayerFromId(source) -- Server-side -local playerData = ESX.GetPlayerData() -- Client-side - --- Modern account operations -xPlayer.addMoney(1000) -- Add cash -xPlayer.addAccountMoney('bank', 5000) -- Add bank money -xPlayer.removeAccountMoney('bank', 500) -- Remove from bank -xPlayer.getAccount('bank').money -- Get bank balance - --- Inventory management with metadata -xPlayer.addInventoryItem('bread', 5, {quality = 100}) -local itemCount = xPlayer.getInventoryItem('water').count -xPlayer.removeInventoryItem('bread', 2) - --- Job system -xPlayer.setJob('police', 2) -- Set job with grade -if xPlayer.job.name == 'police' and xPlayer.job.grade >= 2 then - -- Lieutenant or higher -end - --- Using ox_lib for notifications -ox_lib:notify({ - title = 'ESX', - description = 'You received $1000', - type = 'success' -}) -``` - -### Modern UI with ox_lib - -```lua --- Context menu (replaces old esx_menu) -ox_lib:registerContext({ - id = 'my_menu', - title = 'Menu Title', - options = { - { - title = 'Option 1', - description = 'Description', - icon = 'hand', - onSelect = function() - -- Action - end - }, - { - title = 'Option 2', - arrow = true, - event = 'my:event' - } - } -}) - -ox_lib:showContext('my_menu') - --- Progress bar -ox_lib:progressBar({ - duration = 5000, - label = 'Crafting...', - useWhileDead = false, - canCancel = true, - disable = { - move = true, - car = true, - combat = true - } -}) - --- Text UI -ox_lib:showTextUI('[E] - Interact') -ox_lib:hideTextUI() -``` -RegisterNetEvent('esx:setJob', function(job) - ESX.PlayerData.job = job - -- Update UI/permissions -end) - --- Player loaded (server) -AddEventHandler('esx:playerLoaded', function(playerId, xPlayer) - -- Server-side player initialization -end) -``` - -## Architecture - -ESX follows a modular architecture where each system is handled by separate resources: - -- **es_extended** - Core framework -- **esx_society** - Organization management -- **esx_datastore** - Data persistence -- **esx_menu_*** - UI menu systems -- **Job Resources** - Police, ambulance, mechanic jobs -- **Economy Resources** - Shops, banks, real estate - -## Community - -- [ESX Legacy GitHub](https://github.com/esx-framework/esx-legacy) -- [Official Documentation](https://documentation.esx-framework.org/) -- [Community Discord](https://discord.gg/esx) -- [FiveM Forums](https://forum.cfx.re/c/development/esx/22) - - - ESX Legacy is the official successor to the original ESX framework, featuring improved performance, better security, and active maintenance. - - - - Always test ESX resources and modifications on a development server before deploying to production. - + +We are working hard to get the full ESX documentation done as soon as possible! + \ No newline at end of file diff --git a/content/docs/frameworks/esx/setup.mdx b/content/docs/frameworks/esx/setup.mdx index 9e4d5d8..f3519cd 100644 --- a/content/docs/frameworks/esx/setup.mdx +++ b/content/docs/frameworks/esx/setup.mdx @@ -4,437 +4,8 @@ description: Complete guide to setting up and installing ESX framework. icon: "Download" --- -This guide covers the complete installation and initial setup of ESX framework for your FiveM server. +import { InfoBanner } from '@ui/components/mdx-components'; -## Prerequisites - -Before installing ESX, ensure you have: - -- **FiveM Server**: A properly configured FiveM server -- **MySQL Database**: MySQL 8.0+ or MariaDB 10.6+ -- **Git**: For cloning repositories -- **Basic Command Line Knowledge**: Comfort with terminal commands - -## Installation Methods - -### Method 1: ESX Legacy (Recommended) - -ESX Legacy is the modern, updated version of ESX with improved performance and security. - -#### 1. Database Setup - -Create a new MySQL database: - -```sql -CREATE DATABASE esx_server CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -CREATE USER 'esx_user'@'localhost' IDENTIFIED BY 'your_secure_password'; -GRANT ALL PRIVILEGES ON esx_server.* TO 'esx_user'@'localhost'; -FLUSH PRIVILEGES; -``` - -#### 2. Server Structure - -Create the proper directory structure: - -```bash -# Navigate to your server's resources folder -cd /path/to/your/server/resources - -# Create ESX folder structure -mkdir -p [esx] -mkdir -p [standalone] -``` - -#### 3. Core Framework Installation - -```bash -# Clone ESX Legacy core -cd [esx] -git clone https://github.com/esx-framework/esx_core.git -git clone https://github.com/esx-framework/es_extended.git - -# Essential resources -git clone https://github.com/esx-framework/esx_multicharacter.git -git clone https://github.com/esx-framework/esx_menu_default.git -git clone https://github.com/esx-framework/esx_menu_dialog.git -git clone https://github.com/esx-framework/esx_menu_list.git -git clone https://github.com/esx-framework/esx_notify.git -git clone https://github.com/esx-framework/esx_textui.git -git clone https://github.com/esx-framework/esx_context.git - -# Basic gameplay resources -git clone https://github.com/esx-framework/esx_basicneeds.git -git clone https://github.com/esx-framework/esx_billing.git -git clone https://github.com/esx-framework/esx_society.git -git clone https://github.com/esx-framework/esx_datastore.git -git clone https://github.com/esx-framework/esx_addonaccount.git -git clone https://github.com/esx-framework/esx_addoninventory.git - -# Jobs -git clone https://github.com/esx-framework/esx_ambulancejob.git -git clone https://github.com/esx-framework/esx_policejob.git -git clone https://github.com/esx-framework/esx_mechanicjob.git - -# Properties and vehicles -git clone https://github.com/esx-framework/esx_property.git -git clone https://github.com/esx-framework/esx_vehicleshop.git -git clone https://github.com/esx-framework/esx_lscustom.git -``` - -#### 4. Database Dependencies - -Install required database resources: - -```bash -# Navigate to standalone folder -cd ../[standalone] - -# Clone database connector (choose one) -git clone https://github.com/overextended/oxmysql.git -# OR -git clone https://github.com/GHMatti/ghmattimysql.git mysql-async -``` - -#### 5. Database Import - -Import ESX database structure: - -```bash -# Import base database -mysql -u esx_user -p esx_server < [esx]/es_extended/installation/esx_legacy.sql - -# Import additional resource databases -mysql -u esx_user -p esx_server < [esx]/esx_society/installation/esx_society.sql -mysql -u esx_user -p esx_server < [esx]/esx_datastore/installation/esx_datastore.sql -mysql -u esx_user -p esx_server < [esx]/esx_addonaccount/installation/esx_addonaccount.sql -mysql -u esx_user -p esx_server < [esx]/esx_addoninventory/installation/esx_addoninventory.sql -mysql -u esx_user -p esx_server < [esx]/esx_billing/installation/esx_billing.sql -mysql -u esx_user -p esx_server < [esx]/esx_vehicleshop/installation/esx_vehicleshop.sql -mysql -u esx_user -p esx_server < [esx]/esx_lscustom/installation/esx_lscustom.sql -# Continue for other resources with database requirements -``` - -### Method 2: ESX Template (Alternative) - -For a quicker start, use the ESX server template: - -```bash -# Clone the complete template -git clone https://github.com/esx-framework/esx-serverdumps.git - -# This includes: -# - Pre-configured server structure -# - All essential resources -# - Database files -# - Basic configuration -``` - -## Configuration - -### 1. Database Connection - -Configure your database connection in `es_extended/config.lua`: - -```lua -Config = {} -Config.Locale = 'en' - -Config.Accounts = { - bank = _U('account_bank'), - black_money = _U('account_black_money'), - money = _U('account_money') -} - -Config.StartingAccountMoney = { - bank = 50000, - black_money = 0, - money = 5000 -} - -Config.StartingInventoryItems = false -- Set to false to disable - -Config.DefaultSpawn = {x = -269.4, y = -955.3, z = 31.22, h = 205.8} - -Config.EnablePlayerManagement = true -Config.EnableSocietyOwnedVehicles = false -Config.EnableLicenses = false -Config.EnableJailAccount = false -Config.EnablePVP = true -Config.MaxWeight = 24 -Config.PaycheckInterval = 7 * 60000 -Config.EnableDebug = false -``` - -### 2. Server.cfg Configuration - -Add ESX resources to your `server.cfg`: - -```cfg -# Database -ensure oxmysql -# OR ensure mysql-async - -# ESX Legacy Framework -ensure es_extended - -# ESX Core Resources -ensure esx_menu_default -ensure esx_menu_dialog -ensure esx_menu_list -ensure esx_notify -ensure esx_textui -ensure esx_context - -# ESX Base Resources -ensure esx_datastore -ensure esx_addonaccount -ensure esx_addoninventory -ensure esx_society -ensure esx_billing - -# Character System -ensure esx_multicharacter - -# Basic Needs -ensure esx_basicneeds - -# Jobs -ensure esx_policejob -ensure esx_ambulancejob -ensure esx_mechanicjob - -# Vehicles & Properties -ensure esx_vehicleshop -ensure esx_lscustom -ensure esx_property - -# Database connection string -set mysql_connection_string "mysql://esx_user:your_secure_password@localhost/esx_server?charset=utf8mb4" - -# Server configuration -set sv_hostname "My ESX Server" -set sv_maxclients 32 -set server_description "An ESX FiveM Server" - -# ESX specific -set es_enableCustomData 1 -set esx_multicharacter_enabled true - -# Licensing -sv_licenseKey "your_license_key_here" -``` - -### 3. Resource Loading Order - -Proper loading order is crucial for ESX: - -```cfg -# 1. Database connector -ensure oxmysql - -# 2. Core framework -ensure es_extended - -# 3. Menu systems -ensure esx_menu_default -ensure esx_menu_dialog -ensure esx_menu_list - -# 4. UI Systems -ensure esx_notify -ensure esx_textui -ensure esx_context - -# 5. Base systems -ensure esx_datastore -ensure esx_addonaccount -ensure esx_addoninventory -ensure esx_society - -# 6. Character system -ensure esx_multicharacter - -# 7. Core gameplay -ensure esx_basicneeds -ensure esx_billing - -# 8. Jobs -ensure esx_policejob -ensure esx_ambulancejob -ensure esx_mechanicjob - -# 9. Vehicles & Properties -ensure esx_vehicleshop -ensure esx_lscustom -ensure esx_property - -# 10. Custom resources (add your custom resources last) -``` - -## Verification & Testing - -### 1. Server Startup - -Start your server and verify ESX loads properly: - -```bash -# Check the console for errors -# Look for messages like: -# [es_extended] ESX Started! -# [esx_multicharacter] Multicharacter Started! -``` - -### 2. Database Verification - -Check that database tables were created: - -```sql -USE esx_server; -SHOW TABLES; - --- You should see tables like: --- users --- vehicles --- user_accounts --- jobs --- job_grades --- etc. -``` - -### 3. In-Game Testing - -1. **Connect to Server**: Join your server -2. **Character Creation**: Test the multicharacter system -3. **Basic Functions**: Try commands like `/me`, `/job`, `/money` -4. **Job System**: Test changing jobs with `/setjob [job] [grade]` - -## Common Installation Issues - -### Database Connection Errors - -**Error**: `Failed to execute query: Access denied` - -**Solution**: -```sql --- Recreate user with proper permissions -DROP USER 'esx_user'@'localhost'; -CREATE USER 'esx_user'@'localhost' IDENTIFIED BY 'your_password'; -GRANT ALL PRIVILEGES ON esx_server.* TO 'esx_user'@'localhost'; -FLUSH PRIVILEGES; -``` - -### Resource Loading Errors - -**Error**: `Resource [es_extended] couldn't be started` - -**Solutions**: -1. Check resource path: Ensure resources are in `[esx]` folder -2. Verify manifest: Check `fxmanifest.lua` syntax -3. Dependencies: Ensure database connector loads before es_extended - -### Shared Object Errors - -**Error**: `ESX object is nil` - -**Solution**: -```lua --- Use proper ESX initialization -ESX = exports['es_extended']:getSharedObject() - --- Alternative (legacy method) -ESX = nil -TriggerEvent('esx:getSharedObject', function(obj) ESX = obj end) -``` - -## Post-Installation Steps - -### 1. Admin Setup - -Add yourself as admin: - -```sql --- Method 1: Direct database -INSERT INTO users (identifier, name, accounts, job, job_grade, group) -VALUES ('license:your_license_here', 'Your Name', '{"bank":25000,"black_money":0,"money":5000}', 'admin', 0, 'admin'); - --- Method 2: In-game command (if you have permissions) -/setjob admin 0 -``` - -### 2. Basic Server Configuration - -Configure basic server settings: - -```lua --- In es_extended/config.lua -Config.DefaultSpawn = {x = -269.4, y = -955.3, z = 31.22, h = 205.8} -- Customize spawn location -Config.StartingAccountMoney = {bank = 10000, black_money = 0, money = 1000} -- Starting money -Config.PaycheckInterval = 7 * 60000 -- Paycheck every 7 minutes -``` - -### 3. Job Configuration - -Add or modify jobs in the database: - -```sql --- Add custom job -INSERT INTO jobs (name, label) VALUES ('taxi', 'Taxi Driver'); - --- Add job grades -INSERT INTO job_grades (job_name, grade, name, label, salary, skin_male, skin_female) -VALUES ('taxi', 0, 'driver', 'Driver', 200, '{}', '{}'); - -INSERT INTO job_grades (job_name, grade, name, label, salary, skin_male, skin_female) -VALUES ('taxi', 1, 'experienced', 'Experienced Driver', 300, '{}', '{}'); -``` - -### 4. Society Configuration - -Set up societies for jobs: - -```sql --- Create society for job -INSERT INTO addon_account (name, label, shared) -VALUES ('society_taxi', 'Taxi Company', 1); - -INSERT INTO datastore (name, label, shared) -VALUES ('society_taxi', 'Taxi Company', 1); - -INSERT INTO addon_inventory (name, label, shared) -VALUES ('society_taxi', 'Taxi Company', 1); -``` - -## ESX vs ESX Legacy - -### ESX Legacy Advantages - -- **Better Performance**: Optimized code and reduced resource usage -- **Security Improvements**: Better protection against exploits -- **Modern Code**: Updated to use newer FiveM features -- **Active Development**: Regular updates and bug fixes -- **Compatibility**: Better compatibility with modern resources - -### Migration from Old ESX - -If migrating from old ESX to ESX Legacy: - -1. **Backup Everything**: Database and server files -2. **Update Resources**: Replace old ESX resources with Legacy versions -3. **Database Migration**: Run migration scripts if provided -4. **Test Thoroughly**: Verify all functionality works - -## Next Steps - -After successful installation: - -1. **[ESX Development](/docs/frameworks/esx/development)** - Learn about ESX development -2. **[ESX Troubleshooting](/docs/frameworks/esx/troubleshooting)** - Common issues and solutions -3. **[Resource Management](/docs/frameworks/esx/resources)** - Managing and updating ESX resources - - - Congratulations! You now have a working ESX server. Take time to familiarize yourself with the framework before adding custom resources. - - - - Always backup your database and server files before making major changes or updates. - + +We are working hard to get the full ESX documentation done as soon as possible! + \ No newline at end of file diff --git a/content/docs/frameworks/esx/troubleshooting.mdx b/content/docs/frameworks/esx/troubleshooting.mdx index 9cee7e2..5017de0 100644 --- a/content/docs/frameworks/esx/troubleshooting.mdx +++ b/content/docs/frameworks/esx/troubleshooting.mdx @@ -4,737 +4,8 @@ description: Common issues and solutions for ESX framework. icon: "Bug" --- -This guide covers common issues encountered when working with ESX framework and their solutions. +import { InfoBanner } from '@ui/components/mdx-components'; -## Installation Issues - -### Database Connection Problems - -#### Error: "Access denied for user" - -**Symptoms:** -``` -[ERROR] Access denied for user 'esx_user'@'localhost' (using password: YES) -``` - -**Solutions:** - -1. **Check Database Credentials:** -```sql --- Verify user exists -SELECT User, Host FROM mysql.user WHERE User = 'esx_user'; - --- If user doesn't exist, create it -CREATE USER 'esx_user'@'localhost' IDENTIFIED BY 'your_password'; -GRANT ALL PRIVILEGES ON esx_server.* TO 'esx_user'@'localhost'; -FLUSH PRIVILEGES; -``` - -2. **Verify Connection String:** -```cfg -# In server.cfg, ensure connection string is correct -set mysql_connection_string "mysql://esx_user:your_password@localhost/esx_server?charset=utf8mb4" -``` - -3. **Check MySQL Service:** -```bash -# Windows -net start mysql80 - -# Linux -sudo systemctl start mysql -sudo systemctl status mysql -``` - -#### Error: "Database 'esx_server' doesn't exist" - -**Solution:** -```sql -CREATE DATABASE esx_server CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -``` - -### Resource Loading Issues - -#### Error: "Resource [es_extended] couldn't be started" - -**Symptoms:** -``` -[ERROR] Could not load resource es_extended -[ERROR] Failed to start resource es_extended -``` - -**Solutions:** - -1. **Check Resource Path:** -``` -resources/ -├── [esx]/ -│ └── es_extended/ # Should be here -├── [standalone]/ -└── [voice]/ -``` - -2. **Verify fxmanifest.lua:** -```lua --- Check for syntax errors in fxmanifest.lua -fx_version 'cerulean' -game 'gta5' --- Ensure proper structure -``` - -3. **Check Dependencies:** -```cfg -# Ensure database connector loads before es_extended -ensure oxmysql -ensure es_extended -``` - -#### Error: "ESX object is nil" - -**Symptoms:** -- Scripts can't access ESX functions -- Error: "attempt to index a nil value (global 'ESX')" - -**Solutions:** - -1. **Proper ESX Initialization:** -```lua --- Modern method (recommended) -ESX = exports['es_extended']:getSharedObject() - --- Legacy method (still works) -ESX = nil -TriggerEvent('esx:getSharedObject', function(obj) ESX = obj end) - --- Using imports (ESX Legacy) --- Add @es_extended/imports.lua to shared_scripts -``` - -2. **Wait for ESX to Load:** -```lua --- If getting nil, wait for ESX -ESX = nil -Citizen.CreateThread(function() - while ESX == nil do - TriggerEvent('esx:getSharedObject', function(obj) ESX = obj end) - Citizen.Wait(0) - end -end) -``` - -### Database Schema Issues - -#### Error: "Table doesn't exist" - -**Symptoms:** -- MySQL errors about missing tables -- Resources fail to start - -**Solutions:** - -1. **Import Missing Tables:** -```bash -# Import base ESX schema -mysql -u esx_user -p esx_server < [esx]/es_extended/installation/esx_legacy.sql - -# Import resource-specific tables -mysql -u esx_user -p esx_server < [esx]/esx_society/installation/esx_society.sql -mysql -u esx_user -p esx_server < [esx]/esx_datastore/installation/esx_datastore.sql -``` - -2. **Check Required Tables:** -```sql --- Verify essential tables exist -SHOW TABLES; - --- Should include: --- users --- jobs --- job_grades --- vehicles --- owned_vehicles --- user_accounts --- user_inventory -``` - -## Player Data Issues - -### Player Data Not Loading - -#### Error: "Player data is nil" - -**Symptoms:** -- Player spawns but has no data -- Commands don't work -- Money/job shows as nil - -**Solutions:** - -1. **Check Player Loading Events:** -```lua --- Client-side -RegisterNetEvent('esx:playerLoaded', function(xPlayer) - ESX.PlayerData = xPlayer - print('Player data loaded:', json.encode(xPlayer)) -end) -``` - -2. **Verify Database Schema:** -```sql --- Check if users table exists and has data -SELECT COUNT(*) FROM users; -DESCRIBE users; -``` - -3. **Check for Database Corruption:** -```sql --- Look for corrupted player data -SELECT identifier, name, LENGTH(accounts) as accounts_length -FROM users -WHERE accounts IS NULL OR accounts = '' OR JSON_VALID(accounts) = 0; -``` - -#### Error: "Player already exists" - -**Symptoms:** -- Cannot create new character -- Gets stuck on character creation - -**Solutions:** - -1. **Check for Duplicate Identifiers:** -```sql --- Find duplicate identifiers -SELECT identifier, COUNT(*) as count -FROM users -GROUP BY identifier -HAVING count > 1; - --- Remove duplicates (keep most recent) -DELETE u1 FROM users u1 -INNER JOIN users u2 -WHERE u1.id < u2.id AND u1.identifier = u2.identifier; -``` - -2. **Clear Character Data:** -```sql --- Remove specific player data -DELETE FROM users WHERE identifier = 'license:your_license_here'; -``` - -### Money/Account Issues - -#### Error: "Money is not updating" - -**Solutions:** - -1. **Check Account Format:** -```sql --- Verify accounts are valid JSON -SELECT identifier, accounts, JSON_VALID(accounts) as is_valid -FROM users -WHERE JSON_VALID(accounts) = 0; - --- Fix invalid account data -UPDATE users -SET accounts = '{"bank":5000,"black_money":0,"money":500}' -WHERE JSON_VALID(accounts) = 0; -``` - -2. **Check Money Functions:** -```lua --- Server-side: Proper money handling -local xPlayer = ESX.GetPlayerFromId(source) -if xPlayer then - local currentMoney = xPlayer.getMoney() - xPlayer.addMoney(1000) - print('Money before:', currentMoney, 'After:', xPlayer.getMoney()) -end -``` - -#### Error: "Account type doesn't exist" - -**Solutions:** - -1. **Check Account Configuration:** -```lua --- In es_extended/config.lua -Config.Accounts = { - bank = _U('account_bank'), - black_money = _U('account_black_money'), - money = _U('account_money') -} -``` - -2. **Add Missing Account Types:** -```sql --- If using custom accounts, ensure they're configured properly -``` - -### Job Issues - -#### Error: "Job doesn't exist" - -**Solutions:** - -1. **Verify Job in Database:** -```sql --- Check if job exists -SELECT * FROM jobs WHERE name = 'your_job_name'; - --- Add missing job -INSERT INTO jobs (name, label) VALUES ('mechanic', 'Mechanic'); - --- Add job grades -INSERT INTO job_grades (job_name, grade, name, label, salary, skin_male, skin_female) VALUES -('mechanic', 0, 'trainee', 'Trainee', 200, '{}', '{}'), -('mechanic', 1, 'mechanic', 'Mechanic', 400, '{}', '{}'); -``` - -2. **Check Job Assignment:** -```lua --- Server-side: Proper job setting -local xPlayer = ESX.GetPlayerFromId(source) -if xPlayer then - xPlayer.setJob('mechanic', 1) -end -``` - -## Inventory Issues - -### Inventory Not Working - -#### Error: "Items not showing" - -**Solutions:** - -1. **Check Inventory Table:** -```sql --- Verify user_inventory table exists -DESCRIBE user_inventory; - --- Check for data -SELECT * FROM user_inventory LIMIT 5; -``` - -2. **Verify Item Registration:** -```lua --- Check if items are properly registered in database --- Items should be in the items table or configured in resources -``` - -3. **Check Weight System:** -```lua --- Ensure weight calculations work -local xPlayer = ESX.GetPlayerFromId(source) -local currentWeight = xPlayer.getWeight() -local maxWeight = xPlayer.maxWeight -print('Weight:', currentWeight, '/', maxWeight) -``` - -#### Error: "Cannot add item" - -**Solutions:** - -1. **Check Item Limits:** -```lua --- Verify item can be carried -local xPlayer = ESX.GetPlayerFromId(source) -if xPlayer.canCarryItem('bread', 5) then - xPlayer.addInventoryItem('bread', 5) -else - print('Cannot carry item - weight or space limit') -end -``` - -2. **Check Item Configuration:** -```sql --- Verify item exists in items table (if using database items) -SELECT * FROM items WHERE name = 'your_item_name'; -``` - -### Vehicle Issues - -#### Error: "Vehicles not spawning" - -**Solutions:** - -1. **Check Vehicle Database:** -```sql --- Verify owned_vehicles table -DESCRIBE owned_vehicles; - --- Check vehicle data format -SELECT owner, plate, vehicle FROM owned_vehicles LIMIT 5; -``` - -2. **Check Vehicle Model:** -```lua --- Verify vehicle model exists -local model = GetHashKey('adder') -if not IsModelInCdimage(model) then - print('Vehicle model not found') - return -end -``` - -3. **Check Vehicle Ownership:** -```sql --- Verify player owns the vehicle -SELECT * FROM owned_vehicles WHERE owner = 'license:your_license_here'; -``` - -## Society/Job Issues - -### Society Not Working - -#### Error: "Society account not found" - -**Solutions:** - -1. **Check Society Tables:** -```sql --- Verify society tables exist -SHOW TABLES LIKE 'addon_%'; - --- Should show: --- addon_account --- addon_inventory --- datastore -``` - -2. **Create Missing Society:** -```sql --- Create society account -INSERT INTO addon_account (name, label, shared) VALUES ('society_police', 'Police', 1); -INSERT INTO datastore (name, label, shared) VALUES ('society_police', 'Police', 1); -INSERT INTO addon_inventory (name, label, shared) VALUES ('society_police', 'Police', 1); -``` - -3. **Check Society Access:** -```lua --- Server-side: Access society account -TriggerEvent('esx_addonaccount:getSharedAccount', 'society_police', function(account) - if account then - print('Society balance:', account.money) - else - print('Society account not found') - end -end) -``` - -## Menu System Issues - -### Menus Not Working - -#### Error: "Menu doesn't open" - -**Solutions:** - -1. **Check Menu Resources:** -```cfg -# Ensure menu resources are loaded -ensure esx_menu_default -ensure esx_menu_dialog -ensure esx_menu_list -``` - -2. **Check Menu Dependencies:** -```lua --- Verify menu system is available -if ESX.UI.Menu then - -- Menu system loaded -else - print('Menu system not available') -end -``` - -3. **Test Basic Menu:** -```lua --- Client-side: Test menu opening -ESX.UI.Menu.Open('default', GetCurrentResourceName(), 'test_menu', { - title = 'Test Menu', - align = 'top-left', - elements = { - {label = 'Option 1', value = 'option1'}, - {label = 'Option 2', value = 'option2'} - } -}, function(data, menu) - -- Handle selection - menu.close() -end, function(data, menu) - -- Handle close - menu.close() -end) -``` - -## Notification Issues - -#### Error: "Notifications not showing" - -**Solutions:** - -1. **Check Notification Resource:** -```cfg -# Ensure notification resource is loaded -ensure esx_notify -``` - -2. **Test Notifications:** -```lua --- Client-side: Test notification -ESX.ShowNotification('Test message') - --- Server-side: Send to player -local xPlayer = ESX.GetPlayerFromId(source) -xPlayer.showNotification('Test message') -``` - -## Performance Issues - -### High Resource Usage - -#### Server Performance Problems - -**Symptoms:** -- High CPU usage -- Lag/stuttering -- Thread hitches - -**Solutions:** - -1. **Identify Resource Usage:** -``` -# In server console -resmon - -# Look for resources using high CPU/memory -``` - -2. **Optimize Database Queries:** -```lua --- Bad: Multiple queries in loop -for i = 1, #players do - MySQL.Async.fetchSingle('SELECT * FROM users WHERE identifier = @identifier', { - ['@identifier'] = players[i] - }, function(result) - -- Process result - end) -end - --- Good: Single query with IN clause -local identifiers = {} -for i = 1, #players do - table.insert(identifiers, players[i]) -end - -MySQL.Async.fetchAll('SELECT * FROM users WHERE identifier IN (@identifiers)', { - ['@identifiers'] = table.concat(identifiers, "','") -}, function(results) - -- Process all results at once -end) -``` - -3. **Reduce Timer Frequency:** -```lua --- Bad: Too frequent -Citizen.CreateThread(function() - while true do - Citizen.Wait(0) -- Runs every frame - -- Heavy operation - end -end) - --- Good: Reasonable frequency -Citizen.CreateThread(function() - while true do - Citizen.Wait(5000) -- Runs every 5 seconds - -- Heavy operation - end -end) -``` - -### Memory Leaks - -#### Increasing Memory Usage - -**Solutions:** - -1. **Check for Event Listeners:** -```lua --- Ensure proper cleanup -AddEventHandler('onResourceStop', function(resourceName) - if GetCurrentResourceName() == resourceName then - -- Cleanup code here - ESX = nil - end -end) -``` - -2. **Clear Player References:** -```lua --- Clear player data on disconnect -AddEventHandler('esx:playerDropped', function(playerId, reason) - -- Clear any stored player references - if playerCache[playerId] then - playerCache[playerId] = nil - end -end) -``` - -## Debug Techniques - -### Logging and Debugging - -#### Enable Debug Mode - -```lua --- In config.lua -Config.Debug = true - --- Debug function -local function DebugPrint(...) - if Config.Debug then - print('^3[DEBUG]^7', ...) - end -end - --- Usage -DebugPrint('Player data:', json.encode(ESX.GetPlayerData())) -``` - -#### Database Debugging - -```lua --- Check database operations -local function SafeQuery(query, parameters, callback) - MySQL.Async.fetchAll(query, parameters, function(result) - if result then - callback(result) - else - print('^1[ERROR]^7 Database query failed:', query) - end - end) -end -``` - -### Console Commands for Debugging - -```lua --- Server-side debug commands -RegisterCommand('esx-debug-player', function(source, args) - local playerId = tonumber(args[1]) or source - local xPlayer = ESX.GetPlayerFromId(playerId) - - if xPlayer then - print('=== PLAYER DEBUG ===') - print('Identifier:', xPlayer.identifier) - print('Name:', xPlayer.getName()) - print('Job:', xPlayer.job.name, 'Grade:', xPlayer.job.grade) - print('Money:', xPlayer.getMoney()) - print('Bank:', xPlayer.getAccount('bank').money) - else - print('Player not found') - end -end, true) - -RegisterCommand('esx-debug-db', function(source, args) - local playerId = tonumber(args[1]) or source - local xPlayer = ESX.GetPlayerFromId(playerId) - - if xPlayer then - MySQL.Async.fetchSingle('SELECT * FROM users WHERE identifier = @identifier', { - ['@identifier'] = xPlayer.identifier - }, function(result) - if result then - print('=== DATABASE DEBUG ===') - print('Raw data:', json.encode(result)) - end - end) - end -end, true) -``` - -## Common Error Messages - -### Script Errors - -| Error | Cause | Solution | -|-------|-------|----------| -| `attempt to index a nil value (global 'ESX')` | ESX not initialized | Proper ESX initialization | -| `attempt to call a nil value (method 'getMoney')` | xPlayer is nil | Check if player exists | -| `bad argument #1 to 'pairs'` | Trying to iterate nil value | Check if table exists | -| `attempt to index a nil value (field 'job')` | Player data not loaded | Wait for esx:playerLoaded | - -### Database Errors - -| Error | Cause | Solution | -|-------|-------|----------| -| `Table 'esx_server.users' doesn't exist` | Missing database tables | Import SQL files | -| `Column 'accounts' cannot be null` | Invalid data insertion | Provide default values | -| `Duplicate entry` | Trying to insert duplicate key | Use proper constraints | -| `Data too long for column` | Value exceeds column length | Increase column size | - -## Prevention Best Practices - -### Code Quality - -1. **Always Check for Nil:** -```lua -local xPlayer = ESX.GetPlayerFromId(source) -if not xPlayer then return end -``` - -2. **Use Error Handling:** -```lua -MySQL.Async.fetchSingle('SELECT * FROM users WHERE identifier = @identifier', { - ['@identifier'] = identifier -}, function(result) - if result then - -- Process result - else - print('No player found with identifier:', identifier) - end -end) -``` - -3. **Validate Inputs:** -```lua -RegisterNetEvent('myresource:server:action', function(data) - local xPlayer = ESX.GetPlayerFromId(source) - if not xPlayer then return end - - if not data or type(data) ~= 'table' then - return - end - - if not data.amount or type(data.amount) ~= 'number' or data.amount <= 0 then - return - end - - -- Process valid data -end) -``` - -### Testing - -1. **Test with Multiple Players** -2. **Test Resource Restart** -3. **Test Database Disconnection** -4. **Test Invalid Inputs** -5. **Monitor Resource Usage** - -### Monitoring - -1. **Regular Database Backups** -2. **Monitor Server Performance** -3. **Check Error Logs** -4. **Update Dependencies** - - - Regular maintenance and monitoring can prevent most issues before they become problems. - - - - Always test changes on a development server before applying to production. - + +We are working hard to get the full ESX documentation done as soon as possible! + \ No newline at end of file diff --git a/content/docs/frameworks/index.mdx b/content/docs/frameworks/index.mdx index cdf449c..8952117 100644 --- a/content/docs/frameworks/index.mdx +++ b/content/docs/frameworks/index.mdx @@ -4,6 +4,8 @@ description: An overview of popular frameworks used in FiveM development. icon: "LayersUnion" --- +import { FeatureList, CheckList, QuickLinks, DefinitionList, CommandCard, CommandTable, PropertyCard, IconGrid, Shortcut, StatusBadge, TroubleshootingCard, PermissionTable, ConfigBlock, StepList, ComparisonTable } from '@ui/components/mdx-components' + # Frameworks Overview FiveM supports various frameworks that provide structure, tools, and resources for server development. This section covers the most popular frameworks and how to use them effectively. @@ -12,11 +14,11 @@ FiveM supports various frameworks that provide structure, tools, and resources f - The most widely used framework for FiveM, providing a complete roleplay foundation. + The most widely used framework for FiveM, providing a complete roleplay foundation with modern updates. - A modern, modular framework with extensive features for roleplay servers. + A modern, modular framework with extensive features and flexibility for roleplay servers. @@ -24,31 +26,59 @@ FiveM supports various frameworks that provide structure, tools, and resources f When selecting a framework for your FiveM server, consider: -1. **Community Support**: Larger communities typically mean more resources and faster support -2. **Documentation**: Well-documented frameworks are easier to learn and extend -3. **Performance**: Some frameworks are more optimized than others -4. **Features**: Different frameworks excel in different areas -5. **Development Activity**: Active development means bugs are fixed and features are added + ## Framework Components Most FiveM frameworks include: -- **Core Systems**: Player management, inventory, jobs, etc. -- **Database Integration**: For persistent data storage -- **UI Components**: For user interaction -- **Resource Management**: For handling server resources -- **API**: For extending functionality - -## Framework Integration + -Regardless of which framework you choose, follow these best practices: +## Framework Integration Best Practices -1. **Follow Documentation**: Use official guides when available -2. **Respect Dependencies**: Understand how resources depend on each other -3. **Maintain Compatibility**: Test thoroughly when upgrading -4. **Contribute**: Report bugs and contribute improvements -5. **Share Knowledge**: Document your solutions for others + This section provides guides for the most popular frameworks, but many of the concepts are transferable between frameworks. diff --git a/content/docs/frameworks/qbcore/development.mdx b/content/docs/frameworks/qbcore/development.mdx index 2d95564..3f0b139 100644 --- a/content/docs/frameworks/qbcore/development.mdx +++ b/content/docs/frameworks/qbcore/development.mdx @@ -4,858 +4,8 @@ description: Complete guide to developing resources for QBCore framework. icon: "Code" --- -This guide covers everything you need to know about developing custom resources for QBCore framework. +import { InfoBanner } from '@ui/components/mdx-components'; -## Development Environment Setup - -### Prerequisites - -- **Code Editor**: VS Code with Lua extensions recommended -- **Git**: For version control -- **Database Tool**: HeidiSQL, phpMyAdmin, or similar -- **QBCore Server**: Running development server - -### Recommended VS Code Extensions - -```json -{ - "recommendations": [ - "sumneko.lua", - "actboy168.lua-debug", - "keyring.lua", - "koihik.vscode-lua-format", - "trixnz.vscode-lua" - ] -} -``` - -### Development Server Setup - -Create a separate development server configuration: - -```cfg -# server-dev.cfg -set sv_hostname "QBCore Development Server" -set sv_maxclients 4 -sv_licenseKey "your_license_key" - -# Developer permissions -add_ace group.admin command allow -add_ace group.admin resource allow -add_principal identifier.steam:your_steam_id group.admin - -# Fast restart for development -sv_scriptHookAllowed 1 -set developer_mode true -``` - -## Resource Structure - -### Standard QBCore Resource Structure - -``` -qb-resourcename/ -├── client/ -│ ├── main.lua -│ ├── events.lua -│ └── utils.lua -├── server/ -│ ├── main.lua -│ ├── events.lua -│ └── callbacks.lua -├── shared/ -│ ├── config.lua -│ ├── items.lua -│ └── locale.lua -├── html/ # For NUI resources -│ ├── index.html -│ ├── style.css -│ └── script.js -├── database/ # Database files -│ └── qb-resourcename.sql -├── locales/ # Translation files -│ ├── en.lua -│ ├── es.lua -│ └── fr.lua -├── fxmanifest.lua -└── README.md -``` - -### fxmanifest.lua Template - -```lua -fx_version 'cerulean' -game 'gta5' - -author 'Your Name ' -description 'QBCore Resource Description' -version '1.0.0' -repository 'https://github.com/yourusername/qb-resourcename' - -shared_scripts { - '@qb-core/shared/locale.lua', - 'locales/en.lua', - 'shared/*.lua' -} - -client_scripts { - 'client/*.lua' -} - -server_scripts { - '@oxmysql/lib/MySQL.lua', - 'server/*.lua' -} - -ui_page 'html/index.html' -- For NUI resources - -files { - 'html/index.html', - 'html/style.css', - 'html/script.js' -} - -lua54 'yes' - -dependencies { - 'qb-core', - 'oxmysql' -} - -provide 'qb-resourcename' -- Optional: for resource replacement -``` - -## Core Integration - -### Getting QBCore Object - -```lua --- Client-side -local QBCore = exports['qb-core']:GetCoreObject() - --- Server-side -local QBCore = exports['qb-core']:GetCoreObject() - --- Alternative method (deprecated but still works) -QBCore = nil -CreateThread(function() - while QBCore == nil do - TriggerEvent('QBCore:GetObject', function(obj) QBCore = obj end) - Wait(200) - end -end) -``` - -### Player Data Management - -#### Getting Player Data - -```lua --- Server-side -local Player = QBCore.Functions.GetPlayer(source) -if Player then - local playerData = Player.PlayerData - local citizenId = Player.PlayerData.citizenid - local job = Player.PlayerData.job - local money = Player.PlayerData.money -end - --- Client-side -local PlayerData = QBCore.Functions.GetPlayerData() -if PlayerData then - local job = PlayerData.job - local money = PlayerData.money -end -``` - -#### Player Events - -```lua --- Client-side: Listen for player data updates -RegisterNetEvent('QBCore:Client:OnPlayerLoaded', function() - PlayerData = QBCore.Functions.GetPlayerData() - -- Initialize your resource after player loads -end) - -RegisterNetEvent('QBCore:Client:OnJobUpdate', function(JobInfo) - PlayerData.job = JobInfo - -- Handle job updates -end) - -RegisterNetEvent('QBCore:Client:OnMoneyChange', function(moneyType, amount, operation) - -- Handle money changes -end) - --- Server-side: Player management -AddEventHandler('QBCore:Server:OnPlayerLoaded', function(Player) - -- Handle player loading -end) - -AddEventHandler('playerDropped', function() - local src = source - local Player = QBCore.Functions.GetPlayer(src) - if Player then - -- Handle player disconnect - end -end) -``` - -## Database Integration - -### Using oxmysql - -```lua --- SELECT query -MySQL.Async.fetchAll('SELECT * FROM players WHERE job = ?', {jobName}, function(result) - if result[1] then - -- Handle results - end -end) - --- SELECT single row -MySQL.Async.fetchSingle('SELECT * FROM players WHERE citizenid = ?', {citizenId}, function(result) - if result then - -- Handle single result - end -end) - --- INSERT query -MySQL.Async.execute('INSERT INTO my_table (citizenid, data) VALUES (?, ?)', { - citizenId, - json.encode(data) -}, function(affectedRows) - if affectedRows > 0 then - -- Success - end -end) - --- UPDATE query -MySQL.Async.execute('UPDATE players SET money = ? WHERE citizenid = ?', { - json.encode(money), - citizenId -}, function(affectedRows) - -- Handle update -end) -``` - -### Modern oxmysql (Promise-based) - -```lua --- Using promises (recommended) -local result = MySQL.query.await('SELECT * FROM players WHERE job = ?', {jobName}) -if result[1] then - -- Handle results -end - --- With error handling -local success, result = pcall(MySQL.query.await, 'SELECT * FROM players WHERE citizenid = ?', {citizenId}) -if success and result[1] then - -- Handle success -else - print('Database query failed') -end -``` - -## Item System - -### Creating Custom Items - -Add items to `qb-core/shared/items.lua`: - -```lua -QBShared.Items = { - -- Existing items... - - ['my_custom_item'] = { - name = 'my_custom_item', - label = 'My Custom Item', - weight = 500, - type = 'item', - image = 'my_custom_item.png', - unique = false, - useable = true, - shouldClose = true, - combinable = nil, - description = 'This is my custom item description' - }, - - ['weapon_custom'] = { - name = 'weapon_custom', - label = 'Custom Weapon', - weight = 2000, - type = 'weapon', - ammotype = 'AMMO_PISTOL', - image = 'weapon_custom.png', - unique = true, - useable = false, - description = 'A custom weapon' - } -} -``` - -### Item Usage Events - -```lua --- Server-side: Register useable item -QBCore.Functions.CreateUseableItem('my_custom_item', function(source, item) - local Player = QBCore.Functions.GetPlayer(source) - if Player then - -- Item usage logic - TriggerClientEvent('qb-myresource:client:useItem', source, item) - end -end) - --- Client-side: Handle item usage -RegisterNetEvent('qb-myresource:client:useItem', function(item) - -- Client-side item effects - QBCore.Functions.Notify('You used ' .. item.label, 'success') -end) -``` - -### Inventory Management - -```lua --- Server-side inventory functions -local Player = QBCore.Functions.GetPlayer(source) - --- Add item -Player.Functions.AddItem('my_item', 1, false, {quality = 100}) - --- Remove item -Player.Functions.RemoveItem('my_item', 1) - --- Get item -local item = Player.Functions.GetItemByName('my_item') -if item then - print('Player has ' .. item.amount .. ' of ' .. item.label) -end - --- Check if player has item -local hasItem = Player.Functions.GetItemByName('my_item') ~= nil -``` - -## Job System - -### Creating Custom Jobs - -Add jobs to `qb-core/shared/jobs.lua`: - -```lua -QBShared.Jobs = { - -- Existing jobs... - - ['mechanic'] = { - label = 'Mechanic', - defaultDuty = true, - offDutyPay = false, - grades = { - ['0'] = { - name = 'Trainee', - payment = 50 - }, - ['1'] = { - name = 'Mechanic', - payment = 75 - }, - ['2'] = { - name = 'Expert Mechanic', - payment = 100 - }, - ['3'] = { - name = 'Shop Supervisor', - payment = 125 - }, - ['4'] = { - name = 'Shop Owner', - isboss = true, - payment = 150 - }, - }, - } -} -``` - -### Job Management Functions - -```lua --- Server-side: Job management -local Player = QBCore.Functions.GetPlayer(source) - --- Set player job -Player.Functions.SetJob('mechanic', 2) - --- Check job permissions -if Player.PlayerData.job.name == 'police' and Player.PlayerData.job.grade.level >= 3 then - -- Allow police captain+ actions -end - --- Check if player is boss -if Player.PlayerData.job.isboss then - -- Boss-only actions -end - --- Client-side: Job checking -local PlayerData = QBCore.Functions.GetPlayerData() -if PlayerData.job.name == 'mechanic' then - -- Mechanic-specific functionality -end -``` - -## Vehicle System - -### Vehicle Management - -```lua --- Server-side: Vehicle functions -local Player = QBCore.Functions.GetPlayer(source) - --- Get player vehicles -local vehicles = MySQL.query.await('SELECT * FROM player_vehicles WHERE citizenid = ?', {Player.PlayerData.citizenid}) - --- Add vehicle to player -local vehicleData = { - citizenid = Player.PlayerData.citizenid, - vehicle = 'adder', - hash = GetHashKey('adder'), - mods = json.encode({}), - plate = 'ABC123', - garage = 'pillboxgarage', - fuel = 100, - engine = 1000.0, - body = 1000.0 -} - -MySQL.insert.await('INSERT INTO player_vehicles (citizenid, vehicle, hash, mods, plate, garage, fuel, engine, body) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)', { - vehicleData.citizenid, - vehicleData.vehicle, - vehicleData.hash, - vehicleData.mods, - vehicleData.plate, - vehicleData.garage, - vehicleData.fuel, - vehicleData.engine, - vehicleData.body -}) -``` - -### Vehicle Keys Integration - -```lua --- Give keys to player -TriggerEvent('qb-vehiclekeys:server:GiveVehicleKeys', source, plate) - --- Remove keys from player -TriggerEvent('qb-vehiclekeys:server:RemoveVehicleKeys', source, plate) - --- Client-side: Check if player has keys -local hasKeys = exports['qb-vehiclekeys']:HasKeys(plate) -``` - -## UI Development (NUI) - -### Basic NUI Setup - -```html - - - - - - - QBCore Resource UI - - - - - - - - -``` - -```css -/* html/style.css */ -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -body { - font-family: 'Arial', sans-serif; - background: transparent; - color: white; -} - -#container { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: 600px; - height: 400px; - background: rgba(0, 0, 0, 0.9); - border-radius: 10px; - border: 1px solid #333; -} - -#header { - display: flex; - justify-content: space-between; - align-items: center; - padding: 20px; - border-bottom: 1px solid #333; -} - -#close-btn { - background: #ff4757; - color: white; - border: none; - border-radius: 50%; - width: 30px; - height: 30px; - cursor: pointer; - font-size: 16px; -} - -#content { - padding: 20px; -} -``` - -```javascript -// html/script.js -$(document).ready(function() { - // Hide UI on ESC key - document.onkeyup = function(data) { - if (data.which == 27) { - closeUI(); - } - } - - // Close button click - $('#close-btn').click(function() { - closeUI(); - }); - - // Listen for messages from Lua - window.addEventListener('message', function(event) { - const data = event.data; - - switch(data.action) { - case 'open': - openUI(data.data); - break; - case 'close': - closeUI(); - break; - case 'updateData': - updateUI(data.data); - break; - } - }); -}); - -function openUI(data) { - $('#container').fadeIn(300); - $.post('https://qb-myresource/uiLoaded', JSON.stringify({})); -} - -function closeUI() { - $('#container').fadeOut(300); - $.post('https://qb-myresource/closeUI', JSON.stringify({})); -} - -function updateUI(data) { - // Update UI with new data -} -``` - -### Lua NUI Integration - -```lua --- Client-side NUI management -local isUIOpen = false - --- Open UI -function OpenUI(data) - if isUIOpen then return end - - isUIOpen = true - SetNuiFocus(true, true) - SendNUIMessage({ - action = 'open', - data = data - }) -end - --- Close UI -function CloseUI() - if not isUIOpen then return end - - isUIOpen = false - SetNuiFocus(false, false) - SendNUIMessage({ - action = 'close' - }) -end - --- NUI Callbacks -RegisterNUICallback('uiLoaded', function(data, cb) - -- UI has loaded - cb('ok') -end) - -RegisterNUICallback('closeUI', function(data, cb) - CloseUI() - cb('ok') -end) - --- Export functions for other resources -exports('OpenUI', OpenUI) -exports('CloseUI', CloseUI) -``` - -## Event System - -### Custom Events - -```lua --- Server-side events -RegisterNetEvent('qb-myresource:server:doSomething', function(data) - local src = source - local Player = QBCore.Functions.GetPlayer(src) - - if not Player then return end - - -- Validate data - if not data or not data.value then - return - end - - -- Process request - local success = processRequest(Player, data) - - -- Send response - TriggerClientEvent('qb-myresource:client:requestResult', src, success) -end) - --- Client-side events -RegisterNetEvent('qb-myresource:client:requestResult', function(success) - if success then - QBCore.Functions.Notify('Request successful!', 'success') - else - QBCore.Functions.Notify('Request failed!', 'error') - end -end) -``` - -### Callbacks - -```lua --- Server-side callback -QBCore.Functions.CreateCallback('qb-myresource:server:getData', function(source, cb, playerId) - local Player = QBCore.Functions.GetPlayer(playerId) - if Player then - cb(Player.PlayerData) - else - cb(false) - end -end) - --- Client-side callback usage -QBCore.Functions.TriggerCallback('qb-myresource:server:getData', function(playerData) - if playerData then - -- Use player data - end -end, GetPlayerServerId(PlayerId())) -``` - -## Commands - -### Creating Commands - -```lua --- Server-side command -QBCore.Commands.Add('mycommand', 'Command description', {{name = 'target', help = 'Target player ID'}}, true, function(source, args) - local Player = QBCore.Functions.GetPlayer(source) - if not Player then return end - - local targetId = tonumber(args[1]) - if not targetId then - TriggerClientEvent('QBCore:Notify', source, 'Invalid player ID', 'error') - return - end - - local TargetPlayer = QBCore.Functions.GetPlayer(targetId) - if not TargetPlayer then - TriggerClientEvent('QBCore:Notify', source, 'Player not found', 'error') - return - end - - -- Command logic here - TriggerClientEvent('QBCore:Notify', source, 'Command executed successfully', 'success') -end, 'admin') -- Permission level - --- Client-side command -RegisterCommand('clientcommand', function(source, args, rawCommand) - local PlayerData = QBCore.Functions.GetPlayerData() - if PlayerData.job.name == 'police' then - -- Police-only client command - end -end, false) -``` - -## Configuration Management - -### config.lua Template - -```lua -Config = {} - --- General settings -Config.Debug = false -Config.Locale = GetConvar('qb_locale', 'en') - --- Feature toggles -Config.EnableFeatureA = true -Config.EnableFeatureB = false - --- Timing settings -Config.Cooldowns = { - action1 = 5000, -- 5 seconds - action2 = 30000, -- 30 seconds -} - --- Job permissions -Config.JobPermissions = { - mechanic = { - repair = 0, -- Grade 0+ - advanced = 2, -- Grade 2+ - boss = 4 -- Grade 4+ - }, - police = { - arrest = 0, - impound = 1, - commander = 3 - } -} - --- Locations -Config.Locations = { - mechanic_shop = { - coords = vector3(123.45, 678.90, 12.34), - heading = 90.0, - radius = 2.0, - blip = { - sprite = 446, - color = 2, - scale = 0.8, - name = "Mechanic Shop" - } - } -} - --- Items and pricing -Config.Items = { - repair_kit = { - item = 'repair_kit', - price = 500, - requiredJob = 'mechanic' - } -} - --- Notifications -Config.Notifications = { - success_repair = 'Vehicle repaired successfully', - insufficient_funds = 'You don\'t have enough money', - wrong_job = 'You don\'t have the required job' -} -``` - -## Testing & Debugging - -### Debug Functions - -```lua --- Debug utility -local function DebugPrint(...) - if Config.Debug then - print('^3[QB-MYRESOURCE]^7', ...) - end -end - --- Server-side debugging -local function LogAction(player, action, data) - if Config.Debug then - print(string.format('^3[QB-MYRESOURCE]^7 Player: %s (%s) | Action: %s | Data: %s', - player.PlayerData.name, - player.PlayerData.citizenid, - action, - json.encode(data) - )) - end -end - --- Client-side debugging -local function DrawDebugText(text, x, y) - if Config.Debug then - SetTextFont(0) - SetTextProportional(1) - SetTextScale(0.0, 0.35) - SetTextDropshadow(0, 0, 0, 0, 255) - SetTextEdge(1, 0, 0, 0, 255) - SetTextDropShadow() - SetTextOutline() - SetTextEntry("STRING") - AddTextComponentString(text) - DrawText(x, y) - end -end -``` - -### Testing Checklist - -- [ ] Resource starts without errors -- [ ] Database connections work -- [ ] Player events trigger correctly -- [ ] Items can be used/given/removed -- [ ] UI opens/closes properly -- [ ] Commands execute with proper permissions -- [ ] No console errors or warnings -- [ ] Memory usage is reasonable -- [ ] Performance is acceptable - -## Best Practices - -### Code Organization - -1. **Separate Concerns**: Keep client, server, and shared code separate -2. **Use Callbacks**: For data requests between client and server -3. **Validate Everything**: Never trust client input -4. **Handle Errors**: Use pcall for database operations -5. **Performance**: Avoid unnecessary loops and timers - -### Security - -1. **Server Validation**: Always validate on the server -2. **Permission Checks**: Verify job/permissions before actions -3. **Rate Limiting**: Prevent spam/abuse -4. **Secure Events**: Use source validation - -### Performance - -1. **Efficient Queries**: Use proper database indexes -2. **Caching**: Cache frequently accessed data -3. **Resource Cleanup**: Clean up on resource stop -4. **Minimal UI**: Keep NUI lightweight - -This comprehensive development guide provides everything needed to create professional QBCore resources following best practices and framework conventions. + +We are working hard to get the full qbCore documentation done as soon as possible! + \ No newline at end of file diff --git a/content/docs/frameworks/qbcore/index.mdx b/content/docs/frameworks/qbcore/index.mdx index 94f9343..a40ec83 100644 --- a/content/docs/frameworks/qbcore/index.mdx +++ b/content/docs/frameworks/qbcore/index.mdx @@ -4,91 +4,8 @@ description: A comprehensive guide to using and developing for the QBCore framew icon: "Component" --- -QBCore is a modern, feature-rich framework for FiveM that provides a solid foundation for roleplay servers. It offers a comprehensive set of features, excellent documentation, and an active community. +import { InfoBanner } from '@ui/components/mdx-components'; -## Quick Navigation - - - - - - - -## Overview - -QBCore is designed as a successor to ESX, addressing many common pain points while providing enhanced functionality and performance. The framework is built around a modular design philosophy, making it highly extensible and customizable. - -### Key Features - -- **Player Management**: Multi-character support, data persistence, and permissions -- **Inventory System**: Advanced inventory with item metadata and attachments -- **Job System**: Comprehensive job framework with grades and permissions -- **Vehicle System**: Vehicle ownership, keys, and management -- **Property System**: Housing and property ownership -- **Business System**: Fully-featured business management -- **Phone System**: Integrated mobile phone with apps and messaging -- **Crafting System**: Item crafting and processing -- **Modern UI**: Clean and responsive user interfaces -- **Active Development**: Frequent updates and improvements - -## Documentation Sections - -### 🚀 [Setup & Installation](/docs/frameworks/qbcore/setup) -Complete guide to installing QBCore framework from scratch, including: -- Database setup and configuration -- Core framework installation -- Essential resources setup -- Server configuration -- Post-installation verification - -### 💻 [Development Guide](/docs/frameworks/qbcore/development) -Comprehensive development documentation covering: -- Resource development best practices -- QBCore API integration -- Database management -- Item and job system development -- UI/NUI development -- Testing and debugging - -### 🔧 [Troubleshooting](/docs/frameworks/qbcore/troubleshooting) -Common issues and their solutions: -- Installation problems -- Player data issues -- Performance optimization -- Resource-specific problems -- Debug techniques - -## Quick Start - -New to QBCore? Follow this recommended path: - -1. **[Setup & Installation](/docs/frameworks/qbcore/setup)** - Get QBCore running on your server -2. **[Development Guide](/docs/frameworks/qbcore/development)** - Learn to create custom resources -3. **[Troubleshooting](/docs/frameworks/qbcore/troubleshooting)** - Reference for when things go wrong - -## Community Resources - -- **[QBCore Documentation](https://docs.qbcore.org/)** - Official documentation -- **[QBCore GitHub](https://github.com/qbcore-framework)** - Source code and releases -- **[QBCore Discord](https://discord.gg/qbcore)** - Community support -- **[QBCore Forums](https://forum.cfx.re/t/qbcore-framework/4116649)** - Discussion and help - - - QBCore is actively developed with frequent updates. Always check the official documentation for the latest information. - - - - Always test resources thoroughly before deploying to a production server. Breaking changes in QBCore updates can affect resource compatibility. - \ No newline at end of file + +We are working hard to get the full qbCore documentation done as soon as possible! + \ No newline at end of file diff --git a/content/docs/frameworks/qbcore/setup.mdx b/content/docs/frameworks/qbcore/setup.mdx index 786d649..70d8c7d 100644 --- a/content/docs/frameworks/qbcore/setup.mdx +++ b/content/docs/frameworks/qbcore/setup.mdx @@ -4,404 +4,8 @@ description: Complete guide to setting up and installing QBCore framework. icon: "Download" --- -This guide covers the complete installation and initial setup of QBCore framework for your FiveM server. +import { InfoBanner } from '@ui/components/mdx-components'; -## Prerequisites - -Before installing QBCore, ensure you have: - -- **FiveM Server**: A properly configured FiveM server -- **MySQL Database**: MySQL 8.0+ or MariaDB 10.6+ -- **Git**: For cloning repositories -- **Basic Command Line Knowledge**: Comfort with terminal commands - -## Installation Methods - -### Method 1: Fresh Installation (Recommended) - -#### 1. Database Setup - -Create a new MySQL database for your server: - -```sql -CREATE DATABASE qbcore_server CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -CREATE USER 'qbcore_user'@'localhost' IDENTIFIED BY 'your_secure_password'; -GRANT ALL PRIVILEGES ON qbcore_server.* TO 'qbcore_user'@'localhost'; -FLUSH PRIVILEGES; -``` - -#### 2. Server Structure - -Create the proper directory structure: - -```bash -# Navigate to your server's resources folder -cd /path/to/your/server/resources - -# Create QBCore folder structure -mkdir -p [qb] -mkdir -p [standalone] -mkdir -p [voice] -``` - -#### 3. Core Framework Installation - -```bash -# Clone the core framework -cd [qb] -git clone https://github.com/qbcore-framework/qb-core.git - -# Essential resources -git clone https://github.com/qbcore-framework/qb-multicharacter.git -git clone https://github.com/qbcore-framework/qb-spawn.git -git clone https://github.com/qbcore-framework/qb-inventory.git -git clone https://github.com/qbcore-framework/qb-target.git -git clone https://github.com/qbcore-framework/qb-clothing.git -git clone https://github.com/qbcore-framework/qb-weathersync.git -git clone https://github.com/qbcore-framework/qb-houses.git -git clone https://github.com/qbcore-framework/qb-garages.git -git clone https://github.com/qbcore-framework/qb-phone.git -git clone https://github.com/qbcore-framework/qb-vehicleshop.git -git clone https://github.com/qbcore-framework/qb-vehiclekeys.git -git clone https://github.com/qbcore-framework/qb-bankrobbery.git -git clone https://github.com/qbcore-framework/qb-policejob.git -git clone https://github.com/qbcore-framework/qb-ambulancejob.git -git clone https://github.com/qbcore-framework/qb-management.git -git clone https://github.com/qbcore-framework/qb-radialmenu.git -git clone https://github.com/qbcore-framework/qb-hud.git -git clone https://github.com/qbcore-framework/qb-menu.git -git clone https://github.com/qbcore-framework/qb-input.git -git clone https://github.com/qbcore-framework/qb-loading.git -``` - -#### 4. Database Dependencies - -Install required database resources: - -```bash -# Navigate to standalone folder -cd ../[standalone] - -# Clone database connector -git clone https://github.com/overextended/oxmysql.git -``` - -#### 5. Database Import - -Import the QBCore database structure: - -```bash -# Import base database (located in qb-core/server/database.sql) -mysql -u qbcore_user -p qbcore_server < [qb]/qb-core/server/database.sql - -# Import additional resource databases as needed -mysql -u qbcore_user -p qbcore_server < [qb]/qb-houses/database/qb-houses.sql -mysql -u qbcore_user -p qbcore_server < [qb]/qb-phone/database/qb-phone.sql -# Continue for other resources with database requirements -``` - -### Method 2: QBCore Template (Alternative) - -For a quicker start, use the QBCore server template: - -```bash -# Clone the complete template -git clone https://github.com/qbcore-framework/qb-txAdminRecipe.git - -# This includes: -# - Pre-configured server structure -# - All essential resources -# - Database files -# - Basic configuration -``` - -## Configuration - -### 1. Database Connection - -Configure your database connection in `qb-core/shared/config.lua`: - -```lua -QBConfig = {} - -QBConfig.MaxPlayers = GetConvarInt('sv_maxclients', 48) -QBConfig.DefaultSpawn = {x = -254.88, y = -982.14, z = 31.22, h = 207.5} -QBConfig.UpdateInterval = 5 -QBConfig.StatusInterval = 5000 -QBConfig.Money = {} -QBConfig.Money.MoneyTypes = {cash = 500, bank = 5000, crypto = 0} -QBConfig.Money.DontAllowMinus = {'cash', 'crypto'} -QBConfig.Money.PayCheckTimeOut = 10 -QBConfig.Money.PayCheckSociety = false - --- Player management -QBConfig.Player = {} -QBConfig.Player.HungerRate = 4.2 -QBConfig.Player.ThirstRate = 3.8 -QBConfig.Player.Bloodtypes = {"A+", "A-", "B+", "B-", "AB+", "AB-", "O+", "O-"} - --- Server configuration -QBConfig.Server = {} -QBConfig.Server.Closed = false -QBConfig.Server.ClosedReason = "Server Closed" -QBConfig.Server.Uptime = 0 -QBConfig.Server.Whitelist = false -QBConfig.Server.WhitelistPermission = 'admin' -QBConfig.Server.PVP = true -QBConfig.Server.Discord = "" -QBConfig.Server.CheckDuplicateLicense = true -QBConfig.Server.Permissions = {'god', 'admin', 'mod'} -``` - -### 2. Server.cfg Configuration - -Add QBCore resources to your `server.cfg`: - -```cfg -# Database -ensure oxmysql - -# QBCore Framework -ensure qb-core -ensure qb-multicharacter -ensure qb-spawn -ensure qb-inventory -ensure qb-target -ensure qb-clothing -ensure qb-weathersync - -# Jobs -ensure qb-policejob -ensure qb-ambulancejob -ensure qb-management - -# Properties -ensure qb-houses -ensure qb-garages - -# Vehicles -ensure qb-vehicleshop -ensure qb-vehiclekeys - -# UI/UX -ensure qb-hud -ensure qb-menu -ensure qb-input -ensure qb-phone -ensure qb-radialmenu -ensure qb-loading - -# Other essentials -ensure qb-bankrobbery - -# Database connection string -set mysql_connection_string "mysql://qbcore_user:your_secure_password@localhost/qbcore_server?charset=utf8mb4" - -# Server configuration -set sv_hostname "My QBCore Server" -set sv_maxclients 32 -set server_description "A QBCore FiveM Server" -set sv_projectName "QBCore Server" -set sv_projectDesc "QBCore FiveM Server" - -# QBCore specific -set qb_locale "en" -set qb_UseTarget true -set qb_inventory "qb-inventory" - -# Licensing -sv_licenseKey "your_license_key_here" -``` - -### 3. Resource Loading Order - -Proper loading order is crucial for QBCore: - -```cfg -# 1. Database connector -ensure oxmysql - -# 2. Core framework -ensure qb-core - -# 3. Character system -ensure qb-multicharacter -ensure qb-spawn - -# 4. Essential systems -ensure qb-inventory -ensure qb-target -ensure qb-clothing - -# 5. Environmental -ensure qb-weathersync - -# 6. UI Systems -ensure qb-hud -ensure qb-menu -ensure qb-input -ensure qb-radialmenu - -# 7. Properties & Vehicles -ensure qb-houses -ensure qb-garages -ensure qb-vehicleshop -ensure qb-vehiclekeys - -# 8. Jobs -ensure qb-policejob -ensure qb-ambulancejob -ensure qb-management - -# 9. Additional features -ensure qb-phone -ensure qb-bankrobbery -ensure qb-loading - -# 10. Custom resources (add your custom resources last) -``` - -## Verification & Testing - -### 1. Server Startup - -Start your server and verify QBCore loads properly: - -```bash -# Check the console for errors -# Look for messages like: -# [qb-core] QBCore Started! -# [qb-multicharacter] Multicharacter Started! -``` - -### 2. Database Verification - -Check that database tables were created: - -```sql -USE qbcore_server; -SHOW TABLES; - --- You should see tables like: --- players --- player_vehicles --- player_houses --- etc. -``` - -### 3. In-Game Testing - -1. **Connect to Server**: Join your server -2. **Character Creation**: Test the multicharacter system -3. **Basic Functions**: Try commands like `/me`, `/job`, `/inventory` -4. **Job System**: Test changing jobs with `/setjob [job] [grade]` - -## Common Installation Issues - -### Database Connection Errors - -**Error**: `Failed to execute query: Access denied` - -**Solution**: -```sql --- Recreate user with proper permissions -DROP USER 'qbcore_user'@'localhost'; -CREATE USER 'qbcore_user'@'localhost' IDENTIFIED BY 'your_password'; -GRANT ALL PRIVILEGES ON qbcore_server.* TO 'qbcore_user'@'localhost'; -FLUSH PRIVILEGES; -``` - -### Resource Loading Errors - -**Error**: `Resource [qb-core] couldn't be started` - -**Solutions**: -1. Check resource path: Ensure resources are in `[qb]` folder -2. Verify manifest: Check `fxmanifest.lua` syntax -3. Dependencies: Ensure oxmysql loads before qb-core - -### Permission Errors - -**Error**: `You don't have permissions to access this` - -**Solution**: -```lua --- Add yourself as admin in qb-core/server/player.lua --- Or use the database: -INSERT INTO players (license, name, money, job, position) -VALUES ('license:your_license_here', 'Your Name', '{"cash":5000,"bank":25000,"crypto":0}', '{"name":"admin","label":"Admin","payment":5000,"onduty":true,"isboss":true,"grade":{"name":"Admin","level":10}}', '{"x":-269.4,"y":-955.3,"z":31.22,"h":205.8}'); -``` - -## Post-Installation Steps - -### 1. Admin Setup - -Add yourself as admin: - -```sql --- Method 1: Direct database -UPDATE players SET job = '{"name":"admin","label":"Admin","payment":5000,"onduty":true,"isboss":true,"grade":{"name":"Admin","level":10}}' WHERE license = 'license:your_license_here'; - --- Method 2: In-game command (if you have permissions) -/setjob admin 4 -``` - -### 2. Basic Server Configuration - -Configure basic server settings: - -```lua --- In qb-core/shared/config.lua -QBConfig.DefaultSpawn = {x = -254.88, y = -982.14, z = 31.22, h = 207.5} -- Customize spawn location -QBConfig.Money.MoneyTypes = {cash = 1000, bank = 10000, crypto = 0} -- Starting money -``` - -### 3. Job Configuration - -Add or modify jobs in `qb-core/shared/jobs.lua`: - -```lua -QBShared.Jobs = { - ['unemployed'] = { - label = 'Civilian', - defaultDuty = true, - offDutyPay = false, - grades = { - ['0'] = { - name = 'Freelancer', - payment = 10 - }, - }, - }, - ['police'] = { - label = 'Law Enforcement', - defaultDuty = true, - offDutyPay = false, - grades = { - ['0'] = {name = 'Recruit', payment = 50}, - ['1'] = {name = 'Officer', payment = 75}, - ['2'] = {name = 'Sergeant', payment = 100}, - ['3'] = {name = 'Lieutenant', payment = 125}, - ['4'] = {name = 'Chief', isboss = true, payment = 150}, - }, - }, - -- Add more jobs as needed -} -``` - -## Next Steps - -After successful installation: - -1. **[QBCore Configuration](/docs/frameworks/qbcore/configuration)** - Learn about advanced configuration options -2. **[Development Guide](/docs/frameworks/qbcore/development)** - Start developing custom resources -3. **[Troubleshooting](/docs/frameworks/qbcore/troubleshooting)** - Common issues and solutions -4. **[Resource Management](/docs/frameworks/qbcore/resources)** - Managing and updating QBCore resources - - - Congratulations! You now have a working QBCore server. Take time to familiarize yourself with the framework before adding custom resources. - - - - Always backup your database and server files before making major changes or updates. - + +We are working hard to get the full qbCore documentation done as soon as possible! + \ No newline at end of file diff --git a/content/docs/frameworks/qbcore/troubleshooting.mdx b/content/docs/frameworks/qbcore/troubleshooting.mdx index 3824473..e7f06dd 100644 --- a/content/docs/frameworks/qbcore/troubleshooting.mdx +++ b/content/docs/frameworks/qbcore/troubleshooting.mdx @@ -4,602 +4,8 @@ description: Common issues and solutions for QBCore framework. icon: "Bug" --- -This guide covers common issues encountered when working with QBCore framework and their solutions. +import { InfoBanner } from '@ui/components/mdx-components'; -## Installation Issues - -### Database Connection Problems - -#### Error: "Access denied for user" - -**Symptoms:** -``` -[ERROR] Access denied for user 'qbcore_user'@'localhost' (using password: YES) -``` - -**Solutions:** - -1. **Check Database Credentials:** -```sql --- Verify user exists -SELECT User, Host FROM mysql.user WHERE User = 'qbcore_user'; - --- If user doesn't exist, create it -CREATE USER 'qbcore_user'@'localhost' IDENTIFIED BY 'your_password'; -GRANT ALL PRIVILEGES ON qbcore_server.* TO 'qbcore_user'@'localhost'; -FLUSH PRIVILEGES; -``` - -2. **Verify Connection String:** -```cfg -# In server.cfg, ensure connection string is correct -set mysql_connection_string "mysql://qbcore_user:your_password@localhost/qbcore_server?charset=utf8mb4" -``` - -3. **Check MySQL Service:** -```bash -# Windows -net start mysql80 - -# Linux -sudo systemctl start mysql -sudo systemctl status mysql -``` - -#### Error: "Database 'qbcore_server' doesn't exist" - -**Solution:** -```sql -CREATE DATABASE qbcore_server CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -``` - -### Resource Loading Issues - -#### Error: "Resource [qb-core] couldn't be started" - -**Symptoms:** -``` -[ERROR] Could not load resource qb-core -[ERROR] Failed to start resource qb-core -``` - -**Solutions:** - -1. **Check Resource Path:** -``` -resources/ -├── [qb]/ -│ └── qb-core/ # Should be here -├── [standalone]/ -└── [voice]/ -``` - -2. **Verify fxmanifest.lua:** -```lua --- Check for syntax errors in fxmanifest.lua -fx_version 'cerulean' -game 'gta5' --- Ensure proper structure -``` - -3. **Check Dependencies:** -```cfg -# Ensure oxmysql loads before qb-core -ensure oxmysql -ensure qb-core -``` - -#### Error: "Script timeout" - -**Symptoms:** -``` -[ERROR] Script timeout: qb-core -``` - -**Solutions:** - -1. **Check for Infinite Loops:** -```lua --- Bad -while true do - -- No Wait() call - causes timeout -end - --- Good -while true do - Wait(1000) -- Always include Wait() -end -``` - -2. **Database Connection Issues:** -```lua --- Check if database is accessible -local result = MySQL.query.await('SELECT 1 as test') -if not result then - print('Database connection failed') -end -``` - -## Player Data Issues - -### Player Data Not Loading - -#### Error: "Player data is nil" - -**Symptoms:** -- Player spawns but has no data -- Commands don't work -- Money/job shows as nil - -**Solutions:** - -1. **Check Player Loading Events:** -```lua --- Client-side -RegisterNetEvent('QBCore:Client:OnPlayerLoaded', function() - PlayerData = QBCore.Functions.GetPlayerData() - print('Player data loaded:', json.encode(PlayerData)) -end) -``` - -2. **Verify Database Schema:** -```sql --- Check if players table exists and has data -SELECT COUNT(*) FROM players; -DESCRIBE players; -``` - -3. **Check for Database Corruption:** -```sql --- Look for corrupted player data -SELECT citizenid, name, LENGTH(money) as money_length -FROM players -WHERE money IS NULL OR money = '' OR JSON_VALID(money) = 0; -``` - -#### Error: "Player already exists" - -**Symptoms:** -- Cannot create new character -- Gets stuck on character creation - -**Solutions:** - -1. **Check for Duplicate Licenses:** -```sql --- Find duplicate licenses -SELECT license, COUNT(*) as count -FROM players -GROUP BY license -HAVING count > 1; - --- Remove duplicates (keep most recent) -DELETE p1 FROM players p1 -INNER JOIN players p2 -WHERE p1.id < p2.id AND p1.license = p2.license; -``` - -2. **Clear Character Data:** -```sql --- Remove specific player data -DELETE FROM players WHERE license = 'license:your_license_here'; -``` - -### Money/Job Issues - -#### Error: "Money is not updating" - -**Solutions:** - -1. **Check Money Format:** -```sql --- Verify money is valid JSON -SELECT citizenid, money, JSON_VALID(money) as is_valid -FROM players -WHERE JSON_VALID(money) = 0; - --- Fix invalid money data -UPDATE players -SET money = '{"cash":500,"bank":5000,"crypto":0}' -WHERE JSON_VALID(money) = 0; -``` - -2. **Check Money Functions:** -```lua --- Server-side: Proper money handling -local Player = QBCore.Functions.GetPlayer(source) -if Player then - local success = Player.Functions.AddMoney('cash', 1000) - print('Money added:', success) -end -``` - -#### Error: "Job permissions not working" - -**Solutions:** - -1. **Verify Job Data:** -```sql --- Check job format in database -SELECT citizenid, job, JSON_VALID(job) as is_valid -FROM players -WHERE JSON_VALID(job) = 0; -``` - -2. **Check Job Configuration:** -```lua --- In qb-core/shared/jobs.lua -QBShared.Jobs = { - ['police'] = { - label = 'Law Enforcement', - defaultDuty = true, - offDutyPay = false, - grades = { - ['0'] = {name = 'Recruit', payment = 50}, - -- Ensure grades are properly defined - }, - }, -} -``` - -## Resource-Specific Issues - -### Inventory Issues - -#### Error: "Items not showing in inventory" - -**Solutions:** - -1. **Check Item Registration:** -```lua --- In qb-core/shared/items.lua -QBShared.Items = { - ['my_item'] = { - name = 'my_item', - label = 'My Item', - weight = 100, - type = 'item', - image = 'my_item.png', - unique = false, - useable = true, - shouldClose = true, - combinable = nil, - description = 'Item description' - } -} -``` - -2. **Verify Item Images:** -``` -qb-inventory/html/images/ -└── my_item.png # Image must exist -``` - -3. **Check Inventory Database:** -```sql --- Verify inventory structure -SELECT citizenid, inventory -FROM players -WHERE JSON_VALID(inventory) = 0; -``` - -#### Error: "Cannot use items" - -**Solutions:** - -1. **Register Item Usage:** -```lua --- Server-side -QBCore.Functions.CreateUseableItem('my_item', function(source, item) - local Player = QBCore.Functions.GetPlayer(source) - if Player then - -- Item usage logic - TriggerClientEvent('qb-myresource:client:useItem', source, item) - end -end) -``` - -2. **Check Item Metadata:** -```lua --- Ensure item has proper metadata -local item = Player.Functions.GetItemByName('my_item') -if item and item.info then - -- Item has metadata -end -``` - -### Vehicle Issues - -#### Error: "Vehicles not spawning" - -**Solutions:** - -1. **Check Vehicle Hash:** -```lua --- Verify vehicle model exists -local model = GetHashKey('adder') -if not IsModelInCdimage(model) then - print('Vehicle model not found') - return -end -``` - -2. **Check Vehicle Database:** -```sql --- Verify vehicle data -SELECT plate, vehicle, hash -FROM player_vehicles -WHERE hash = 0 OR vehicle = ''; -``` - -3. **Vehicle Keys Issues:** -```lua --- Ensure proper key assignment -TriggerEvent('qb-vehiclekeys:server:GiveVehicleKeys', source, plate) -``` - -### Phone/UI Issues - -#### Error: "Phone not opening" - -**Solutions:** - -1. **Check Resource Dependencies:** -```cfg -# Ensure proper loading order -ensure qb-core -ensure qb-phone -``` - -2. **Check Phone Item:** -```lua --- Verify player has phone item -local Player = QBCore.Functions.GetPlayer(source) -local phone = Player.Functions.GetItemByName('phone') -if not phone then - Player.Functions.AddItem('phone', 1) -end -``` - -3. **UI/NUI Issues:** -```lua --- Check for JavaScript errors in F8 console --- Verify NUI focus -SetNuiFocus(true, true) -``` - -## Performance Issues - -### High Resource Usage - -#### Server Performance Problems - -**Symptoms:** -- High CPU usage -- Lag/stuttering -- Thread hitches - -**Solutions:** - -1. **Identify Resource Usage:** -``` -# In server console -resmon - -# Look for resources using high CPU/memory -``` - -2. **Optimize Database Queries:** -```lua --- Bad: Multiple queries in loop -for i = 1, #players do - MySQL.query.await('SELECT * FROM players WHERE citizenid = ?', {players[i]}) -end - --- Good: Single query with IN clause -local citizenIds = {} -for i = 1, #players do - citizenIds[#citizenIds + 1] = players[i] -end -local placeholders = string.rep('?,', #citizenIds):sub(1, -2) -MySQL.query.await('SELECT * FROM players WHERE citizenid IN (' .. placeholders .. ')', citizenIds) -``` - -3. **Reduce Timer Frequency:** -```lua --- Bad: Too frequent -CreateThread(function() - while true do - Wait(0) -- Runs every frame - -- Heavy operation - end -end) - --- Good: Reasonable frequency -CreateThread(function() - while true do - Wait(5000) -- Runs every 5 seconds - -- Heavy operation - end -end) -``` - -### Memory Leaks - -#### Increasing Memory Usage - -**Solutions:** - -1. **Check for Event Listeners:** -```lua --- Ensure proper cleanup -AddEventHandler('onResourceStop', function(resourceName) - if GetCurrentResourceName() == resourceName then - -- Cleanup code here - for i = 1, #createdObjects do - DeleteEntity(createdObjects[i]) - end - end -end) -``` - -2. **Clear References:** -```lua --- Prevent memory leaks -local myData = {} - -AddEventHandler('onResourceStop', function(resourceName) - if GetCurrentResourceName() == resourceName then - myData = nil - end -end) -``` - -## Debug Techniques - -### Logging and Debugging - -#### Enable Debug Mode - -```lua --- In config.lua -Config.Debug = true - --- Debug function -local function DebugPrint(...) - if Config.Debug then - print('^3[DEBUG]^7', ...) - end -end - --- Usage -DebugPrint('Player data:', json.encode(PlayerData)) -``` - -#### Database Debugging - -```lua --- Check database operations -local function SafeQuery(query, parameters) - local success, result = pcall(MySQL.query.await, query, parameters) - if not success then - print('^1[ERROR]^7 Database query failed:', result) - return nil - end - return result -end -``` - -### Console Commands for Debugging - -```lua --- Server-side debug commands -RegisterCommand('qb-debug-player', function(source, args) - local playerId = tonumber(args[1]) or source - local Player = QBCore.Functions.GetPlayer(playerId) - - if Player then - print('=== PLAYER DEBUG ===') - print('CitizenID:', Player.PlayerData.citizenid) - print('Name:', Player.PlayerData.name) - print('Job:', json.encode(Player.PlayerData.job)) - print('Money:', json.encode(Player.PlayerData.money)) - print('Position:', json.encode(Player.PlayerData.position)) - else - print('Player not found') - end -end, true) - -RegisterCommand('qb-debug-db', function(source, args) - local playerId = tonumber(args[1]) or source - local Player = QBCore.Functions.GetPlayer(playerId) - - if Player then - local result = MySQL.query.await('SELECT * FROM players WHERE citizenid = ?', {Player.PlayerData.citizenid}) - if result[1] then - print('=== DATABASE DEBUG ===') - print('Raw data:', json.encode(result[1])) - end - end -end, true) -``` - -## Common Error Messages - -### Script Errors - -| Error | Cause | Solution | -|-------|-------|----------| -| `attempt to index a nil value` | Variable not initialized | Check if variable exists before using | -| `attempt to call a nil value` | Function doesn't exist | Verify function name and exports | -| `bad argument #1 to 'pairs'` | Trying to iterate nil value | Check if table exists before iteration | -| `string expected, got nil` | Passing nil to string function | Validate input parameters | - -### Database Errors - -| Error | Cause | Solution | -|-------|-------|----------| -| `Table doesn't exist` | Missing database table | Import SQL files | -| `Column doesn't exist` | Database schema mismatch | Update database schema | -| `Duplicate entry` | Trying to insert duplicate key | Use UPDATE or INSERT IGNORE | -| `Data too long` | Value exceeds column length | Increase column size | - -## Prevention Best Practices - -### Code Quality - -1. **Always Check for Nil:** -```lua -local Player = QBCore.Functions.GetPlayer(source) -if not Player then return end -``` - -2. **Use Error Handling:** -```lua -local success, result = pcall(function() - return MySQL.query.await(query, params) -end) - -if not success then - print('Query failed:', result) - return -end -``` - -3. **Validate Inputs:** -```lua -RegisterNetEvent('myresource:server:action', function(data) - if not data or type(data) ~= 'table' then - return - end - - if not data.amount or type(data.amount) ~= 'number' then - return - end - - -- Process valid data -end) -``` - -### Testing - -1. **Test with Multiple Players** -2. **Test Resource Restart** -3. **Test Database Disconnection** -4. **Test Invalid Inputs** -5. **Monitor Resource Usage** - -### Monitoring - -1. **Regular Database Backups** -2. **Monitor Server Performance** -3. **Check Error Logs** -4. **Update Dependencies** - - - Regular maintenance and monitoring can prevent most issues before they become problems. - - - - Always test changes on a development server before applying to production. - + +We are working hard to get the full qbCore documentation done as soon as possible! + \ No newline at end of file diff --git a/content/docs/guides/backup-recovery.mdx b/content/docs/guides/backup-recovery.mdx index c574e26..fa56180 100644 --- a/content/docs/guides/backup-recovery.mdx +++ b/content/docs/guides/backup-recovery.mdx @@ -4,12 +4,30 @@ description: Complete guide to backing up and recovering FiveM server data and c icon: "HardDrive" --- +import { FeatureList, CheckList, QuickLinks, DefinitionList, CommandCard, CommandTable, PropertyCard, IconGrid, Shortcut, StatusBadge, TroubleshootingCard, PermissionTable, ConfigBlock, StepList, ComparisonTable } from '@ui/components/mdx-components' + A comprehensive backup and recovery strategy is essential for any FiveM server. This guide covers everything from basic backup procedures to advanced disaster recovery planning. ## Understanding What to Backup + + ### Critical Data Categories + + **Server Files** - Server executable and artifacts - Configuration files (server.cfg) diff --git a/content/docs/guides/common-threats.mdx b/content/docs/guides/common-threats.mdx new file mode 100644 index 0000000..a79e67c --- /dev/null +++ b/content/docs/guides/common-threats.mdx @@ -0,0 +1,511 @@ +--- +title: Common Threats +description: Comprehensive guide to malicious users, backdoors, and attacks targeting FiveM and RedM servers. Learn how to identify, prevent, and remediate security threats. +--- + +import { TroubleshootingCard, StepList, InfoBanner, DefinitionList, ActionTable, CheckList, CategoryGrid } from '@ui/components/mdx-components' + +## Introduction + +Your FiveM or RedM server is a valuable asset. It hosts data, manages player interactions, and runs custom code. This also makes it a target. Bad actors range from script kiddies running automated exploits to sophisticated attackers with specific knowledge of your infrastructure. Understanding the threat landscape is the first step to defending it. + +This guide covers the most common attack vectors, how they work, how to detect them, and how to prevent them. Consider it your server's security blueprint. + +## Types of Malicious Actors + +The people trying to compromise your server fall into several categories, each with different motivations and capabilities. + +**Script Kiddies** + +These are attackers with limited technical knowledge who use pre-built tools and scripts. They're the most common threat and typically run broad, indiscriminate attacks hoping something sticks. They lack the sophistication to target specific infrastructure but can still cause significant damage through sheer volume and persistence. + +**Disgruntled Users** + +Former admins, banned players, or unhappy staff members represent an insider threat. They have legitimate access credentials or knowledge of your infrastructure, which makes them particularly dangerous. They know your weak points because they've been inside your network. + +**Competitors** + +Other server owners may attempt to take down or compromise competing servers to drive players to their own. These attacks are often targeted and persistent. + +**Professional Threat Actors** + +Ransomware gangs and professional cybercriminals may target your server for financial gain. This tier operates at a higher level and may involve multiple attack vectors, reconnaissance, and patience. + +**Nation State Actors** + +Unlikely unless your server hosts something politically sensitive, but included for completeness. If you're worried about nation state actors, you have bigger problems than this guide can solve. + +## Attack Vector: Malicious Resources and Scripts + +The most common entry point for compromise is through custom resources and scripts. Your server runs code written by you, your developers, and potentially third-party developers. Any of these could be compromised or intentionally malicious. + +### Direct Code Exploitation + +A resource can perform almost any action on your server due to how FiveM's server-side scripting works. A malicious resource can: + +Read sensitive data from your database and exfiltrate it to remote servers. This includes player information, financial data, or administrative logs. The attacker simply connects to your database with the same credentials your server uses and downloads whatever they want. + +Modify server state to grant itself admin permissions, enable god mode, or create false evidence. Once administrative access is obtained, the attacker can do anything your admin system allows. + +Execute arbitrary commands on the host machine running your server. With insufficient sandboxing, a compromised resource can break out of the FiveM runtime and access the underlying operating system. + +Establish persistent access by creating backdoors that survive resource restarts or server reboots. The attacker doesn't need physical access to your machine to maintain control. + +Create hidden functionality that operates silently. A resource might appear normal to administrators while performing malicious activities in the background. The code that handles player reports might also be logging their conversations to a remote server. The resource that manages transactions might be skimming a percentage to the attacker's account. + +### Where Malicious Resources Come From + +Developers and server owners obtain resources from various sources, not all of them trustworthy. + +**Purchased or Downloaded Scripts** + +Resources acquired from marketplaces, GitHub, or purchased from developers may contain hidden functionality. Developers might include backdoors intentionally to maintain control of the resource and extract value from it. A resource sold to multiple servers can have a single backdoor benefiting the original developer. They monitor their backdoored resource across all servers, collecting player data or running resource-intensive tasks. + +Sometimes the resource is compromised after purchase. A developer's GitHub account is breached, and malicious code is added. The developer may not even realize their work has been compromised. Updates distributed to existing customers contain the malicious payload. + +**Custom Development** + +Even custom code written for your server can be compromised if the developer has malicious intent or their development environment has been compromised. A developer's laptop contains malware that injects code into resources before they're uploaded to your server. The developer has no idea this is happening because the injected code only activates under certain conditions they don't test for. + +**Open Source Resources** + +Open source doesn't mean secure. Many open source resources are abandoned or maintained by volunteers with limited security expertise. Vulnerabilities may exist and never be patched. Sometimes open source repositories are taken over by bad actors after the original maintainer loses interest. The repository's commit history is clean until suddenly it's not. + +### Identifying Malicious Resources + +Finding malicious code before it compromises your server requires vigilance and technical knowledge. + +