Source: RouterProject/projectShare/controller.js

// @ts-check
/**
 * Share controllers (contract envelope).
 * @import {ExpressRequestAuthorized, ExpressResponse} from '../../types.js'
 */

import { sendOk, sendErrorFrom } from '../../utils/apiEnvelope.js';
import * as service from './service.js';

/**
 * @swagger
 * /api/project/project/{projectUid}/shares:
 *   put:
 *     summary: Add a share to a project
 *     description: |
 *       Creates a repositoryShare or directoryShare belonging to exactly one
 *       project. Requires `admin` on the project. Shares carry no own ACL.
 *     tags:
 *       - Project Shares
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: path
 *         name: projectUid
 *         required: true
 *         schema:
 *           type: string
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             $ref: './projectShare.swagger.yaml#/components/schemas/ShareCreateBody'
 *     responses:
 *       200:
 *         description: Share created
 *         content:
 *           application/json:
 *             schema:
 *               $ref: './projectShare.swagger.yaml#/components/schemas/SuccessResponse'
 *       422:
 *         description: Invalid share type or metadata (e.g. ACL/capabilities present)
 *         content:
 *           application/json:
 *             schema:
 *               $ref: './projectShare.swagger.yaml#/components/schemas/ErrorResponse'
 *   get:
 *     summary: List shares of a project
 *     description: Requires at least `visible` on the project (derived right).
 *     tags:
 *       - Project Shares
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: path
 *         name: projectUid
 *         required: true
 *         schema:
 *           type: string
 *     responses:
 *       200:
 *         description: List of shares
 *         content:
 *           application/json:
 *             schema:
 *               $ref: './projectShare.swagger.yaml#/components/schemas/SuccessResponse'
 */
/**
 * @swagger
 * /api/project/project/shares/search:
 *   get:
 *     summary: Search org repository shares visible to the user
 *     description: |
 *       Returns repositoryShare/directoryShare objects of the organization that
 *       are linked to at least one project visible to the current user
 *       (admins see all org shares). Used by the "add existing repository" flow.
 *       The response is a raw array (SearchSelect-compatible).
 *     tags:
 *       - Project Shares
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: query
 *         name: search
 *         required: false
 *         schema:
 *           type: string
 *         description: Terms matching title, display, repositoryKey or gitUrl
 *       - in: query
 *         name: excludeProject
 *         required: false
 *         schema:
 *           type: string
 *         description: Share UID of a project whose already-linked shares are skipped
 *     responses:
 *       200:
 *         description: Array of matching shares
 *         content:
 *           application/json:
 *             schema:
 *               type: array
 *               items:
 *                 $ref: './projectShare.swagger.yaml#/components/schemas/ShareSearchItem'
 *       401:
 *         description: Missing or invalid authentication
 *         content:
 *           application/json:
 *             schema:
 *               $ref: './projectShare.swagger.yaml#/components/schemas/ErrorResponse'
 */
/**
 * GET /project/project/shares/search
 * @param {ExpressRequestAuthorized} req
 * @param {ExpressResponse} res
 */
export const searchShares = async (req, res) => {
    try {
        const result = await service.searchShares(req, req.query.search, req.query.excludeProject);
        // Raw array — die SearchSelect-Komponente erwartet ein Array als Antwort
        res.json(result.result);
    } catch (e) {
        sendErrorFrom(res, e);
    }
};

/**
 * PUT /project/project/:projectUid/shares
 * @param {ExpressRequestAuthorized} req
 * @param {ExpressResponse} res
 */
export const postShare = async (req, res) => {
    try {
        const result = await service.addShare(req, req.params.projectUid);
        sendOk(res, result.result);
    } catch (e) {
        sendErrorFrom(res, e);
    }
};

/**
 * GET /project/project/:projectUid/shares
 * @param {ExpressRequestAuthorized} req
 * @param {ExpressResponse} res
 */
export const listShares = async (req, res) => {
    try {
        const result = await service.listShares(req, req.params.projectUid);
        sendOk(res, result.result);
    } catch (e) {
        sendErrorFrom(res, e);
    }
};

