name: 'Repository Maintenance'
on:
  schedule:
    - cron: '0 3 * * *'
  workflow_dispatch:
permissions:
  issues: write
  pull-requests: write
  discussions: write
concurrency:
  group: lock
jobs:
  stale:
    name: 'Stale'
    if: github.repository_owner == 'paperless-ngx'
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/stale@v10
        with:
          days-before-stale: 7
          days-before-close: 14
          any-of-issue-labels: 'cant-reproduce,not a bug'
          stale-issue-label: stale
          stale-issue-message: >
            This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. See our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-repository-maintenance) for more details.

          days-before-pr-stale: 14
          days-before-pr-close: 7
          stale-pr-message: ""
          stale-pr-label: stale
          exempt-pr-labels: 'notable'
          close-pr-message: >
            This pull request has been automatically closed because it has not had recent activity. Thank you for your contributions. Please open a new pull request or discussion if you would like to continue working on this change.

  lock-threads:
    name: 'Lock Old Threads'
    if: github.repository_owner == 'paperless-ngx'
    runs-on: ubuntu-24.04
    steps:
      - uses: dessant/lock-threads@v6
        with:
          issue-inactive-days: '30'
          pr-inactive-days: '30'
          discussion-inactive-days: '30'
          log-output: true
          issue-comment: >
            This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new discussion or issue for related concerns. See our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-repository-maintenance) for more details.

          pr-comment: >
            This pull request has been automatically locked since there has not been any recent activity after it was closed. Please open a new discussion or issue for related concerns. See our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-repository-maintenance) for more details.

          discussion-comment: >
            This discussion has been automatically locked since there has not been any recent activity after it was closed. Please open a new discussion for related concerns. See our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-repository-maintenance) for more details.

  close-answered-discussions:
    name: 'Close Answered Discussions'
    if: github.repository_owner == 'paperless-ngx'
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/github-script@v8
        with:
          script: |
            function sleep(ms) {
              return new Promise(resolve => setTimeout(resolve, ms));
            }

            const query = `query($owner:String!, $name:String!) {
              repository(owner:$owner, name:$name){
                discussions(first:100, answered:true, states:[OPEN]) {
                  nodes {
                    id,
                    number
                  }
                }
              }
            }`;
            const variables = {
              owner: context.repo.owner,
              name: context.repo.repo,
            }
            const result = await github.graphql(query, variables)

            console.log(`Found ${result.repository.discussions.nodes.length} open answered discussions`)

            for (const discussion of result.repository.discussions.nodes) {
              console.log(`Closing discussion #${discussion.number} (${discussion.id})`)

              const addCommentMutation = `mutation($discussion:ID!, $body:String!) {
                addDiscussionComment(input:{discussionId:$discussion, body:$body}) {
                  clientMutationId
                }
              }`;
              const commentVariables = {
                discussion: discussion.id,
                body: 'This discussion has been automatically closed because it was marked as answered. Please see our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-repository-maintenance) for more details.',
              }
              await github.graphql(addCommentMutation, commentVariables)

              const closeDiscussionMutation = `mutation($discussion:ID!, $reason:DiscussionCloseReason!) {
                closeDiscussion(input:{discussionId:$discussion, reason:$reason}) {
                  clientMutationId
                }
              }`;
              const closeVariables = {
                discussion: discussion.id,
                reason: "RESOLVED",
              }
              await github.graphql(closeDiscussionMutation, closeVariables)

              await sleep(1000)
            }
  close-outdated-discussions:
    name: 'Close Outdated Discussions'
    if: github.repository_owner == 'paperless-ngx'
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/github-script@v8
        with:
          script: |
            function sleep(ms) {
              return new Promise(resolve => setTimeout(resolve, ms));
            }

            const CUTOFF_DAYS = 180;
            const cutoff = new Date();
            cutoff.setDate(cutoff.getDate() - CUTOFF_DAYS);

            const query = `query(
                $owner:String!,
                $name:String!,
                $supportCategory:ID!,
                $generalCategory:ID!,
              ) {
              supportDiscussions: repository(owner:$owner, name:$name){
                discussions(
                  categoryId:$supportCategory,
                  last:50,
                  answered:false,
                  states:[OPEN],
                ) {
                  nodes {
                    id,
                    number,
                    updatedAt
                  }
                },
              },
              generalDiscussions: repository(owner:$owner, name:$name){
                discussions(
                  categoryId:$generalCategory,
                  last:50,
                  states:[OPEN],
                ) {
                  nodes {
                    id,
                    number,
                    updatedAt
                  }
                }
              }
            }`;
            const variables = {
              owner: context.repo.owner,
              name: context.repo.repo,
              supportCategory: "DIC_kwDOG1Zs184CBKWK",
              generalCategory: "DIC_kwDOG1Zs184CBKWJ"
            }
            const result = await github.graphql(query, variables);
            const combinedDiscussions = [
              ...result.supportDiscussions.discussions.nodes,
              ...result.generalDiscussions.discussions.nodes,
            ]

            console.log(`Checking ${combinedDiscussions.length} open discussions`);

            for (const discussion of combinedDiscussions) {
              if (new Date(discussion.updatedAt) < cutoff) {
                console.log(`Closing outdated discussion #${discussion.number} (${discussion.id}), last updated at ${discussion.updatedAt}`);
                const addCommentMutation = `mutation($discussion:ID!, $body:String!) {
                  addDiscussionComment(input:{discussionId:$discussion, body:$body}) {
                    clientMutationId
                  }
                }`;
                const commentVariables = {
                  discussion: discussion.id,
                  body: 'This discussion has been automatically closed due to inactivity. Please see our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-repository-maintenance) for more details.',
                }
                await github.graphql(addCommentMutation, commentVariables);

                const closeDiscussionMutation = `mutation($discussion:ID!, $reason:DiscussionCloseReason!) {
                  closeDiscussion(input:{discussionId:$discussion, reason:$reason}) {
                    clientMutationId
                  }
                }`;
                const closeVariables = {
                  discussion: discussion.id,
                  reason: "OUTDATED",
                }
                await github.graphql(closeDiscussionMutation, closeVariables);

                await sleep(1000);
              }
            }
  close-unsupported-feature-requests:
    name: 'Close Unsupported Feature Requests'
    if: github.repository_owner == 'paperless-ngx'
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/github-script@v8
        with:
          script: |
            function sleep(ms) {
              return new Promise(resolve => setTimeout(resolve, ms));
            }

            const CUTOFF_MAX_COUNT = 80;
            const CUTOFF_1_DAYS = 180;
            const CUTOFF_1_COUNT = 5;
            const CUTOFF_2_DAYS = 365;
            const CUTOFF_2_COUNT = 20;
            const CUTOFF_3_DAYS = 730;
            const CUTOFF_3_COUNT = 40;

            const cutoff1Date = new Date();
            cutoff1Date.setDate(cutoff1Date.getDate() - CUTOFF_1_DAYS);
            const cutoff2Date = new Date();
            cutoff2Date.setDate(cutoff2Date.getDate() - CUTOFF_2_DAYS);
            const cutoff3Date = new Date();
            cutoff3Date.setDate(cutoff3Date.getDate() - CUTOFF_3_DAYS);

            const query = `query(
                $owner:String!,
                $name:String!,
                $featureRequestsCategory:ID!,
              ) {
              repository(owner:$owner, name:$name){
                discussions(
                  categoryId:$featureRequestsCategory,
                  last:100,
                  states:[OPEN],
                ) {
                  nodes {
                    id,
                    createdAt,
                    number,
                    updatedAt,
                    upvoteCount,
                  }
                },
              }
            }`;
            const variables = {
              owner: context.repo.owner,
              name: context.repo.repo,
              featureRequestsCategory: "DIC_kwDOG1Zs184CBNr4"
            }
            const result = await github.graphql(query, variables);

            for (const discussion of result.repository.discussions.nodes) {
              const discussionUpdatedDate = new Date(discussion.updatedAt);
              const discussionCreatedDate = new Date(discussion.createdAt);
              if ((discussionUpdatedDate < cutoff1Date && discussion.upvoteCount < CUTOFF_MAX_COUNT) ||
                  (discussionCreatedDate < cutoff1Date && discussion.upvoteCount < CUTOFF_1_COUNT) ||
                  (discussionCreatedDate < cutoff2Date && discussion.upvoteCount < CUTOFF_2_COUNT) ||
                  (discussionCreatedDate < cutoff3Date && discussion.upvoteCount < CUTOFF_3_COUNT)) {
                console.log(`Closing discussion #${discussion.number} (${discussion.id}), last updated at ${discussion.updatedAt} with votes ${discussion.upvoteCount}`);
                const addCommentMutation = `mutation($discussion:ID!, $body:String!) {
                  addDiscussionComment(input:{discussionId:$discussion, body:$body}) {
                    clientMutationId
                  }
                }`;
                const commentVariables = {
                  discussion: discussion.id,
                  body: 'This discussion has been automatically closed due to lack of community support. Please see our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-repository-maintenance) for more details.',
                }
                await github.graphql(addCommentMutation, commentVariables);

                const closeDiscussionMutation = `mutation($discussion:ID!, $reason:DiscussionCloseReason!) {
                  closeDiscussion(input:{discussionId:$discussion, reason:$reason}) {
                    clientMutationId
                  }
                }`;
                const closeVariables = {
                  discussion: discussion.id,
                  reason: "OUTDATED",
                }
                await github.graphql(closeDiscussionMutation, closeVariables);

                await sleep(1000);
              }
            }
