Merge branch 'main' into configuration-checking
This commit is contained in:
commit
5e9dfa0172
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"name": "Postiz Dev Container",
|
||||
"image": "localhost/postiz-devcontainer",
|
||||
"features": {},
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"settings": {},
|
||||
"extensions": []
|
||||
}
|
||||
},
|
||||
"forwardPorts": ["4200:4200", "3000:3000"],
|
||||
"mounts": ["source=/apps,destination=/apps/dist/,type=bind,consistency=cached"]
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
# We want the docker builds to be clean, and as fast as possible. Don't send
|
||||
# any half-built stuff in the build context as a pre-caution (also saves copying
|
||||
# 180k files in node_modules that isn't used!).
|
||||
**/node_modules
|
||||
dist
|
||||
.nx
|
||||
.devcontainer
|
||||
**/.git
|
||||
**/dist
|
||||
**/*.md
|
||||
**/LICENSE
|
||||
**/npm-debug.log
|
||||
**/*.vscode
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
---
|
||||
name: "Build Containers"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
build-containers:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Login to ghcr
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ github.token }}
|
||||
|
||||
- name: docker build
|
||||
run: ./var/docker/docker-build.sh
|
||||
|
||||
- name: Get date
|
||||
run: |
|
||||
echo "DATE=$(date +'%s')" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Print post-build debug info
|
||||
run: |
|
||||
docker images
|
||||
|
||||
- name: docker tag
|
||||
run: |
|
||||
docker tag localhost/postiz ghcr.io/gitroomhq/postiz-app:${{ env.DATE }}
|
||||
docker push ghcr.io/gitroomhq/postiz-app:${{ env.DATE }}
|
||||
|
||||
docker tag localhost/postiz-devcontainer ghcr.io/gitroomhq/postiz-devcontainer:${{ env.DATE }}
|
||||
docker push ghcr.io/gitroomhq/postiz-devcontainer:${{ env.DATE }}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
---
|
||||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- package.json
|
||||
- apps/**
|
||||
- '!apps/docs/**'
|
||||
- libraries/**
|
||||
|
||||
pull_request:
|
||||
paths:
|
||||
- package.json
|
||||
- apps/**
|
||||
- '!apps/docs/**'
|
||||
- libraries/**
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: ['20.17.0']
|
||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'npm'
|
||||
cache-dependency-path: |
|
||||
**/package-lock.json
|
||||
|
||||
# https://nextjs.org/docs/pages/building-your-application/deploying/ci-build-caching#github-actions
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.npm
|
||||
${{ github.workspace }}/.next/cache
|
||||
|
||||
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-
|
||||
|
||||
- run: npm ci
|
||||
- run: npm run build
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
---
|
||||
name: "Code Quality Analysis"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- apps/**
|
||||
- '!apps/docs/**'
|
||||
- libraries/**
|
||||
|
||||
pull_request:
|
||||
paths:
|
||||
- apps/**
|
||||
- '!apps/docs/**'
|
||||
- libraries/**
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze (${{ matrix.language }})
|
||||
|
||||
runs-on: 'ubuntu-latest'
|
||||
permissions:
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- language: javascript-typescript
|
||||
build-mode: none
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
---
|
||||
name: ESLint
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- package.json
|
||||
- apps/**
|
||||
- '!apps/docs/**'
|
||||
- libraries/**
|
||||
|
||||
pull_request:
|
||||
paths:
|
||||
- package.json
|
||||
- apps/**
|
||||
- '!apps/docs/**'
|
||||
- libraries/**
|
||||
|
||||
jobs:
|
||||
eslint:
|
||||
name: Run eslint scanning
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: write
|
||||
actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status
|
||||
strategy:
|
||||
matrix:
|
||||
service: ["backend", "frontend"]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: |
|
||||
**/package-lock.json
|
||||
|
||||
- name: Install ESLint
|
||||
run: |
|
||||
npm install eslint
|
||||
npm install @microsoft/eslint-formatter-sarif@2.1.7
|
||||
|
||||
- name: Run ESLint
|
||||
run: npx eslint apps/${{ matrix.service }}/
|
||||
--config apps/${{ matrix.service }}/.eslintrc.json
|
||||
--format @microsoft/eslint-formatter-sarif
|
||||
--output-file apps/${{ matrix.service }}/eslint-results.sarif
|
||||
continue-on-error: true
|
||||
|
||||
- name: Upload analysis results to GitHub
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
with:
|
||||
sarif_file: apps/${{ matrix.service }}/eslint-results.sarif
|
||||
wait-for-processing: true
|
||||
|
|
@ -42,3 +42,7 @@ Thumbs.db
|
|||
|
||||
# Next.js
|
||||
.next
|
||||
|
||||
# Vim files
|
||||
**/*.swp
|
||||
**/*.swo
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
# This Dockerfile is used for producing 3 container images.
|
||||
#
|
||||
# base - which is thrown away, that contains node and the basic infrastructure.
|
||||
# devcontainer - which is used for development, and contains the source code and the node_modules.
|
||||
# dist - which is used for production, and contains the built source code and the node_modules.
|
||||
|
||||
ARG NODE_VERSION="20.17"
|
||||
|
||||
# Base image
|
||||
FROM docker.io/node:${NODE_VERSION}-alpine3.19 AS base
|
||||
|
||||
## Just reduce unccessary noise in the logs.
|
||||
ENV NPM_CONFIG_UPDATE_NOTIFIER=false
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
|
||||
RUN apk add --no-cache \
|
||||
bash=5.2.21-r0 \
|
||||
supervisor=4.2.5-r4 \
|
||||
make \
|
||||
build-base
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
EXPOSE 4200
|
||||
EXPOSE 3000
|
||||
|
||||
COPY var/docker/entrypoint.sh /app/entrypoint.sh
|
||||
COPY var/docker/supervisord.conf /etc/supervisord.conf
|
||||
COPY var/docker/supervisord /app/supervisord_available_configs/
|
||||
COPY .env.example /config/.env
|
||||
|
||||
VOLUME /config
|
||||
|
||||
LABEL org.opencontainers.image.source=https://github.com/gitroomhq/postiz-app
|
||||
|
||||
ENTRYPOINT ["/app/entrypoint.sh"]
|
||||
|
||||
# Builder image
|
||||
FROM base AS devcontainer
|
||||
|
||||
COPY nx.json tsconfig.base.json package.json package-lock.json /app/
|
||||
COPY apps /app/apps/
|
||||
COPY libraries /app/libraries/
|
||||
|
||||
RUN npm ci --no-fund && npx nx run-many --target=build --projects=frontend,backend,workers,cron
|
||||
|
||||
LABEL org.opencontainers.image.title="Postiz App (DevContainer)"
|
||||
|
||||
# Output image
|
||||
FROM base AS dist
|
||||
|
||||
COPY --from=devcontainer /app/node_modules/ /app/node_modules/
|
||||
COPY --from=devcontainer /app/dist/ /app/dist/
|
||||
|
||||
COPY package.json nx.json /app/
|
||||
|
||||
## Labels at the bottom, because CI will eventually add dates, commit hashes, etc.
|
||||
LABEL org.opencontainers.image.title="Postiz App (Production)"
|
||||
|
|
@ -48,19 +48,18 @@ export class PostsController {
|
|||
@GetOrgFromRequest() org: Organization,
|
||||
@Query() query: GetPostsDto
|
||||
) {
|
||||
const [posts, comments] = await Promise.all([
|
||||
const [posts] = await Promise.all([
|
||||
this._postsService.getPosts(org.id, query),
|
||||
this._commentsService.getAllCommentsByWeekYear(
|
||||
org.id,
|
||||
query.year,
|
||||
query.week,
|
||||
query.isIsoWeek === 'true'
|
||||
),
|
||||
// this._commentsService.getAllCommentsByWeekYear(
|
||||
// org.id,
|
||||
// query.year,
|
||||
// query.week
|
||||
// ),
|
||||
]);
|
||||
|
||||
return {
|
||||
posts,
|
||||
comments,
|
||||
// comments,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -169,4 +169,4 @@ You can look at the other integration to understand what data to put inside.
|
|||
And add the new provider to the list.
|
||||
```typescript show.all.providers.tsx
|
||||
{identifier: 'providerName', component: DefaultImportFromHighOrderProvider},
|
||||
```
|
||||
```
|
||||
|
|
@ -3,14 +3,16 @@ title: Email Notifications
|
|||
description: How to send notifications to users
|
||||
---
|
||||
|
||||
At the moment we are using Resend to send email notifications to users, and might be changed the Novu later.
|
||||
Postiz uses Resend to send email notifications to users. Emails are currently
|
||||
required as part of the new-user creation process, which sends an activation
|
||||
email.
|
||||
|
||||
Register to [Resend](https://resend.com) connect your domain.
|
||||
Copy your API Key.
|
||||
Head over to .env file and add the following line.
|
||||
* Register to [Resend](https://resend.com), and connect your domain.
|
||||
* Copy your API Key from the Resend control panel.
|
||||
* Open the .env file and edit the following line.
|
||||
|
||||
```env
|
||||
RESEND_API_KEY=""
|
||||
RESEND_API_KEY="<your-api-key-here>"
|
||||
```
|
||||
|
||||
Feel free to contribute other providers to send email notifications.
|
||||
Feel free to contribute other providers to send email notifications.
|
||||
|
|
|
|||
|
|
@ -8,13 +8,13 @@ Unlike other NX project, this project has one `.env` file that is shared between
|
|||
It makes it easier to develop and deploy the project.<br /><br />
|
||||
When deploying to websites like [Railway](https://railway.app) or [Heroku](https://heroku.com), you can use a shared environment variables for all the apps.<br /><br />
|
||||
|
||||
**At the moment it has 6 project:**
|
||||
**At the moment it has 6 projects:**
|
||||
|
||||
- **Backend** - NestJS based system
|
||||
- **Workers** - NestJS based workers connected to a Redis Queue.
|
||||
- **Cron** - NestJS scheduler to run cron jobs.
|
||||
- **Frontend** - NextJS based control panel.
|
||||
- **Docs** - Mintlify based documentation website.
|
||||
- [Frontend](#frontend) - Provides the Web user interface, talks to the Backend.
|
||||
- [Backend](#backend) - Does all the real work, provides an API for the frontend, and posts work to the redis queue.
|
||||
- [Workers](#worker) - Consumes work from the Redis Queue.
|
||||
- [Cron](#cron) - Run jobs at scheduled times.
|
||||
- [Docs](#docs) - This documentation site!
|
||||
|
||||
<img
|
||||
src="/images/arch.png"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,144 @@
|
|||
---
|
||||
title: Development Environment
|
||||
---
|
||||
|
||||
This is currently the recommended option to install Postiz in a supportable configuration. The docker images are in active and heavy development for now.
|
||||
|
||||
## Tested configurations
|
||||
|
||||
- MacOS
|
||||
- Linux (Fedora 40)
|
||||
|
||||
Naturally you can use these instructions to setup a development environment on any platform, but there may not be much experience in the community to help you with any issues you may encounter.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
This guide will ask you to install & configure several services exaplained below.
|
||||
|
||||
### Prerequisite Cloud Services
|
||||
|
||||
- **[Resend account](https://resend.com)** - for user activation and email notifications.
|
||||
- **[Cloudflare R2](https://cloudfalre.com)** - for uploads (optional, can use local machine), and storing account data.
|
||||
- **Social Media API details** - various API keys and secrets (more details later) for services you want to use; reddit, X, Instagram, etc..
|
||||
|
||||
### Prerequisite Local Services
|
||||
|
||||
- **Node.js** - for running the code! (version 18+)
|
||||
- **PostgreSQL** - or any other SQL database (instructions beleow suggest Docker)
|
||||
- **Redis** - for handling worker queues (instructions below suggest Docker)
|
||||
|
||||
We have some messages from users who are using Windows, which should work, but they are not tested well yet.
|
||||
|
||||
## Installation Instructions
|
||||
|
||||
### NodeJS (version 18+)
|
||||
|
||||
A complete guide of how to install NodeJS can be found [here](https://nodejs.org/en/download/).
|
||||
|
||||
### PostgreSQL (or any other SQL database) & Redis
|
||||
|
||||
You can choose **Option A** to **Option B** to install the database.
|
||||
|
||||
#### Option A) Postgres and Redis as Single containers
|
||||
|
||||
You can install [Docker](https://www.docker.com/products/docker-desktop) and run:
|
||||
|
||||
```bash Terminal
|
||||
docker run -e POSTGRES_USER=root -e POSTGRES_PASSWORD=your_password --name postgres -p 5432:5432 -d postgres
|
||||
docker run --name redis -p 6379:6379 -d redis
|
||||
```
|
||||
|
||||
#### Option B) Postgres and Redis as docker-compose
|
||||
|
||||
Download the [docker-compose.yaml file here](https://raw.githubusercontent.com/gitroomhq/postiz-app/main/docker-compose.dev.yaml),
|
||||
or grab it from the repository in the next step.
|
||||
|
||||
```bash Terminal
|
||||
docker compose -f "docker-compose.dev.yaml" up
|
||||
```
|
||||
|
||||
## Build Postiz
|
||||
|
||||
<Steps>
|
||||
<Step title="Clone the repository">
|
||||
```bash Terminal
|
||||
git clone https://github.com/gitroomhq/gitroom
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Set environment variables">
|
||||
Copy the `.env.example` file to `.env` and fill in the values
|
||||
|
||||
```bash .env
|
||||
# Required Settings
|
||||
DATABASE_URL="postgresql://postiz-user:postiz-password@localhost:5432/postiz-db-local"
|
||||
REDIS_URL="redis://localhost:6379"
|
||||
JWT_SECRET="random string for your JWT secret, make it long"
|
||||
FRONTEND_URL="http://localhost:4200"
|
||||
NEXT_PUBLIC_BACKEND_URL="http://localhost:3000"
|
||||
BACKEND_INTERNAL_URL="http://localhost:3000"
|
||||
|
||||
# Social Media API Settings
|
||||
X_API_KEY="Twitter API key for normal oAuth not oAuth2"
|
||||
X_API_SECRET="Twitter API secret for normal oAuth not oAuth2"
|
||||
LINKEDIN_CLIENT_ID="Linkedin Client ID"
|
||||
LINKEDIN_CLIENT_SECRET="Linkedin Client Secret"
|
||||
REDDIT_CLIENT_ID="Reddit Client ID"
|
||||
REDDIT_CLIENT_SECRET="Linkedin Client Secret"
|
||||
GITHUB_CLIENT_ID="GitHub Client ID"
|
||||
GITHUB_CLIENT_SECRET="GitHub Client Secret"
|
||||
RESEND_API_KEY="Resend API KEY"
|
||||
UPLOAD_DIRECTORY="optional: your upload directory path if you host your files locally"
|
||||
NEXT_PUBLIC_UPLOAD_STATIC_DIRECTORY="optional: your upload directory slug if you host your files locally"
|
||||
CLOUDFLARE_ACCOUNT_ID="Cloudflare R2 Account ID"
|
||||
CLOUDFLARE_ACCESS_KEY="Cloudflare R2 Access Key"
|
||||
CLOUDFLARE_SECRET_ACCESS_KEY="Cloudflare R2 Secret Access Key"
|
||||
CLOUDFLARE_BUCKETNAME="Cloudflare R2 Bucket Name"
|
||||
CLOUDFLARE_BUCKET_URL="Cloudflare R2 Backet URL"
|
||||
|
||||
# Developer Settings
|
||||
NX_ADD_PLUGINS=false
|
||||
IS_GENERAL="true" # required for now
|
||||
```
|
||||
|
||||
</Step>
|
||||
|
||||
<Step title="Install the dependencies">
|
||||
```bash Terminal
|
||||
npm install
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Generate the prisma client and run the migrations">
|
||||
```bash Terminal
|
||||
npm run prisma-db-push
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Run the project">
|
||||
```bash Terminal
|
||||
npm run dev
|
||||
```
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
If everything is running successfully, open http://localhost:4200 in your browser!
|
||||
|
||||
If everything is not running - you had errors in the steps above, please head over to our [support](/support) page.
|
||||
|
||||
## Next Steps
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="How it works" icon="screwdriver-wrench" href="/howitworks">
|
||||
Learn the architecture of the project
|
||||
</Card>
|
||||
<Card title="Email notifications" icon="envelope" href="/emails">
|
||||
Set up email for notifications
|
||||
</Card>
|
||||
<Card title="GitHub" icon="code-branch" href="/github">
|
||||
Set up github for authentication and sync
|
||||
</Card>
|
||||
<Card title="Providers" icon="linkedin" href="/providers/x/x">
|
||||
Set up providers such as Linkedin, X and Reddit
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
---
|
||||
title: Docker Compose
|
||||
---
|
||||
|
||||
import EarlyDoc from '/snippets/earlydoc.mdx';
|
||||
import DockerDatabase from '/snippets/docker-database.mdx';
|
||||
import DockerEnvvarApps from '/snippets/docker-envvar-apps.mdx';
|
||||
|
||||
<EarlyDoc />
|
||||
<DockerDatabase />
|
||||
|
||||
# Example `docker-compose.yml` file
|
||||
|
||||
```yaml
|
||||
services:
|
||||
postiz:
|
||||
image: ghcr.io/gitroomhq/postiz-app:latest
|
||||
container_name: postiz
|
||||
restart: always
|
||||
volumes:
|
||||
- ./config:/config/ # Should contain your .env file
|
||||
ports:
|
||||
- 4200:4200
|
||||
- 3000:3000
|
||||
networks:
|
||||
- postiz-network
|
||||
|
||||
postiz-postgres:
|
||||
image: postgres:14.5
|
||||
container_name: postiz-postgres
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_PASSWORD: postiz-local-pwd
|
||||
POSTGRES_USER: postiz-local
|
||||
POSTGRES_DB: postiz-db-local
|
||||
volumes:
|
||||
- postgres-volume:/var/lib/postgresql/data
|
||||
ports:
|
||||
- 5432:5432
|
||||
networks:
|
||||
- postiz-network
|
||||
postiz-pg-admin:
|
||||
image: dpage/pgadmin4
|
||||
container_name: postiz-pg-admin
|
||||
restart: always
|
||||
ports:
|
||||
- 8081:80
|
||||
environment:
|
||||
PGADMIN_DEFAULT_EMAIL: admin@admin.com
|
||||
PGADMIN_DEFAULT_PASSWORD: admin
|
||||
networks:
|
||||
- postiz-network
|
||||
postiz-redis:
|
||||
image: redis:7.2
|
||||
container_name: postiz-redis
|
||||
restart: always
|
||||
ports:
|
||||
- 6379:6379
|
||||
|
||||
volumes:
|
||||
postgres-volume:
|
||||
external: false
|
||||
|
||||
networks:
|
||||
postiz-network:
|
||||
external: false
|
||||
```
|
||||
|
||||
<DockerEnvvarApps />
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
title: Docker
|
||||
---
|
||||
|
||||
import EarlyDoc from '/snippets/earlydoc.mdx';
|
||||
import DockerDatabase from '/snippets/docker-database.mdx';
|
||||
import DockerEnvvarApps from '/snippets/docker-envvar-apps.mdx';
|
||||
|
||||
<EarlyDoc />
|
||||
|
||||
<DockerDatabase />
|
||||
|
||||
|
||||
# Create the container on command line
|
||||
|
||||
```bash
|
||||
docker create --name postiz -v ./config:/config -p 4200:4200 -p 3000:3000 ghcr.io/postiz/postiz:latest
|
||||
```
|
||||
|
||||
<DockerEnvvarApps />
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
title: Helm
|
||||
---
|
||||
|
||||
import EarlyDoc from '/snippets/earlydoc.mdx';
|
||||
import DockerDatabase from '/snippets/docker-database.mdx';
|
||||
|
||||
<EarlyDoc />
|
||||
<DockerDatabase />
|
||||
|
||||
Postiz has a helm chart that is in very active development. You can find it here;
|
||||
|
||||
https://github.com/gitroomhq/postiz-helmchart
|
||||
|
|
@ -61,6 +61,15 @@
|
|||
"pages": [
|
||||
"introduction",
|
||||
"quickstart",
|
||||
{
|
||||
"group": "Install",
|
||||
"pages": [
|
||||
"installation/development",
|
||||
"installation/docker",
|
||||
"installation/docker-compose",
|
||||
"installation/kubernetes-helm"
|
||||
]
|
||||
},
|
||||
"howitworks",
|
||||
"emails",
|
||||
"github",
|
||||
|
|
@ -73,9 +82,15 @@
|
|||
"providers/articles"
|
||||
]
|
||||
},
|
||||
"providers/how-to-add-provider"
|
||||
"support"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"group": "Developer Guide",
|
||||
"pages": [
|
||||
"developer-guide/how-to-add-provider"
|
||||
]
|
||||
}
|
||||
],
|
||||
"footerSocials": {
|
||||
"twitter": "https://twitter.com/nevodavid",
|
||||
|
|
|
|||
|
|
@ -2,117 +2,17 @@
|
|||
title: 'Quickstart'
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
At the moment it is necessary to build the project locally - some dependencies
|
||||
like redis and postgres can run as docker containers, but there is no docker
|
||||
container for Postiz just yet.
|
||||
|
||||
To run the project you need to have multiple things:
|
||||
## Self Hosted installation options
|
||||
|
||||
- Node.js (version 18+)
|
||||
- PostgreSQL (or any other SQL database)
|
||||
- Redis
|
||||
- Resend account
|
||||
- Cloudflare R2 for uploads (optional, can use local machine)
|
||||
- Social Media Client and Secret (more details later)
|
||||
You can choose between the following installation options;
|
||||
|
||||
### NodeJS (version 18+)
|
||||
* [Development](/installation/development) - The only installation option that is offically supported at the moment.
|
||||
* [Docker (standalone)](/installation/docker) - Run from the command line with Docker.
|
||||
* [Docker Compose](/installation/docker-compose) - Run with Docker Compose.
|
||||
* [Helm](/installation/kubernetes-helm) - Run with Kubernetes + Helm.
|
||||
|
||||
A complete guide of how to install NodeJS can be found [here](https://nodejs.org/en/download/).
|
||||
|
||||
### PostgreSQL (or any other SQL database)
|
||||
|
||||
Make sure you have PostgreSQL installed on your machine.<br />
|
||||
If you don't, you can install [Docker](https://www.docker.com/products/docker-desktop) and run:
|
||||
|
||||
```bash
|
||||
docker run -e POSTGRES_USER=root -e POSTGRES_PASSWORD=your_password --name postgres -p 5432:5432 -d postgres
|
||||
```
|
||||
|
||||
### Redis
|
||||
|
||||
Make sure you have Redis installed on your machine.<br />
|
||||
If you don't, you can install [Docker](https://www.docker.com/products/docker-desktop) and run:
|
||||
|
||||
```bash
|
||||
docker run --name redis -p 6379:6379 -d redis
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
<Steps>
|
||||
<Step title="Clone the repository">
|
||||
```bash Terminal
|
||||
git clone https://github.com/gitroomhq/gitroom
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Copy environment variables">
|
||||
Copy the `.env.example` file to `.env` and fill in the values
|
||||
|
||||
```bash .env
|
||||
DATABASE_URL="postgres database URL"
|
||||
REDIS_URL="redis database URL"
|
||||
JWT_SECRET="random string for your JWT secret, make it long"
|
||||
FRONTEND_URL="By default: http://localhost:4200"
|
||||
NEXT_PUBLIC_BACKEND_URL="By default: http://localhost:3000"
|
||||
BACKEND_INTERNAL_URL="If you use docker, you might want something like: http://backend:3000"
|
||||
X_API_KEY="Twitter API key for normal oAuth not oAuth2"
|
||||
X_API_SECRET="Twitter API secret for normal oAuth not oAuth2"
|
||||
LINKEDIN_CLIENT_ID="Linkedin Client ID"
|
||||
LINKEDIN_CLIENT_SECRET="Linkedin Client Secret"
|
||||
REDDIT_CLIENT_ID="Reddit Client ID"
|
||||
REDDIT_CLIENT_SECRET="Linkedin Client Secret"
|
||||
GITHUB_CLIENT_ID="GitHub Client ID"
|
||||
GITHUB_CLIENT_SECRET="GitHub Client Secret"
|
||||
RESEND_API_KEY="Resend API KEY"
|
||||
UPLOAD_DIRECTORY="optional: your upload directory path if you host your files locally"
|
||||
NEXT_PUBLIC_UPLOAD_STATIC_DIRECTORY="optional: your upload directory slug if you host your files locally"
|
||||
CLOUDFLARE_ACCOUNT_ID="Cloudflare R2 Account ID"
|
||||
CLOUDFLARE_ACCESS_KEY="Cloudflare R2 Access Key"
|
||||
CLOUDFLARE_SECRET_ACCESS_KEY="Cloudflare R2 Secret Access Key"
|
||||
CLOUDFLARE_BUCKETNAME="Cloudflare R2 Bucket Name"
|
||||
CLOUDFLARE_BUCKET_URL="Cloudflare R2 Backet URL"
|
||||
NX_ADD_PLUGINS=false
|
||||
IS_GENERAL="true" # required for now
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Install the dependencies">
|
||||
|
||||
```bash Terminal
|
||||
npm install
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Setup postgres & redis via docker compose">
|
||||
```bash Terminal
|
||||
docker compose -f "docker-compose.dev.yaml" up
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Generate the prisma client and run the migrations">
|
||||
```bash Terminal
|
||||
npm run prisma-db-push
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Run the project">
|
||||
```bash Terminal
|
||||
npm run dev
|
||||
```
|
||||
</Step>
|
||||
</Steps>
|
||||
You have to follow all the tabs in the "Developers" menu to install Gitroom
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="How it works" icon="screwdriver-wrench" href="/howitworks">
|
||||
Learn the architecture of the project
|
||||
</Card>
|
||||
<Card title="Email notifications" icon="envelope" href="/emails">
|
||||
Set up email for notifications
|
||||
</Card>
|
||||
<Card title="GitHub" icon="code-branch" href="/github">
|
||||
Set up github for authentication and sync
|
||||
</Card>
|
||||
<Card title="Providers" icon="linkedin" href="/providers/x/x">
|
||||
Set up providers such as Linkedin, X and Reddit
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
<Warning>
|
||||
The container images do not yet provide automatic database "installation"
|
||||
(migrations). This must be done manually outside of the docker containers for now.
|
||||
|
||||
This is being worked on with a high priority.
|
||||
</Warning>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
## Controlling container services
|
||||
The environment variable POSTIZ_APPS defaults to "", which means that all
|
||||
services will be started in a single container. However, you can only start
|
||||
specific services within the docker container by changing this environement variable.
|
||||
|
||||
For most deployments, starting all services is fine. To scale out, you might want
|
||||
to start individual containers for "frontend", "backend", "worker" and "cron".
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<Info>
|
||||
**NOTE:** This page is marked "earlydoc", or "early documentation", which means it might
|
||||
be brief, or contain information about parts of the app that are under heavy development.
|
||||
If you encounter issues with instructions found here, please check out the [support](/support)
|
||||
page for options.
|
||||
</Info>
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
---
|
||||
title: Support
|
||||
---
|
||||
|
||||
Sometimes, things can go wrong, or you need some help!
|
||||
|
||||
Note that the Self Hosted version of Postiz is supported by the community in their
|
||||
free time, on a best-efforts basis. Please post your question and be patient.
|
||||
|
||||
- [Discord](https://discord.com/invite/sf7QjTcX37) - Flexible chat, with screenshots and screen sharing, probably the best option for support.
|
||||
- [GitHub issue](https://github.com/gitroomhq/postiz-app/issues/new/choose) - backup option if you are unable to use Discord.
|
||||
|
||||
## How to effectively ask for support
|
||||
|
||||
Try to follow this guide when asking for support, in this order; **Goal, Environment, Changes, Results**.
|
||||
|
||||
- **Goal:** Start off by explaining what you were trying to do
|
||||
- _I want to schedule a post on Reddit at 6pm_
|
||||
- _I want to run in a Linux container on a Raspberry Pi_
|
||||
- _I want to use a custom domain name_
|
||||
|
||||
- **Environment:** - Share the relevant parts about your environment. Web App issues; Are you using Firefox, Chrome, etc? Installation/Other issues; a Mac, Linux, Windows, how did you install?
|
||||
- _I'm using Firefox on Windows 10_
|
||||
- _I'm using a Raspberry Pi 4 with Ubuntu 20.04, and Node version 18_
|
||||
- _This is a new installation on a Mac_
|
||||
|
||||
- **Changed:** - Most likely something has changed, what is it?
|
||||
- _I updated my browser to the latest version and now ..._
|
||||
- _I found a change in the latest version and now ..._
|
||||
- _I think this used to work, but now..._
|
||||
|
||||
- **Results:** - What happened? What did you expect to happen?
|
||||
- _I see a blank screen_
|
||||
- _I see an error message_
|
||||
- _I see a 404 page_
|
||||
|
|
@ -17,8 +17,8 @@ export default async function AuthLayout({
|
|||
<>
|
||||
<ReturnUrlComponent />
|
||||
<div className="absolute left-0 top-0 z-[0] h-[100vh] w-[100vw] overflow-hidden bg-loginBg bg-contain bg-no-repeat bg-left-top" />
|
||||
<div className="relative z-[1] pr-[100px] flex justify-end items-center h-[100vh] w-[100vw] overflow-hidden">
|
||||
<div className="w-[557px] flex h-[614px] bg-loginBox bg-contain">
|
||||
<div className="relative z-[1] px-3 lg:pr-[100px] xs:mt-[70px] flex justify-center lg:justify-end items-center h-[100vh] w-[100vw] overflow-hidden">
|
||||
<div className="w-full max-w-lg h-[614px] flex flex-col bg-loginBox bg-no-repeat bg-contain">
|
||||
<div className="w-full relative">
|
||||
<div className="custom:fixed custom:text-left custom:left-[20px] custom:justify-start custom:top-[20px] absolute -top-[100px] text-textColor justify-center items-center w-full flex gap-[10px]">
|
||||
<Image
|
||||
|
|
@ -61,7 +61,7 @@ export default async function AuthLayout({
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-[32px] absolute w-[557px] h-[614px] text-textColor">
|
||||
<div className="p-[32px] w-full h-[614px] text-textColor">
|
||||
{children}
|
||||
</div>
|
||||
<div className="flex flex-1 flex-col">
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@
|
|||
--color-custom20: #121b2c;
|
||||
--color-custom21: #506490;
|
||||
--color-custom22: #b91c1c;
|
||||
--color-custom23: #06080d;
|
||||
--color-custom23: #000000;
|
||||
--color-custom24: #eaff00;
|
||||
--color-custom25: #2e3336;
|
||||
--color-custom26: #1d9bf0;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import interClass from '@gitroom/react/helpers/inter.font';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
import './global.scss';
|
||||
import 'react-tooltip/dist/react-tooltip.css';
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import React, {
|
|||
import dayjs from 'dayjs';
|
||||
import { Integrations } from '@gitroom/frontend/components/launches/calendar.context';
|
||||
import clsx from 'clsx';
|
||||
import { commands } from '@uiw/react-md-editor';
|
||||
import { usePreventWindowUnload } from '@gitroom/react/helpers/use.prevent.window.unload';
|
||||
import { deleteDialog } from '@gitroom/react/helpers/delete.dialog';
|
||||
import { useModals } from '@mantine/modals';
|
||||
|
|
@ -27,16 +26,14 @@ import {
|
|||
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
|
||||
import { useMoveToIntegration } from '@gitroom/frontend/components/launches/helpers/use.move.to.integration';
|
||||
import { useExistingData } from '@gitroom/frontend/components/launches/helpers/use.existing.data';
|
||||
import { newImage } from '@gitroom/frontend/components/launches/helpers/new.image.component';
|
||||
import { MultiMediaComponent } from '@gitroom/frontend/components/media/media.component';
|
||||
import { useExpend } from '@gitroom/frontend/components/launches/helpers/use.expend';
|
||||
import { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component';
|
||||
import { PickPlatforms } from '@gitroom/frontend/components/launches/helpers/pick.platform.component';
|
||||
import { ProvidersOptions } from '@gitroom/frontend/components/launches/providers.options';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import useSWR, { useSWRConfig } from 'swr';
|
||||
import useSWR from 'swr';
|
||||
import { useToaster } from '@gitroom/react/toaster/toaster';
|
||||
import { postSelector } from '@gitroom/frontend/components/post-url-selector/post.url.selector';
|
||||
import { UpDownArrow } from '@gitroom/frontend/components/launches/up.down.arrow';
|
||||
import { DatePicker } from '@gitroom/frontend/components/launches/helpers/date.picker';
|
||||
import { arrayMoveImmutable } from 'array-move';
|
||||
|
|
@ -56,10 +53,10 @@ export const AddEditModal: FC<{
|
|||
date: dayjs.Dayjs;
|
||||
integrations: Integrations[];
|
||||
reopenModal: () => void;
|
||||
mutate: () => void;
|
||||
}> = (props) => {
|
||||
const { date, integrations, reopenModal } = props;
|
||||
const { date, integrations, reopenModal, mutate } = props;
|
||||
const [dateState, setDateState] = useState(date);
|
||||
const { mutate } = useSWRConfig();
|
||||
|
||||
// hook to open a new modal
|
||||
const modal = useModals();
|
||||
|
|
@ -246,7 +243,7 @@ export const AddEditModal: FC<{
|
|||
await fetch(`/posts/${existingData.group}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
mutate('/posts');
|
||||
mutate();
|
||||
modal.closeAll();
|
||||
return;
|
||||
}
|
||||
|
|
@ -324,7 +321,7 @@ export const AddEditModal: FC<{
|
|||
|
||||
existingData.group = uuidv4();
|
||||
|
||||
mutate('/posts');
|
||||
mutate();
|
||||
toaster.show(
|
||||
!existingData.integration
|
||||
? 'Added successfully'
|
||||
|
|
|
|||
|
|
@ -17,16 +17,33 @@ import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
|
|||
import { Post, Integration } from '@prisma/client';
|
||||
import { useRouter, useSearchParams } from 'next/navigation';
|
||||
import { isGeneral } from '@gitroom/react/helpers/is.general';
|
||||
import isoWeek from 'dayjs/plugin/isoWeek';
|
||||
import weekOfYear from 'dayjs/plugin/weekOfYear';
|
||||
|
||||
dayjs.extend(isoWeek);
|
||||
dayjs.extend(weekOfYear);
|
||||
|
||||
const CalendarContext = createContext({
|
||||
currentWeek: dayjs().week(),
|
||||
currentYear: dayjs().year(),
|
||||
currentMonth: dayjs().month(),
|
||||
comments: [] as Array<{ date: string; total: number }>,
|
||||
integrations: [] as Integrations[],
|
||||
trendings: [] as string[],
|
||||
posts: [] as Array<Post & { integration: Integration }>,
|
||||
setFilters: (filters: { currentWeek: number; currentYear: number }) => {},
|
||||
changeDate: (id: string, date: dayjs.Dayjs) => {},
|
||||
reloadCalendarView: () => {/** empty **/},
|
||||
display: 'week',
|
||||
setFilters: (filters: {
|
||||
currentWeek: number;
|
||||
currentYear: number;
|
||||
currentMonth: number;
|
||||
display: 'week' | 'month';
|
||||
}) => {
|
||||
/** empty **/
|
||||
},
|
||||
changeDate: (id: string, date: dayjs.Dayjs) => {
|
||||
/** empty **/
|
||||
},
|
||||
});
|
||||
|
||||
export interface Integrations {
|
||||
|
|
@ -40,28 +57,21 @@ export interface Integrations {
|
|||
}
|
||||
|
||||
function getWeekNumber(date: Date) {
|
||||
// Copy date so don't modify original
|
||||
const targetDate = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
|
||||
// Set to nearest Thursday: current date + 4 - current day number
|
||||
// Make Sunday's day number 7
|
||||
targetDate.setUTCDate(targetDate.getUTCDate() + 4 - (targetDate.getUTCDay() || 7));
|
||||
// Get first day of year
|
||||
const yearStart = new Date(Date.UTC(targetDate.getUTCFullYear(), 0, 1));
|
||||
// Calculate full weeks to nearest Thursday
|
||||
return Math.ceil((((targetDate.getTime() - yearStart.getTime()) / 86400000) + 1) / 7);
|
||||
}
|
||||
|
||||
function isISOWeek(date: Date, weekNumber: number): boolean {
|
||||
// Copy date so don't modify original
|
||||
const targetDate = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
|
||||
// Set to nearest Thursday: current date + 4 - current day number
|
||||
// Make Sunday's day number 7
|
||||
targetDate.setUTCDate(targetDate.getUTCDate() + 4 - (targetDate.getUTCDay() || 7));
|
||||
// Get first day of year
|
||||
const yearStart = new Date(Date.UTC(targetDate.getUTCFullYear(), 0, 1));
|
||||
// Calculate full weeks to nearest Thursday
|
||||
const isoWeekNo = Math.ceil((((targetDate.getTime() - yearStart.getTime()) / 86400000) + 1) / 7);
|
||||
return isoWeekNo === weekNumber;
|
||||
// Copy date so don't modify original
|
||||
const targetDate = new Date(
|
||||
Date.UTC(date.getFullYear(), date.getMonth(), date.getDate())
|
||||
);
|
||||
// Set to nearest Thursday: current date + 4 - current day number
|
||||
// Make Sunday's day number 7
|
||||
targetDate.setUTCDate(
|
||||
targetDate.getUTCDate() + 4 - (targetDate.getUTCDay() || 7)
|
||||
);
|
||||
// Get first day of year
|
||||
const yearStart = new Date(Date.UTC(targetDate.getUTCFullYear(), 0, 1));
|
||||
// Calculate full weeks to nearest Thursday
|
||||
return Math.ceil(
|
||||
((targetDate.getTime() - yearStart.getTime()) / 86400000 + 1) / 7
|
||||
);
|
||||
}
|
||||
|
||||
export const CalendarWeekProvider: FC<{
|
||||
|
|
@ -70,64 +80,72 @@ export const CalendarWeekProvider: FC<{
|
|||
}> = ({ children, integrations }) => {
|
||||
const fetch = useFetch();
|
||||
const [internalData, setInternalData] = useState([] as any[]);
|
||||
const [trendings, setTrendings] = useState<string[]>([]);
|
||||
const { mutate } = useSWRConfig();
|
||||
const [trendings] = useState<string[]>([]);
|
||||
const searchParams = useSearchParams();
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (isGeneral()) {
|
||||
return [];
|
||||
}
|
||||
setTrendings(await (await fetch('/posts/predict-trending')).json());
|
||||
})();
|
||||
}, []);
|
||||
|
||||
const display = searchParams.get('month') ? 'month' : 'week';
|
||||
const [filters, setFilters] = useState({
|
||||
currentWeek: +(searchParams.get('week') || getWeekNumber(new Date())),
|
||||
currentWeek:
|
||||
display === 'week'
|
||||
? +(searchParams.get('week') || getWeekNumber(new Date()))
|
||||
: 0,
|
||||
currentMonth:
|
||||
display === 'week' ? 0 : +(searchParams.get('month') || dayjs().month()),
|
||||
currentYear: +(searchParams.get('year') || dayjs().year()),
|
||||
display,
|
||||
});
|
||||
|
||||
const isIsoWeek = useMemo(() => {
|
||||
return isISOWeek(new Date(), filters.currentWeek);
|
||||
}, [filters]);
|
||||
|
||||
const setFiltersWrapper = useCallback(
|
||||
(filters: { currentWeek: number; currentYear: number }) => {
|
||||
setFilters(filters);
|
||||
router.replace(
|
||||
`/launches?week=${filters.currentWeek}&year=${filters.currentYear}`
|
||||
);
|
||||
setTimeout(() => {
|
||||
mutate('/posts');
|
||||
});
|
||||
},
|
||||
[filters]
|
||||
);
|
||||
|
||||
const params = useMemo(() => {
|
||||
return new URLSearchParams({
|
||||
week: filters.currentWeek.toString(),
|
||||
year: filters.currentYear.toString(),
|
||||
isIsoWeek: isIsoWeek.toString(),
|
||||
}).toString();
|
||||
return new URLSearchParams(
|
||||
filters.currentWeek
|
||||
? {
|
||||
week: filters.currentWeek.toString(),
|
||||
year: filters.currentYear.toString(),
|
||||
}
|
||||
: {
|
||||
year: filters.currentYear.toString(),
|
||||
month: (filters.currentMonth + 1).toString(),
|
||||
}
|
||||
).toString();
|
||||
}, [filters]);
|
||||
|
||||
const loadData = useCallback(
|
||||
async (url: string) => {
|
||||
const data = (await fetch(`${url}?${params}`)).json();
|
||||
async () => {
|
||||
const data = (await fetch(`/posts?${params}`)).json();
|
||||
return data;
|
||||
},
|
||||
[filters]
|
||||
[filters, params]
|
||||
);
|
||||
|
||||
const swr = useSWR(`/posts`, loadData, {
|
||||
const swr = useSWR(`/posts-${params}`, loadData, {
|
||||
refreshInterval: 3600000,
|
||||
refreshWhenOffline: false,
|
||||
refreshWhenHidden: false,
|
||||
revalidateOnFocus: false,
|
||||
});
|
||||
|
||||
const setFiltersWrapper = useCallback(
|
||||
(filters: {
|
||||
currentWeek: number;
|
||||
currentYear: number;
|
||||
currentMonth: number;
|
||||
display: 'week' | 'month';
|
||||
}) => {
|
||||
setFilters(filters);
|
||||
setInternalData([]);
|
||||
window.history.replaceState(
|
||||
null,
|
||||
'',
|
||||
`/launches?${
|
||||
filters.currentWeek
|
||||
? `week=${filters.currentWeek}`
|
||||
: `month=${filters.currentMonth}`
|
||||
}&year=${filters.currentYear}`
|
||||
);
|
||||
},
|
||||
[filters, swr.mutate]
|
||||
);
|
||||
|
||||
const { isLoading } = swr;
|
||||
const { posts, comments } = swr?.data || { posts: [], comments: [] };
|
||||
|
||||
|
|
@ -158,6 +176,7 @@ export const CalendarWeekProvider: FC<{
|
|||
<CalendarContext.Provider
|
||||
value={{
|
||||
trendings,
|
||||
reloadCalendarView: swr.mutate,
|
||||
...filters,
|
||||
posts: isLoading ? [] : internalData,
|
||||
integrations,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,13 @@
|
|||
'use client';
|
||||
|
||||
import React, { FC, useCallback, useMemo } from 'react';
|
||||
import React, {
|
||||
FC,
|
||||
Fragment,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import {
|
||||
Integrations,
|
||||
useCalendar,
|
||||
|
|
@ -17,14 +24,16 @@ import { Integration, Post, State } from '@prisma/client';
|
|||
import { useAddProvider } from '@gitroom/frontend/components/launches/add.provider.component';
|
||||
import { CommentComponent } from '@gitroom/frontend/components/launches/comments/comment.component';
|
||||
import { useSWRConfig } from 'swr';
|
||||
import { useIntersectionObserver } from '@uidotdev/usehooks';
|
||||
import { useToaster } from '@gitroom/react/toaster/toaster';
|
||||
import { useUser } from '@gitroom/frontend/components/layout/user.context';
|
||||
import { IntegrationContext } from '@gitroom/frontend/components/launches/helpers/use.integration';
|
||||
import { PreviewPopup } from '@gitroom/frontend/components/marketplace/special.message';
|
||||
import isSameOrAfter from 'dayjs/plugin/isSameOrAfter';
|
||||
import isSameOrBefore from 'dayjs/plugin/isSameOrBefore';
|
||||
dayjs.extend(isSameOrAfter);
|
||||
dayjs.extend(isSameOrBefore);
|
||||
|
||||
export const days = [
|
||||
'',
|
||||
'Monday',
|
||||
'Tuesday',
|
||||
'Wednesday',
|
||||
|
|
@ -33,191 +42,155 @@ export const days = [
|
|||
'Saturday',
|
||||
'Sunday',
|
||||
];
|
||||
export const hours = [
|
||||
'00:00',
|
||||
'01:00',
|
||||
'02:00',
|
||||
'03:00',
|
||||
'04:00',
|
||||
'05:00',
|
||||
'06:00',
|
||||
'07:00',
|
||||
'08:00',
|
||||
'09:00',
|
||||
'10:00',
|
||||
'11:00',
|
||||
'12:00',
|
||||
'13:00',
|
||||
'14:00',
|
||||
'15:00',
|
||||
'16:00',
|
||||
'17:00',
|
||||
'18:00',
|
||||
'19:00',
|
||||
'20:00',
|
||||
'21:00',
|
||||
'22:00',
|
||||
'23:00',
|
||||
];
|
||||
export const hours = Array.from({ length: 24 }, (_, i) => i);
|
||||
|
||||
export const WeekView = () => {
|
||||
const { currentYear, currentWeek } = useCalendar();
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-screen overflow-hidden text-textColor flex-1">
|
||||
<div className="flex-1">
|
||||
<div className="grid grid-cols-8 bg-customColor31 gap-[1px] border-customColor31 border rounded-[10px]">
|
||||
<div className="bg-customColor20 sticky top-0 z-10 bg-gray-900"></div>
|
||||
{days.map((day, index) => (
|
||||
<div
|
||||
key={day}
|
||||
className="sticky top-0 z-10 bg-customColor20 p-2 text-center"
|
||||
>
|
||||
<div>{day}</div>
|
||||
</div>
|
||||
))}
|
||||
{hours.map((hour) => (
|
||||
<Fragment key={hour}>
|
||||
<div className="p-2 pr-4 bg-secondary text-center items-center justify-center flex">
|
||||
{hour.toString().padStart(2, '0')}:00
|
||||
</div>
|
||||
{days.map((day, indexDay) => (
|
||||
<Fragment key={`${day}-${hour}`}>
|
||||
<div className="relative bg-secondary">
|
||||
<CalendarColumn
|
||||
getDate={dayjs()
|
||||
.year(currentYear)
|
||||
.week(currentWeek)
|
||||
.day(indexDay + 1)
|
||||
.hour(hour)
|
||||
.startOf('hour')}
|
||||
/>
|
||||
</div>
|
||||
</Fragment>
|
||||
))}
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const MonthView = () => {
|
||||
const { currentYear, currentMonth } = useCalendar();
|
||||
|
||||
const calendarDays = useMemo(() => {
|
||||
const startOfMonth = dayjs(new Date(currentYear, currentMonth, 1));
|
||||
|
||||
// Calculate the day offset for Monday (isoWeekday() returns 1 for Monday)
|
||||
const startDayOfWeek = startOfMonth.isoWeekday(); // 1 for Monday, 7 for Sunday
|
||||
const daysBeforeMonth = startDayOfWeek - 1; // Days to show from the previous month
|
||||
|
||||
// Get the start date (Monday of the first week that includes this month)
|
||||
const startDate = startOfMonth.subtract(daysBeforeMonth, 'day');
|
||||
|
||||
// Create an array to hold the calendar days (6 weeks * 7 days = 42 days max)
|
||||
const calendarDays = [];
|
||||
let currentDay = startDate;
|
||||
|
||||
for (let i = 0; i < 42; i++) {
|
||||
let label = 'current-month';
|
||||
if (currentDay.month() < currentMonth) label = 'previous-month';
|
||||
if (currentDay.month() > currentMonth) label = 'next-month';
|
||||
|
||||
calendarDays.push({
|
||||
day: currentDay,
|
||||
label,
|
||||
});
|
||||
|
||||
// Move to the next day
|
||||
currentDay = currentDay.add(1, 'day');
|
||||
}
|
||||
|
||||
return calendarDays;
|
||||
}, [currentYear, currentMonth]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-screen overflow-hidden text-textColor flex-1">
|
||||
<div className="flex-1 flex">
|
||||
<div className="grid grid-cols-7 grid-rows-[40px_auto] bg-customColor31 gap-[1px] border-customColor31 border rounded-[10px] flex-1">
|
||||
{days.map((day) => (
|
||||
<div
|
||||
key={day}
|
||||
className="sticky top-0 z-10 bg-customColor20 p-2 text-center"
|
||||
>
|
||||
<div>{day}</div>
|
||||
</div>
|
||||
))}
|
||||
{calendarDays.map((date, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="bg-secondary text-center items-center justify-center flex min-h-[100px]"
|
||||
>
|
||||
<CalendarColumn
|
||||
getDate={dayjs(date.day).endOf('day')}
|
||||
randomHour={true}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Calendar = () => {
|
||||
const { currentWeek, currentYear, comments } = useCalendar();
|
||||
|
||||
const firstDay = useMemo(() => {
|
||||
return dayjs().year(currentYear).isoWeek(currentWeek).isoWeekday(1);
|
||||
}, [currentYear, currentWeek]);
|
||||
const { display } = useCalendar();
|
||||
|
||||
return (
|
||||
<DNDProvider>
|
||||
<div className="select-none">
|
||||
<div className="grid grid-cols-8 text-center border-tableBorder border-r">
|
||||
{days.map((day, index) => (
|
||||
<div
|
||||
className="border-tableBorder gap-[4px] border-l border-b h-[36px] border-t flex items-center justify-center bg-input text-[14px] sticky top-0 z-[100]"
|
||||
key={day}
|
||||
>
|
||||
<div>{day} </div>
|
||||
<div className="text-[12px]">
|
||||
{day && `(${firstDay.add(index - 1, 'day').format('DD/MM')})`}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{hours.map((hour) =>
|
||||
days.map((day, index) => (
|
||||
<>
|
||||
{index === 0 ? (
|
||||
<div
|
||||
className="border-tableBorder border-l border-b h-[216px]"
|
||||
key={day + hour}
|
||||
>
|
||||
{['00', '10', '20', '30', '40', '50'].map((num) => (
|
||||
<div
|
||||
key={day + hour + num}
|
||||
className="h-[calc(216px/6)] text-[12px] flex justify-center items-center"
|
||||
>
|
||||
{hour.split(':')[0] + ':' + num}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className="group relative border-tableBorder border-l border-b h-[216px] flex flex-col overflow-hidden"
|
||||
key={day + hour}
|
||||
>
|
||||
<CommentBox
|
||||
totalComments={
|
||||
comments.find(
|
||||
(p) =>
|
||||
dayjs
|
||||
.utc(p.date)
|
||||
.local()
|
||||
.format('YYYY-MM-DD HH:mm') ===
|
||||
dayjs()
|
||||
.isoWeek(currentWeek)
|
||||
.isoWeekday(index + 1)
|
||||
.hour(+hour.split(':')[0] - 1)
|
||||
.minute(0)
|
||||
.format('YYYY-MM-DD HH:mm')
|
||||
)?.total || 0
|
||||
}
|
||||
date={dayjs()
|
||||
.isoWeek(currentWeek)
|
||||
.isoWeekday(index + 1)
|
||||
.hour(+hour.split(':')[0] - 1)
|
||||
.minute(0)}
|
||||
/>
|
||||
{['00', '10', '20', '30', '40', '50'].map((num) => (
|
||||
<CalendarColumn
|
||||
key={day + hour + num + currentWeek + currentYear}
|
||||
day={index}
|
||||
hour={hour.split(':')[0] + ':' + num}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{display === 'week' ? <WeekView /> : <MonthView />}
|
||||
</DNDProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export const CalendarColumn: FC<{ day: number; hour: string }> = (props) => {
|
||||
const { day, hour } = props;
|
||||
const { currentWeek, currentYear } = useCalendar();
|
||||
|
||||
const getDate = useMemo(() => {
|
||||
const date =
|
||||
dayjs()
|
||||
.year(currentYear)
|
||||
.isoWeek(currentWeek)
|
||||
.isoWeekday(day)
|
||||
.format('YYYY-MM-DD') +
|
||||
'T' +
|
||||
hour +
|
||||
':00';
|
||||
return dayjs(date);
|
||||
}, [currentWeek]);
|
||||
|
||||
const isBeforeNow = useMemo(() => {
|
||||
return getDate.isBefore(dayjs());
|
||||
}, [getDate]);
|
||||
|
||||
const [ref, entry] = useIntersectionObserver({
|
||||
threshold: 0.5,
|
||||
root: null,
|
||||
rootMargin: '0px',
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="w-full h-full" ref={ref}>
|
||||
{!entry?.isIntersecting ? (
|
||||
<div />
|
||||
) : (
|
||||
<CalendarColumnRender {...props} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
const CalendarColumnRender: FC<{ day: number; hour: string }> = (props) => {
|
||||
const { day, hour } = props;
|
||||
export const CalendarColumn: FC<{
|
||||
getDate: dayjs.Dayjs;
|
||||
randomHour?: boolean;
|
||||
}> = (props) => {
|
||||
const { getDate, randomHour } = props;
|
||||
const user = useUser();
|
||||
const {
|
||||
currentWeek,
|
||||
currentYear,
|
||||
integrations,
|
||||
posts,
|
||||
trendings,
|
||||
changeDate,
|
||||
display,
|
||||
reloadCalendarView,
|
||||
} = useCalendar();
|
||||
|
||||
const toaster = useToaster();
|
||||
const modal = useModals();
|
||||
const fetch = useFetch();
|
||||
|
||||
const getDate = useMemo(() => {
|
||||
const date =
|
||||
dayjs()
|
||||
.year(currentYear)
|
||||
.isoWeek(currentWeek)
|
||||
.isoWeekday(day)
|
||||
.format('YYYY-MM-DD') +
|
||||
'T' +
|
||||
hour +
|
||||
':00';
|
||||
return dayjs(date);
|
||||
}, [currentWeek]);
|
||||
|
||||
const postList = useMemo(() => {
|
||||
return posts.filter((post) => {
|
||||
return dayjs
|
||||
.utc(post.publishDate)
|
||||
.local()
|
||||
.isBetween(getDate, getDate.add(59, 'minute'), 'minute', '[)');
|
||||
const pList = dayjs.utc(post.publishDate).local();
|
||||
const check =
|
||||
display === 'week'
|
||||
? pList.isSameOrAfter(getDate.startOf('hour')) &&
|
||||
pList.isBefore(getDate.endOf('hour'))
|
||||
: pList.format('DD/MM/YYYY') === getDate.format('DD/MM/YYYY');
|
||||
|
||||
return check;
|
||||
});
|
||||
}, [posts]);
|
||||
}, [posts, display, getDate]);
|
||||
|
||||
const canBeTrending = useMemo(() => {
|
||||
return !!trendings.find((trend) => {
|
||||
|
|
@ -229,7 +202,7 @@ const CalendarColumnRender: FC<{ day: number; hour: string }> = (props) => {
|
|||
}, [trendings]);
|
||||
|
||||
const isBeforeNow = useMemo(() => {
|
||||
return getDate.isBefore(dayjs());
|
||||
return getDate.startOf('hour').isBefore(dayjs().startOf('hour'));
|
||||
}, [getDate]);
|
||||
|
||||
const [{ canDrop }, drop] = useDrop(() => ({
|
||||
|
|
@ -311,6 +284,7 @@ const CalendarColumnRender: FC<{ day: number; hour: string }> = (props) => {
|
|||
return previewPublication(post);
|
||||
}
|
||||
const data = await (await fetch(`/posts/${post.id}`)).json();
|
||||
const publishDate = dayjs.utc(data.posts[0].publishDate).local();
|
||||
|
||||
modal.openModal({
|
||||
closeOnClickOutside: false,
|
||||
|
|
@ -323,11 +297,12 @@ const CalendarColumnRender: FC<{ day: number; hour: string }> = (props) => {
|
|||
<ExistingDataContextProvider value={data}>
|
||||
<AddEditModal
|
||||
reopenModal={editPost(post)}
|
||||
mutate={reloadCalendarView}
|
||||
integrations={integrations
|
||||
.slice(0)
|
||||
.filter((f) => f.id === data.integration)
|
||||
.map((p) => ({ ...p, picture: data.integrationPicture }))}
|
||||
date={getDate}
|
||||
date={publishDate}
|
||||
/>
|
||||
</ExistingDataContextProvider>
|
||||
),
|
||||
|
|
@ -349,84 +324,103 @@ const CalendarColumnRender: FC<{ day: number; hour: string }> = (props) => {
|
|||
children: (
|
||||
<AddEditModal
|
||||
integrations={integrations.slice(0).map((p) => ({ ...p }))}
|
||||
date={getDate}
|
||||
mutate={reloadCalendarView}
|
||||
date={
|
||||
randomHour ? getDate.hour(Math.floor(Math.random() * 24)) : getDate
|
||||
}
|
||||
reopenModal={() => ({})}
|
||||
/>
|
||||
),
|
||||
size: '80%',
|
||||
// title: `Adding posts for ${getDate.format('DD/MM/YYYY HH:mm')}`,
|
||||
});
|
||||
}, [integrations]);
|
||||
}, [integrations, getDate]);
|
||||
|
||||
const addProvider = useAddProvider();
|
||||
|
||||
return (
|
||||
<div className="relative flex flex-col w-full min-h-full">
|
||||
<div className="flex flex-col w-full min-h-full" ref={drop}>
|
||||
{display === 'month' && (
|
||||
<div className={clsx('pt-[5px]', isBeforeNow && 'bg-customColor23')}>
|
||||
{getDate.date()}
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
{...(canBeTrending
|
||||
? {
|
||||
'data-tooltip-id': 'tooltip',
|
||||
'data-tooltip-content': 'Predicted GitHub Trending Change',
|
||||
}
|
||||
: {})}
|
||||
ref={drop}
|
||||
className={clsx(
|
||||
'flex-col flex-1 text-[12px] pointer w-full overflow-hidden justify-center overflow-x-auto flex scrollbar scrollbar-thumb-tableBorder scrollbar-track-secondary',
|
||||
isBeforeNow && 'bg-customColor23',
|
||||
canDrop && 'bg-white/80',
|
||||
canBeTrending && 'bg-customColor24'
|
||||
'relative flex flex-col flex-1',
|
||||
canDrop && 'bg-white/80'
|
||||
)}
|
||||
>
|
||||
{postList.map((post) => (
|
||||
<div
|
||||
{...(canBeTrending
|
||||
? {
|
||||
'data-tooltip-id': 'tooltip',
|
||||
'data-tooltip-content': 'Predicted GitHub Trending Change',
|
||||
}
|
||||
: {})}
|
||||
className={clsx(
|
||||
'flex-col text-[12px] pointer w-full cursor-pointer overflow-hidden overflow-x-auto flex scrollbar scrollbar-thumb-tableBorder scrollbar-track-secondary',
|
||||
isBeforeNow && 'bg-customColor23 flex-1',
|
||||
canBeTrending && 'bg-customColor24'
|
||||
)}
|
||||
>
|
||||
{postList.map((post) => (
|
||||
<div
|
||||
key={post.id}
|
||||
className={clsx(
|
||||
'text-textColor p-[2.5px] relative flex flex-col justify-center items-center'
|
||||
)}
|
||||
>
|
||||
<div className="relative w-full flex flex-col items-center p-[2.5px]">
|
||||
<CalendarItem
|
||||
isBeforeNow={isBeforeNow}
|
||||
date={getDate}
|
||||
state={post.state}
|
||||
editPost={editPost(post)}
|
||||
post={post}
|
||||
integrations={integrations}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{!isBeforeNow && (
|
||||
<div
|
||||
key={post.id}
|
||||
className={clsx(
|
||||
'text-textColor p-[2.5px] relative flex flex-col justify-center items-center'
|
||||
)}
|
||||
className="pb-[2.5px] px-[5px] flex-1 flex"
|
||||
onClick={integrations.length ? addModal : addProvider}
|
||||
>
|
||||
<div className="relative w-full flex flex-col items-center p-[2.5px]">
|
||||
<CalendarItem
|
||||
date={getDate}
|
||||
state={post.state}
|
||||
editPost={editPost(post)}
|
||||
post={post}
|
||||
integrations={integrations}
|
||||
<div
|
||||
className={clsx(
|
||||
display === 'month'
|
||||
? 'flex-1 min-h-[40px] w-full'
|
||||
: !postList.length
|
||||
? 'h-full w-full absolute left-0 top-0 p-[5px]'
|
||||
: 'min-h-[40px] w-full',
|
||||
'flex items-center justify-center cursor-pointer pb-[2.5px]'
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
'hover:before:content-["+"] w-full h-full text-seventh rounded-[10px] hover:border hover:border-seventh flex justify-center items-center'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
)}
|
||||
</div>
|
||||
{!isBeforeNow && (
|
||||
<div className="pb-[2.5px] px-[5px]">
|
||||
<div
|
||||
className={clsx(
|
||||
!postList.length
|
||||
? 'h-full w-full absolute left-0 top-0 p-[5px]'
|
||||
: 'h-[40px]',
|
||||
'flex items-center justify-center cursor-pointer pb-[2.5px]'
|
||||
)}
|
||||
>
|
||||
<div
|
||||
onClick={integrations.length ? addModal : addProvider}
|
||||
className={clsx(
|
||||
'hover:before:content-["+"] w-full h-full text-seventh rounded-[10px] hover:border hover:border-seventh flex justify-center items-center'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const CalendarItem: FC<{
|
||||
date: dayjs.Dayjs;
|
||||
isBeforeNow: boolean;
|
||||
editPost: () => void;
|
||||
integrations: Integrations[];
|
||||
state: State;
|
||||
post: Post & { integration: Integration };
|
||||
}> = (props) => {
|
||||
const { editPost, post, date, integrations, state } = props;
|
||||
const { editPost, post, date, isBeforeNow, state } = props;
|
||||
const [{ opacity }, dragRef] = useDrag(
|
||||
() => ({
|
||||
type: 'post',
|
||||
|
|
@ -444,7 +438,7 @@ const CalendarItem: FC<{
|
|||
className={clsx(
|
||||
'gap-[5px] w-full flex h-full flex-1 rounded-[10px] border border-seventh px-[5px] p-[2.5px]',
|
||||
'relative',
|
||||
state === 'DRAFT' && '!grayscale'
|
||||
(state === 'DRAFT' || isBeforeNow) && '!grayscale'
|
||||
)}
|
||||
style={{ opacity }}
|
||||
>
|
||||
|
|
@ -458,7 +452,10 @@ const CalendarItem: FC<{
|
|||
src={`/icons/platforms/${post.integration?.providerIdentifier}.png`}
|
||||
/>
|
||||
</div>
|
||||
<div className="whitespace-pre-wrap line-clamp-3">{post.content}</div>
|
||||
<div className="whitespace-pre-wrap line-clamp-3">
|
||||
{state === 'DRAFT' ? 'Draft: ' : ''}
|
||||
{post.content}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,32 +1,109 @@
|
|||
'use client';
|
||||
import { useCalendar } from '@gitroom/frontend/components/launches/calendar.context';
|
||||
import clsx from 'clsx';
|
||||
import dayjs from 'dayjs';
|
||||
import {useCallback} from "react";
|
||||
import { useCallback } from 'react';
|
||||
|
||||
export const Filters = () => {
|
||||
const week = useCalendar();
|
||||
const betweenDates =
|
||||
dayjs().year(week.currentYear).isoWeek(week.currentWeek).startOf('isoWeek').format('DD/MM/YYYY') +
|
||||
' - ' +
|
||||
dayjs().year(week.currentYear).isoWeek(week.currentWeek).endOf('isoWeek').format('DD/MM/YYYY');
|
||||
week.display === 'week'
|
||||
? dayjs()
|
||||
.year(week.currentYear)
|
||||
.isoWeek(week.currentWeek)
|
||||
.startOf('isoWeek')
|
||||
.format('DD/MM/YYYY') +
|
||||
' - ' +
|
||||
dayjs()
|
||||
.year(week.currentYear)
|
||||
.isoWeek(week.currentWeek)
|
||||
.endOf('isoWeek')
|
||||
.format('DD/MM/YYYY')
|
||||
: dayjs()
|
||||
.year(week.currentYear)
|
||||
.month(week.currentMonth)
|
||||
.startOf('month')
|
||||
.format('DD/MM/YYYY') +
|
||||
' - ' +
|
||||
dayjs()
|
||||
.year(week.currentYear)
|
||||
.month(week.currentMonth)
|
||||
.endOf('month')
|
||||
.format('DD/MM/YYYY');
|
||||
|
||||
const nextWeek = useCallback(() => {
|
||||
week.setFilters({
|
||||
currentWeek: week.currentWeek === 52 ? 1 : week.currentWeek + 1,
|
||||
currentYear: week.currentWeek === 52 ? week.currentYear + 1 : week.currentYear,
|
||||
});
|
||||
}, [week.currentWeek, week.currentYear]);
|
||||
const setWeek = useCallback(() => {
|
||||
week.setFilters({
|
||||
currentWeek: dayjs().isoWeek(),
|
||||
currentYear: dayjs().year(),
|
||||
currentMonth: 0,
|
||||
display: 'week',
|
||||
});
|
||||
}, [week]);
|
||||
|
||||
const previousWeek = useCallback(() => {
|
||||
week.setFilters({
|
||||
currentWeek: week.currentWeek === 1 ? 52 : week.currentWeek - 1,
|
||||
currentYear: week.currentWeek === 1 ? week.currentYear - 1 : week.currentYear,
|
||||
});
|
||||
}, [week.currentWeek, week.currentYear]);
|
||||
const setMonth = useCallback(() => {
|
||||
week.setFilters({
|
||||
currentMonth: dayjs().month(),
|
||||
currentWeek: 0,
|
||||
currentYear: dayjs().year(),
|
||||
display: 'month',
|
||||
});
|
||||
}, [week]);
|
||||
|
||||
const next = useCallback(() => {
|
||||
week.setFilters({
|
||||
currentWeek:
|
||||
week.display === 'week'
|
||||
? week.currentWeek === 52
|
||||
? 1
|
||||
: week.currentWeek + 1
|
||||
: 0,
|
||||
currentYear:
|
||||
week.display === 'week'
|
||||
? week.currentWeek === 52
|
||||
? week.currentYear + 1
|
||||
: week.currentYear
|
||||
: week.currentMonth === 11
|
||||
? week.currentYear + 1
|
||||
: week.currentYear,
|
||||
display: week.display as any,
|
||||
currentMonth:
|
||||
week.display === 'week'
|
||||
? 0
|
||||
: week.currentMonth === 11
|
||||
? 0
|
||||
: week.currentMonth + 1,
|
||||
});
|
||||
}, [week.display, week.currentMonth, week.currentWeek, week.currentYear]);
|
||||
|
||||
const previous = useCallback(() => {
|
||||
week.setFilters({
|
||||
currentWeek:
|
||||
week.display === 'week'
|
||||
? week.currentWeek === 1
|
||||
? 52
|
||||
: week.currentWeek - 1
|
||||
: 0,
|
||||
currentYear:
|
||||
week.display === 'week'
|
||||
? week.currentWeek === 1
|
||||
? week.currentYear - 1
|
||||
: week.currentYear
|
||||
: week.currentMonth === 0
|
||||
? week.currentYear - 1
|
||||
: week.currentYear,
|
||||
display: week.display as any,
|
||||
currentMonth:
|
||||
week.display === 'week'
|
||||
? 0
|
||||
: week.currentMonth === 0
|
||||
? 11
|
||||
: week.currentMonth - 1,
|
||||
});
|
||||
}, [week.display, week.currentMonth, week.currentWeek, week.currentYear]);
|
||||
|
||||
return (
|
||||
<div className="h-[20px] text-textColor flex gap-[8px] items-center select-none">
|
||||
<div onClick={previousWeek}>
|
||||
<div className="text-textColor flex gap-[8px] items-center select-none">
|
||||
<div onClick={previous}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
|
|
@ -40,8 +117,12 @@ export const Filters = () => {
|
|||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>Week {week.currentWeek}</div>
|
||||
<div onClick={nextWeek}>
|
||||
<div className="w-[80px] text-center">
|
||||
{week.display === 'week'
|
||||
? `Week ${week.currentWeek}`
|
||||
: `${dayjs().month(week.currentMonth).format('MMMM')}`}
|
||||
</div>
|
||||
<div onClick={next}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
|
|
@ -55,7 +136,25 @@ export const Filters = () => {
|
|||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>{betweenDates}</div>
|
||||
<div className="flex-1">{betweenDates}</div>
|
||||
<div
|
||||
className={clsx(
|
||||
'border border-tableBorder p-[10px]',
|
||||
week.display === 'week' && 'bg-tableBorder'
|
||||
)}
|
||||
onClick={setWeek}
|
||||
>
|
||||
Week
|
||||
</div>
|
||||
<div
|
||||
className={clsx(
|
||||
'border border-tableBorder p-[10px]',
|
||||
week.display === 'month' && 'bg-tableBorder'
|
||||
)}
|
||||
onClick={setMonth}
|
||||
>
|
||||
Month
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -13,13 +13,12 @@ import { LoadingComponent } from '@gitroom/frontend/components/layout/loading';
|
|||
import clsx from 'clsx';
|
||||
import { useUser } from '../layout/user.context';
|
||||
import { Menu } from '@gitroom/frontend/components/launches/menu/menu';
|
||||
import { GeneratorComponent } from '@gitroom/frontend/components/launches/generator/generator';
|
||||
import { useRouter, useSearchParams } from 'next/navigation';
|
||||
import { Integration } from '@prisma/client';
|
||||
import ImageWithFallback from '@gitroom/react/helpers/image.with.fallback';
|
||||
import { useToaster } from '@gitroom/react/toaster/toaster';
|
||||
import { useFireEvents } from '@gitroom/helpers/utils/use.fire.events';
|
||||
import { NewCalendarComponent } from '@gitroom/frontend/components/launches/new.calendar.component';
|
||||
import { Calendar } from './calendar';
|
||||
|
||||
export const LaunchesComponent = () => {
|
||||
const fetch = useFetch();
|
||||
|
|
@ -117,7 +116,7 @@ export const LaunchesComponent = () => {
|
|||
<CalendarWeekProvider integrations={sortedIntegrations}>
|
||||
<div className="flex flex-1 flex-col">
|
||||
<div className="flex flex-1 relative">
|
||||
<div className="outline-none absolute w-full h-full grid grid-cols-[220px_minmax(0,1fr)] gap-[30px] overflow-hidden overflow-y-auto scrollbar scrollbar-thumb-tableBorder scrollbar-track-secondary">
|
||||
<div className="outline-none w-full h-full grid grid-cols-[220px_minmax(0,1fr)] gap-[30px] scrollbar scrollbar-thumb-tableBorder scrollbar-track-secondary">
|
||||
<div className="w-[220px] bg-third p-[16px] flex flex-col gap-[24px] min-h-[100%]">
|
||||
<h2 className="text-[20px]">Channels</h2>
|
||||
<div className="gap-[16px] flex flex-col">
|
||||
|
|
@ -213,8 +212,7 @@ export const LaunchesComponent = () => {
|
|||
</div>
|
||||
<div className="flex-1 flex flex-col gap-[14px]">
|
||||
<Filters />
|
||||
<NewCalendarComponent />
|
||||
{/*<Calendar />*/}
|
||||
<Calendar />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,56 +0,0 @@
|
|||
'use client';
|
||||
import { ChevronLeft, ChevronRight, Plus } from 'lucide-react';
|
||||
import { Button } from '@gitroom/react/form/button';
|
||||
import { Fragment } from 'react';
|
||||
import { CalendarColumn } from '@gitroom/frontend/components/launches/calendar';
|
||||
import { DNDProvider } from '@gitroom/frontend/components/launches/helpers/dnd.provider';
|
||||
|
||||
export const days = [
|
||||
'Monday',
|
||||
'Tuesday',
|
||||
'Wednesday',
|
||||
'Thursday',
|
||||
'Friday',
|
||||
'Saturday',
|
||||
'Sunday',
|
||||
];
|
||||
export const hours = Array.from({ length: 24 }, (_, i) => i);
|
||||
|
||||
export const NewCalendarComponent = () => {
|
||||
return (
|
||||
<DNDProvider>
|
||||
<div className="flex flex-col h-screen overflow-hidden text-textColor flex-1">
|
||||
<div className="flex-1">
|
||||
<div className="grid grid-cols-8 bg-customColor31 gap-[1px] border-customColor31 border rounded-[10px]">
|
||||
<div className="bg-customColor20 sticky top-0 z-10 bg-gray-900"></div>
|
||||
{days.map((day, index) => (
|
||||
<div
|
||||
key={day}
|
||||
className="sticky top-0 z-10 bg-customColor20 p-2 text-center"
|
||||
>
|
||||
<div>{day}</div>
|
||||
</div>
|
||||
))}
|
||||
{hours.map((hour) => (
|
||||
<Fragment key={hour}>
|
||||
<div className="p-2 pr-4 bg-secondary text-center items-center justify-center flex">
|
||||
{hour.toString().padStart(2, '0')}:00
|
||||
</div>
|
||||
{days.map((day, indexDay) => (
|
||||
<Fragment key={`${day}-${hour}`}>
|
||||
<div className="relative bg-secondary">
|
||||
<CalendarColumn
|
||||
day={indexDay}
|
||||
hour={`${hour.toString().padStart(2, '0')}:00`}
|
||||
/>
|
||||
</div>
|
||||
</Fragment>
|
||||
))}
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DNDProvider>
|
||||
);
|
||||
};
|
||||
|
|
@ -149,6 +149,7 @@ module.exports = {
|
|||
}),
|
||||
screens: {
|
||||
custom: { raw: '(max-height: 800px)' },
|
||||
xs: { max: '401px'} ,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,26 +1,40 @@
|
|||
version: '3.9'
|
||||
|
||||
services:
|
||||
gitroom-postgres:
|
||||
postiz-postgres:
|
||||
image: postgres:14.5
|
||||
container_name: gitroom-postgres
|
||||
container_name: postiz-postgres
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_PASSWORD: gitroom-local-pwd
|
||||
POSTGRES_USER: gitroom-local
|
||||
POSTGRES_DB: gitroom-db-local
|
||||
POSTGRES_PASSWORD: postiz-local-pwd
|
||||
POSTGRES_USER: postiz-local
|
||||
POSTGRES_DB: postiz-db-local
|
||||
volumes:
|
||||
- postgres-volume:/var/lib/postgresql/data
|
||||
ports:
|
||||
- 5432:5432
|
||||
gitroom-redis:
|
||||
networks:
|
||||
- postiz-network
|
||||
postiz-pg-admin:
|
||||
image: dpage/pgadmin4
|
||||
container_name: postiz-pg-admin
|
||||
restart: always
|
||||
ports:
|
||||
- 8081:80
|
||||
environment:
|
||||
PGADMIN_DEFAULT_EMAIL: admin@admin.com
|
||||
PGADMIN_DEFAULT_PASSWORD: admin
|
||||
networks:
|
||||
- postiz-network
|
||||
postiz-redis:
|
||||
image: redis:7.2
|
||||
container_name: gitroom-redis
|
||||
container_name: postiz-redis
|
||||
restart: always
|
||||
ports:
|
||||
- 6379:6379
|
||||
|
||||
|
||||
volumes:
|
||||
postgres-volume:
|
||||
external: false
|
||||
|
||||
networks:
|
||||
postiz-network:
|
||||
external: false
|
||||
|
|
|
|||
|
|
@ -116,10 +116,9 @@ export class CommentsRepository {
|
|||
orgId: string,
|
||||
year: number,
|
||||
week: number,
|
||||
isIsoWeek: boolean
|
||||
) {
|
||||
const dateYear = dayjs().year(year);
|
||||
const date = isIsoWeek ? dateYear.isoWeek(week) : dateYear.week(week);
|
||||
const date = dateYear.isoWeek(week);
|
||||
const startDate = date.startOf('isoWeek').subtract(2, 'days').toDate();
|
||||
const endDate = date.endOf('isoWeek').add(2, 'days').toDate();
|
||||
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ export class CommentsService {
|
|||
);
|
||||
}
|
||||
|
||||
getAllCommentsByWeekYear(orgId: string, year: number, week: number, isIsoWeek: boolean) {
|
||||
return this._commentsRepository.getAllCommentsByWeekYear(orgId, year, week, isIsoWeek);
|
||||
getAllCommentsByWeekYear(orgId: string, year: number, week: number) {
|
||||
return this._commentsRepository.getAllCommentsByWeekYear(orgId, year, week);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,10 +64,10 @@ export class PostsRepository {
|
|||
|
||||
getPosts(orgId: string, query: GetPostsDto) {
|
||||
const dateYear = dayjs().year(query.year);
|
||||
const date = query.isIsoWeek === 'true' ? dateYear.isoWeek(query.week) : dateYear.week(query.week);
|
||||
const date = query.week ? dateYear.isoWeek(query.week) : dateYear.month(query.month-1);
|
||||
|
||||
const startDate = date.startOf('week').subtract(2, 'days').toDate();
|
||||
const endDate = date.endOf('week').add(2, 'days').toDate();
|
||||
const startDate = (query.week ? date.startOf('isoWeek') : date.startOf('month')).subtract(2, 'days').toDate();
|
||||
const endDate = (query.week ? date.endOf('isoWeek') : date.endOf('month')).add(2, 'days').toDate();
|
||||
|
||||
return this._post.model.post.findMany({
|
||||
where: {
|
||||
|
|
|
|||
|
|
@ -1,21 +1,25 @@
|
|||
import { Type } from 'class-transformer';
|
||||
import { IsIn, IsNumber, IsString, Max, Min } from 'class-validator';
|
||||
import { IsIn, IsNumber, IsString, Max, Min, ValidateIf } from 'class-validator';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
export class GetPostsDto {
|
||||
@ValidateIf((o) => !o.month)
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@Max(52)
|
||||
@Min(1)
|
||||
week: number;
|
||||
|
||||
@ValidateIf((o) => !o.week)
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@Max(52)
|
||||
@Min(1)
|
||||
month: number;
|
||||
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@Max(dayjs().add(10, 'year').year())
|
||||
@Min(2022)
|
||||
year: number;
|
||||
|
||||
@IsIn(['true', 'false'])
|
||||
@IsString()
|
||||
isIsoWeek: 'true' | 'false';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
"scripts": {
|
||||
"dev": "npx nx run-many --target=serve --projects=frontend,backend,workers --parallel=4",
|
||||
"dev:stripe": "npx concurrently \"stripe listen --forward-to localhost:3000/stripe\" \"npm run dev\"",
|
||||
"build": "npx nx run-many --target=build --projects=frontend,backend,workers",
|
||||
"build": "npx nx run-many --target=build --projects=frontend,backend,workers,cron",
|
||||
"start:prod": "node dist/apps/backend/main.js",
|
||||
"start:prod:frontend": "nx run frontend:serve:production",
|
||||
"start:prod:workers": "node dist/apps/workers/main.js",
|
||||
|
|
@ -20,6 +20,8 @@
|
|||
"prisma-generate": "cd ./libraries/nestjs-libraries/src/database/prisma && npx prisma generate",
|
||||
"prisma-db-push": "cd ./libraries/nestjs-libraries/src/database/prisma && npx prisma db push",
|
||||
"prisma-reset": "cd ./libraries/nestjs-libraries/src/database/prisma && npx prisma db push --force-reset && npx prisma db push",
|
||||
"docker-build": "./var/docker/docker-build.sh",
|
||||
"docker-create": "./var/docker/docker-create.sh",
|
||||
"postinstall": "npm run prisma-generate"
|
||||
},
|
||||
"private": true,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -o xtrace
|
||||
|
||||
docker rmi localhost/postiz || true
|
||||
docker build --target dist -t localhost/postiz -f Dockerfile.dev .
|
||||
docker build --target devcontainer -t localhost/postiz-devcontainer -f Dockerfile.dev .
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
docker kill postiz || true
|
||||
docker rm postiz || true
|
||||
docker create --name postiz -p 3000:3000 -p 4200:4200 localhost/postiz
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
#!/bin/bash
|
||||
|
||||
if [[ "$SKIP_CONFIG_CHECK" != "true" ]]; then
|
||||
echo "symlinking /config/.env into /app/.env"
|
||||
|
||||
if [ ! -f /config/.env ]; then
|
||||
echo "ERROR: No .env file found in /config/.env"
|
||||
fi
|
||||
|
||||
ln -sf /config/.env /app/.env
|
||||
fi
|
||||
|
||||
if [[ "$POSTIZ_APPS" -eq "" ]]; then
|
||||
echo "POSTIZ_APPS is not set, starting everything!"
|
||||
POSTIZ_APPS="frontend workers cron backend"
|
||||
fi
|
||||
|
||||
mkdir -p /etc/supervisor.d/
|
||||
|
||||
if [[ "$POSTIZ_APPS" == *"frontend"* ]]; then
|
||||
ln -sf /app/supervisord_available_configs/frontend.conf /etc/supervisor.d/
|
||||
fi
|
||||
|
||||
if [[ $POSTIZ_APPS == *"workers"* ]]; then
|
||||
ln -sf /app/supervisord_available_configs/workers.conf /etc/supervisor.d/
|
||||
fi
|
||||
|
||||
if [[ $POSTIZ_APPS == *"cron"* ]]; then
|
||||
ln -sf /app/supervisord_available_configs/cron.conf /etc/supervisor.d/
|
||||
fi
|
||||
|
||||
if [[ $POSTIZ_APPS == *"backend"* ]]; then
|
||||
ln -sf /app/supervisord_available_configs/backend.conf /etc/supervisor.d/
|
||||
fi
|
||||
|
||||
/usr/bin/supervisord
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
[supervisord]
|
||||
nodaemon=true
|
||||
logfile=/dev/null
|
||||
logfile_maxbytes=0
|
||||
|
||||
[unix_http_server]
|
||||
file=/run/supervisord.sock
|
||||
|
||||
[include]
|
||||
files = /etc/supervisor.d/*.conf
|
||||
|
||||
[rpcinterface:supervisor]
|
||||
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
|
||||
|
||||
[supervisorctl]
|
||||
serverurl=unix:///run/supervisord.sock
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
[program:backend]
|
||||
directory=/app
|
||||
command=npm run start:prod
|
||||
autostart=true
|
||||
autorestart=false
|
||||
redirect_stderr=true
|
||||
stdout_logfile=/dev/fd/1
|
||||
stdout_logfile_maxbytes=0
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
[program:cron]
|
||||
directory=/app
|
||||
command=npm run start:prod:cron
|
||||
autostart=true
|
||||
autorestart=false
|
||||
redirect_stderr=true
|
||||
stdout_logfile=/dev/fd/1
|
||||
stdout_logfile_maxbytes=0
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
[program:frontend]
|
||||
directory=/app
|
||||
command=npm run start:prod:frontend
|
||||
autostart=true
|
||||
autorestart=false
|
||||
redirect_stderr=true
|
||||
stdout_logfile=/dev/fd/1
|
||||
stdout_logfile_maxbytes=0
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
[program:workers]
|
||||
directory=/app
|
||||
command=npm run start:prod:workers
|
||||
autostart=true
|
||||
autorestart=false
|
||||
redirect_stderr=true
|
||||
stdout_logfile=/dev/fd/1
|
||||
stdout_logfile_maxbytes=0
|
||||
Loading…
Reference in New Issue