/**
 * @swagger
 * /api/project/project/{projectUid}/shares/{shareUid}:
 *   post:
 *     summary: Update share metadata
 *     description: Requires `admin` on the parent project. No ACL/capabilities allowed.
 *     tags:
 *       - Project Shares
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: path
 *         name: projectUid
 *         required: true
 *         schema:
 *           type: string
 *       - in: path
 *         name: shareUid
 *         required: true
 *         schema:
 *           type: string
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             $ref: './projectShare.swagger.yaml#/components/schemas/ShareUpdateBody'
 *     responses:
 *       200:
 *         description: Share updated
 *         content:
 *           application/json:
 *             schema:
 *               $ref: './projectShare.swagger.yaml#/components/schemas/SuccessResponse'
 *       404:
 *         description: Share not found for this project
 *         content:
 *           application/json:
 *             schema:
 *               $ref: './projectShare.swagger.yaml#/components/schemas/ErrorResponse'
 *   delete:
 *     summary: Delete a share
 *     description: |
 *       Removes the projectShare link and the share object in one transaction.
 *       Requires `admin` on the parent project.
 *     tags:
 *       - Project Shares
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: path
 *         name: projectUid
 *         required: true
 *         schema:
 *           type: string
 *       - in: path
 *         name: shareUid
 *         required: true
 *         schema:
 *           type: string
 *     responses:
 *       200:
 *         description: Share deleted
 *         content:
 *           application/json:
 *             schema:
 *               $ref: './projectShare.swagger.yaml#/components/schemas/SuccessResponse'
 *       404:
 *         description: Share not found for this project
 *         content:
 *           application/json:
 *             schema:
 *               $ref: './projectShare.swagger.yaml#/components/schemas/ErrorResponse'
 */
/**
 * POST /project/project/:projectUid/shares/:shareUid
 * @param {ExpressRequestAuthorized} req
 * @param {ExpressResponse} res
 */
export const updateShare = async (req, res) => {
    try {
        const result = await service.updateShare(req, req.params.projectUid, req.params.shareUid);
        sendOk(res, result.result);
    } catch (e) {
        sendErrorFrom(res, e);
    }
};

/**
 * DELETE /project/project/:projectUid/shares/:shareUid
 * @param {ExpressRequestAuthorized} req
 * @param {ExpressResponse} res
 */
export const deleteShare = async (req, res) => {
    try {
        const result = await service.deleteShare(req, req.params.projectUid, req.params.shareUid);
        sendOk(res, result.result);
    } catch (e) {
        sendErrorFrom(res, e);
    }
};

/**
 * @swagger
 * /api/project/project/{projectUid}/shares/{shareUid}/link:
 *   post:
 *     summary: Link an existing share to a project
 *     description: |
 *       Links an org-owned share to the project. Requires `admin` on the
 *       project. `linkType` 'memberA' grants write/admin (Master-Projekt),
 *       'member' grants read only. Fires /add/project/{read|write}/{projectUid}.
 *     tags:
 *       - Project Shares
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: path
 *         name: projectUid
 *         required: true
 *         schema:
 *           type: string
 *       - in: path
 *         name: shareUid
 *         required: true
 *         schema:
 *           type: string
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             type: object
 *             properties:
 *               linkType:
 *                 type: string
 *                 enum: [memberA, member]
 *     responses:
 *       200:
 *         description: Share linked
 *         content:
 *           application/json:
 *             schema:
 *               $ref: './projectShare.swagger.yaml#/components/schemas/SuccessResponse'
 *       404:
 *         description: Share not found in this organization
 *         content:
 *           application/json:
 *             schema:
 *               $ref: './projectShare.swagger.yaml#/components/schemas/ErrorResponse'
 *   delete:
 *     summary: Remove the link between a share and a project
 *     description: |
 *       Removes the memberA/member link. Requires `admin` on the project.
 *       Fires /remove/project/{read|write}/{projectUid}. The share object
 *       itself is kept.
 *     tags:
 *       - Project Shares
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: path
 *         name: projectUid
 *         required: true
 *         schema:
 *           type: string
 *       - in: path
 *         name: shareUid
 *         required: true
 *         schema:
 *           type: string
 *     responses:
 *       200:
 *         description: Share unlinked
 *         content:
 *           application/json:
 *             schema:
 *               $ref: './projectShare.swagger.yaml#/components/schemas/SuccessResponse'
 *       404:
 *         description: Share not linked to this project
 *         content:
 *           application/json:
 *             schema:
 *               $ref: './projectShare.swagger.yaml#/components/schemas/ErrorResponse'
 */
/**
 * POST /project/project/:projectUid/shares/:shareUid/link
 * @param {ExpressRequestAuthorized} req
 * @param {ExpressResponse} res
 */
export const linkShare = async (req, res) => {
    try {
        const result = await service.linkShare(req, req.params.projectUid, req.params.shareUid);
        sendOk(res, result.result);
    } catch (e) {
        sendErrorFrom(res, e);
    }
};

/**
 * DELETE /project/project/:projectUid/shares/:shareUid/link
 * @param {ExpressRequestAuthorized} req
 * @param {ExpressResponse} res
 */
export const unlinkShare = async (req, res) => {
    try {
        const result = await service.unlinkShare(req, req.params.projectUid, req.params.shareUid);
        sendOk(res, result.result);
    } catch (e) {
        sendErrorFrom(res, e);
    }
};