Compare commits

...
10 Commits
Author SHA1 Message Date
Alex van Andel c8b577fb7a chore: Release v4.3.0 (#15771)
release.yml / release (push) Canceled after 0s
2024-07-15 13:55:09 +00:00
Peer Richelsen 7f97d27c20 chore: Update README.md (#15766)
* Update README.md

* Update README.md

* Update README.md
2024-07-15 13:40:28 +02:00
Morgan 9ff10877e7 chore: fix apiv2 org teams doc (#15746) 2024-07-15 10:27:33 +00:00
Morgan eb0768917c chore: readme platform libraries (#15737)
* chore: readme platform libraries

* fixup! chore: readme platform libraries

* fixup! fixup! chore: readme platform libraries

* Update README.md

* Update README.md
2024-07-15 10:13:41 +00:00
ec65cd6333 fix: icon visibility and add up and down icons (#15543)
* fixed the icons visibility and added up and down icon

* wip: TimeUnitAddonSuffix

* chore: Quick pass for rerender-optimisation

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-07-15 09:35:48 +00:00
Udit Takkar 563141b295 chore: add check for booking (#15740)
* chore: add check

* chore: missting no shoe
2024-07-15 08:59:29 +00:00
Hariom Balhara 7b24972900 fix: Ensure unique username in an organization (#15730) 2024-07-15 06:11:24 +00:00
Somay Chauhan 6d2ec71df3 add missing getServerSideProps to /event-types (#15731) 2024-07-13 09:51:00 +00:00
492304fe1d feat: BookerEmbed atom (#15689)
* init: booker embed atom

* update booker platform wrapper

* export booker embed for atoms

* update booker embed wrapper

* fixup: refresh url not needed

* rename booker embed

* add global styles

* export not needed

* add booker embed atoms in examples app

* fixup

* fixup! fixup

* fixup! fixup! fixup

---------

Co-authored-by: Rajiv Sahal <rajivsahal@Rajivs-MacBook-Pro.local>
Co-authored-by: Morgan Vernay <morgan@cal.com>
Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
2024-07-12 11:10:32 +00:00
c7c5cdf133 feat: Campsite conferencing app (#15713)
* feat: Campsite conferencing app

* Improve redirect after OAuth flow

---------

Co-authored-by: Somay Chauhan <somaychauhan98@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2024-07-11 14:20:31 +00:00
44 changed files with 955 additions and 81 deletions
+31 -1
View File
@@ -392,7 +392,37 @@ Currently Vercel Pro Plan is required to be able to Deploy this application with
See the [roadmap project](https://cal.com/roadmap) for a list of proposed features (and known issues). You can change the view to see planned tagged releases.
<!-- RORADMAP -->
<!-- LICENSE -->
## License
Cal.com, Inc. is a commercial open source company, which means some parts of this open source repository require a commercial license. The concept is called "Open Core" where the core technology (99%) is fully open source, licensed under [AGPLv3](https://opensource.org/license/agpl-v3) and the last 1% is covered under a commercial license (["/ee" Enterprise Edition](https://github.com/calcom/cal.com/tree/main/packages/features/ee)) which we believe is entirely relevant for larger organisations that require enterprise features. Enterprise features are built by the core engineering team of Cal.com, Inc. which is hired in full-time. Find their compensation on https://cal.com/open.
> [!NOTE]
> Our philosophy is simple, all "Singleplayer APIs" are open-source under AGPLv3. All commercial "Multiplayer APIs" are under a commercial license.
| | AGPLv3 | EE |
|---|---|---|
| Self-host for commercial purposes | ✅ | ✅ |
| Clone privately | ✅ | ✅ |
| Fork publicly | ✅ | ✅ |
| Requires CLA | ✅ | ✅ |
| Official Support| ❌ | ✅ |
| Derivative work privately | ❌ | ✅ |
| SSO | ❌ | ✅ |
| Admin Panel | ❌ | ✅ |
| Impersonation | ❌ | ✅ |
| Managed Event Types | ❌ | ✅ |
| Organizations | ❌ | ✅ |
| Payments | ❌ | ✅ |
| Platform | ❌ | ✅ |
| Teams | ❌ | ✅ |
| Users | ❌ | ✅ |
| Video | ❌ | ✅ |
| Workflows | ❌ | ✅ |
> [!TIP]
> We work closely with the community and always invite feedback about what should be open and what is fine to be commercial. This list is not set and stone and we have moved things from commercial to open in the past. Please open a [discussion](https://github.com/calcom/cal.com/discussions) if you feel like something is wrong.
## Repo Activity
@@ -21,8 +21,6 @@ import {
Query,
} from "@nestjs/common";
import { ApiTags as DocsTags } from "@nestjs/swagger";
import { Transform } from "class-transformer";
import { IsNumber, Min, Max, IsOptional } from "class-validator";
import { SUCCESS_STATUS } from "@calcom/platform-constants";
import {
@@ -34,21 +32,7 @@ import {
UpdateScheduleInput_2024_06_11,
UpdateScheduleOutput_2024_06_11,
} from "@calcom/platform-types";
class SkipTakePagination {
@Transform(({ value }: { value: string }) => value && parseInt(value))
@IsNumber()
@Min(1)
@Max(250)
@IsOptional()
take?: number;
@Transform(({ value }: { value: string }) => value && parseInt(value))
@IsNumber()
@Min(0)
@IsOptional()
skip?: number;
}
import { SkipTakePagination } from "@calcom/platform-types";
@Controller({
path: "/v2/organizations/:orgId",
@@ -6,7 +6,11 @@ import { IsOrgGuard } from "@/modules/auth/guards/organizations/is-org.guard";
import { RolesGuard } from "@/modules/auth/guards/roles/roles.guard";
import { IsTeamInOrg } from "@/modules/auth/guards/teams/is-team-in-org.guard";
import { CreateOrgTeamDto } from "@/modules/organizations/inputs/create-organization-team.input";
import { OrgTeamOutputDto } from "@/modules/organizations/outputs/organization-team.output";
import {
OrgTeamOutputDto,
OrgTeamOutputResponseDto,
OrgTeamsOutputResponseDto,
} from "@/modules/organizations/outputs/organization-team.output";
import { OrganizationsTeamsService } from "@/modules/organizations/services/organizations-teams.service";
import {
Controller,
@@ -20,11 +24,11 @@ import {
Post,
Body,
} from "@nestjs/common";
import { ApiTags as DocsTags } from "@nestjs/swagger";
import { ApiOperation, ApiTags as DocsTags } from "@nestjs/swagger";
import { plainToClass } from "class-transformer";
import { SUCCESS_STATUS } from "@calcom/platform-constants";
import { ApiResponse, SkipTakePagination } from "@calcom/platform-types";
import { SkipTakePagination } from "@calcom/platform-types";
import { Team } from "@calcom/prisma/client";
@Controller({
@@ -37,12 +41,13 @@ export class OrganizationsTeamsController {
constructor(private organizationsTeamsService: OrganizationsTeamsService) {}
@Get()
@ApiOperation({ summary: "Get all the teams of an organization." })
@UseGuards()
@Roles("ORG_ADMIN")
async getAllTeams(
@Param("orgId", ParseIntPipe) orgId: number,
@Query() queryParams: SkipTakePagination
): Promise<ApiResponse<OrgTeamOutputDto[]>> {
): Promise<OrgTeamsOutputResponseDto> {
const { skip, take } = queryParams;
const teams = await this.organizationsTeamsService.getPaginatedOrgTeams(orgId, skip ?? 0, take ?? 250);
return {
@@ -54,7 +59,8 @@ export class OrganizationsTeamsController {
@UseGuards(IsTeamInOrg)
@Roles("TEAM_ADMIN")
@Get("/:teamId")
async getTeam(@GetTeam() team: Team): Promise<ApiResponse<OrgTeamOutputDto>> {
@ApiOperation({ summary: "Get a team of the organization by ID." })
async getTeam(@GetTeam() team: Team): Promise<OrgTeamOutputResponseDto> {
return {
status: SUCCESS_STATUS,
data: plainToClass(OrgTeamOutputDto, team, { strategy: "excludeAll" }),
@@ -64,10 +70,11 @@ export class OrganizationsTeamsController {
@UseGuards(IsTeamInOrg)
@Roles("ORG_ADMIN")
@Delete("/:teamId")
@ApiOperation({ summary: "Delete a team of the organization by ID." })
async deleteTeam(
@Param("orgId", ParseIntPipe) orgId: number,
@Param("teamId", ParseIntPipe) teamId: number
): Promise<ApiResponse<OrgTeamOutputDto>> {
): Promise<OrgTeamOutputResponseDto> {
const team = await this.organizationsTeamsService.deleteOrgTeam(orgId, teamId);
return {
status: SUCCESS_STATUS,
@@ -78,11 +85,12 @@ export class OrganizationsTeamsController {
@UseGuards(IsTeamInOrg)
@Roles("ORG_ADMIN")
@Patch("/:teamId")
@ApiOperation({ summary: "Update a team of the organization by ID." })
async updateTeam(
@Param("orgId", ParseIntPipe) orgId: number,
@Param("teamId", ParseIntPipe) teamId: number,
@Body() body: CreateOrgTeamDto
): Promise<ApiResponse<OrgTeamOutputDto>> {
): Promise<OrgTeamOutputResponseDto> {
const team = await this.organizationsTeamsService.updateOrgTeam(orgId, teamId, body);
return {
status: SUCCESS_STATUS,
@@ -93,10 +101,11 @@ export class OrganizationsTeamsController {
@Post()
@UseGuards()
@Roles("ORG_ADMIN")
@ApiOperation({ summary: "Create a team for an organization." })
async createTeam(
@Param("orgId", ParseIntPipe) orgId: number,
@Body() body: CreateOrgTeamDto
): Promise<ApiResponse<OrgTeamOutputDto>> {
): Promise<OrgTeamOutputResponseDto> {
const team = await this.organizationsTeamsService.createOrgTeam(orgId, body);
return {
status: SUCCESS_STATUS,
@@ -1,5 +1,17 @@
import { Expose } from "class-transformer";
import { IsBoolean, IsInt, IsOptional, IsString, IsUrl, Length } from "class-validator";
import { ApiProperty } from "@nestjs/swagger";
import { Expose, Type } from "class-transformer";
import {
IsBoolean,
IsEnum,
IsInt,
IsOptional,
IsString,
IsUrl,
Length,
ValidateNested,
} from "class-validator";
import { ERROR_STATUS, SUCCESS_STATUS } from "@calcom/platform-constants";
export class OrgTeamOutputDto {
@IsInt()
@@ -105,3 +117,25 @@ export class OrgTeamOutputDto {
@Expose()
readonly weekStart?: string = "Sunday";
}
export class OrgTeamsOutputResponseDto {
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
@Expose()
@ValidateNested()
@Type(() => OrgTeamOutputDto)
data!: OrgTeamOutputDto[];
}
export class OrgTeamOutputResponseDto {
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
@Expose()
@ValidateNested()
@Type(() => OrgTeamOutputDto)
data!: OrgTeamOutputDto;
}
+286 -5
View File
@@ -33,6 +33,16 @@
"schema": {
"type": "string"
}
},
{
"name": "limit",
"required": false,
"in": "query",
"description": "The number of items to return",
"example": 10,
"schema": {
"type": "number"
}
}
],
"responses": {
@@ -417,6 +427,16 @@
"schema": {
"type": "string"
}
},
{
"name": "limit",
"required": false,
"in": "query",
"description": "The number of items to return",
"example": 10,
"schema": {
"type": "number"
}
}
],
"responses": {
@@ -809,6 +829,7 @@
"/v2/organizations/{orgId}/teams": {
"get": {
"operationId": "OrganizationsTeamsController_getAllTeams",
"summary": "Get all the teams of an organization.",
"parameters": [
{
"name": "orgId",
@@ -817,6 +838,26 @@
"schema": {
"type": "number"
}
},
{
"name": "take",
"required": false,
"in": "query",
"description": "The number of items to return",
"example": 10,
"schema": {
"type": "number"
}
},
{
"name": "skip",
"required": false,
"in": "query",
"description": "The number of items to skip",
"example": 0,
"schema": {
"type": "number"
}
}
],
"responses": {
@@ -825,7 +866,7 @@
"content": {
"application/json": {
"schema": {
"type": "object"
"$ref": "#/components/schemas/OrgTeamsOutputResponseDto"
}
}
}
@@ -837,6 +878,7 @@
},
"post": {
"operationId": "OrganizationsTeamsController_createTeam",
"summary": "Create a team for an organization.",
"parameters": [
{
"name": "orgId",
@@ -863,7 +905,7 @@
"content": {
"application/json": {
"schema": {
"type": "object"
"$ref": "#/components/schemas/OrgTeamOutputResponseDto"
}
}
}
@@ -877,6 +919,7 @@
"/v2/organizations/{orgId}/teams/{teamId}": {
"get": {
"operationId": "OrganizationsTeamsController_getTeam",
"summary": "Get a team of the organization by ID.",
"parameters": [],
"responses": {
"200": {
@@ -884,7 +927,7 @@
"content": {
"application/json": {
"schema": {
"type": "object"
"$ref": "#/components/schemas/OrgTeamOutputResponseDto"
}
}
}
@@ -896,6 +939,7 @@
},
"delete": {
"operationId": "OrganizationsTeamsController_deleteTeam",
"summary": "Delete a team of the organization by ID.",
"parameters": [
{
"name": "orgId",
@@ -920,7 +964,7 @@
"content": {
"application/json": {
"schema": {
"type": "object"
"$ref": "#/components/schemas/OrgTeamOutputResponseDto"
}
}
}
@@ -932,6 +976,7 @@
},
"patch": {
"operationId": "OrganizationsTeamsController_updateTeam",
"summary": "Update a team of the organization by ID.",
"parameters": [
{
"name": "orgId",
@@ -966,7 +1011,7 @@
"content": {
"application/json": {
"schema": {
"type": "object"
"$ref": "#/components/schemas/OrgTeamOutputResponseDto"
}
}
}
@@ -988,6 +1033,26 @@
"schema": {
"type": "number"
}
},
{
"name": "take",
"required": false,
"in": "query",
"description": "The number of items to return",
"example": 10,
"schema": {
"type": "number"
}
},
{
"name": "skip",
"required": false,
"in": "query",
"description": "The number of items to skip",
"example": 0,
"schema": {
"type": "number"
}
}
],
"responses": {
@@ -1902,6 +1967,46 @@
]
}
},
"/v2/bookings/{bookingUid}/mark-no-show": {
"post": {
"operationId": "BookingsController_markNoShow",
"parameters": [
{
"name": "bookingUid",
"required": true,
"in": "path",
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarkNoShowInput"
}
}
}
},
"responses": {
"201": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MarkNoShowOutput"
}
}
}
}
},
"tags": [
"Bookings"
]
}
},
"/v2/bookings/recurring": {
"post": {
"operationId": "BookingsController_createRecurringBooking",
@@ -3700,6 +3805,125 @@
"data"
]
},
"OrgTeamOutputDto": {
"type": "object",
"properties": {
"id": {
"type": "number"
},
"parentId": {
"type": "number"
},
"name": {
"type": "string",
"minLength": 1
},
"slug": {
"type": "string"
},
"logoUrl": {
"type": "string"
},
"calVideoLogo": {
"type": "string"
},
"appLogo": {
"type": "string"
},
"appIconLogo": {
"type": "string"
},
"bio": {
"type": "string"
},
"hideBranding": {
"type": "boolean"
},
"isOrganization": {
"type": "boolean"
},
"isPrivate": {
"type": "boolean"
},
"hideBookATeamMember": {
"type": "boolean",
"default": false
},
"metadata": {
"type": "string"
},
"theme": {
"type": "string"
},
"brandColor": {
"type": "string"
},
"darkBrandColor": {
"type": "string"
},
"bannerUrl": {
"type": "string"
},
"timeFormat": {
"type": "number"
},
"timeZone": {
"type": "string",
"default": "Europe/London"
},
"weekStart": {
"type": "string",
"default": "Sunday"
}
},
"required": [
"id",
"name"
]
},
"OrgTeamsOutputResponseDto": {
"type": "object",
"properties": {
"status": {
"type": "string",
"example": "success",
"enum": [
"success",
"error"
]
},
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/OrgTeamOutputDto"
}
}
},
"required": [
"status",
"data"
]
},
"OrgTeamOutputResponseDto": {
"type": "object",
"properties": {
"status": {
"type": "string",
"example": "success",
"enum": [
"success",
"error"
]
},
"data": {
"$ref": "#/components/schemas/OrgTeamOutputDto"
}
},
"required": [
"status",
"data"
]
},
"CreateOrgTeamDto": {
"type": "object",
"properties": {
@@ -5461,6 +5685,63 @@
"seatReferenceUid"
]
},
"MarkNoShowInput": {
"type": "object",
"properties": {
"noShowHost": {
"type": "boolean"
},
"attendees": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Attendee"
}
}
}
},
"HandleMarkNoShowData": {
"type": "object",
"properties": {
"message": {
"type": "string"
},
"noShowHost": {
"type": "boolean"
},
"messageKey": {
"type": "string"
},
"attendees": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Attendee"
}
}
},
"required": [
"message"
]
},
"MarkNoShowOutput": {
"type": "object",
"properties": {
"status": {
"type": "string",
"example": "success",
"enum": [
"success",
"error"
]
},
"data": {
"$ref": "#/components/schemas/HandleMarkNoShowData"
}
},
"required": [
"status",
"data"
]
},
"ReserveSlotInput": {
"type": "object",
"properties": {}
+6 -1
View File
@@ -1,6 +1,9 @@
import { withAppDirSsr } from "app/WithAppDirSsr";
import { _generateMetadata } from "app/_utils";
import { WithLayout } from "app/layoutHOC";
import { getServerSideProps } from "@lib/event-types/getServerSideProps";
import EventTypes from "~/event-types/views/event-types-listing-view";
export const generateMetadata = async () =>
@@ -9,4 +12,6 @@ export const generateMetadata = async () =>
(t) => t("event_types_page_subtitle")
);
export default WithLayout({ getLayout: null, Page: EventTypes })<"P">;
const getData = withAppDirSsr(getServerSideProps);
export default WithLayout({ getLayout: null, getData, Page: EventTypes })<"P">;
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@calcom/web",
"version": "4.2.5",
"version": "4.3.0",
"private": true,
"scripts": {
"analyze": "ANALYZE=true next build",
@@ -4,6 +4,7 @@
**/
import { appKeysSchema as alby_zod_ts } from "./alby/zod";
import { appKeysSchema as basecamp3_zod_ts } from "./basecamp3/zod";
import { appKeysSchema as campsite_zod_ts } from "./campsite/zod";
import { appKeysSchema as closecom_zod_ts } from "./closecom/zod";
import { appKeysSchema as dailyvideo_zod_ts } from "./dailyvideo/zod";
import { appKeysSchema as fathom_zod_ts } from "./fathom/zod";
@@ -50,6 +51,7 @@ import { appKeysSchema as zoomvideo_zod_ts } from "./zoomvideo/zod";
export const appKeysSchemas = {
alby: alby_zod_ts,
basecamp3: basecamp3_zod_ts,
campsite: campsite_zod_ts,
closecom: closecom_zod_ts,
dailyvideo: dailyvideo_zod_ts,
fathom: fathom_zod_ts,
@@ -12,6 +12,7 @@ import basecamp3_config_json from "./basecamp3/config.json";
import cal_ai_config_json from "./cal-ai/config.json";
import { metadata as caldavcalendar__metadata_ts } from "./caldavcalendar/_metadata";
import campfire_config_json from "./campfire/config.json";
import campsite_config_json from "./campsite/config.json";
import clic_config_json from "./clic/config.json";
import closecom_config_json from "./closecom/config.json";
import cron_config_json from "./cron/config.json";
@@ -103,6 +104,7 @@ export const appStoreMetadata = {
"cal-ai": cal_ai_config_json,
caldavcalendar: caldavcalendar__metadata_ts,
campfire: campfire_config_json,
campsite: campsite_config_json,
clic: clic_config_json,
closecom: closecom_config_json,
cron: cron_config_json,
@@ -4,6 +4,7 @@
**/
import { appDataSchema as alby_zod_ts } from "./alby/zod";
import { appDataSchema as basecamp3_zod_ts } from "./basecamp3/zod";
import { appDataSchema as campsite_zod_ts } from "./campsite/zod";
import { appDataSchema as closecom_zod_ts } from "./closecom/zod";
import { appDataSchema as dailyvideo_zod_ts } from "./dailyvideo/zod";
import { appDataSchema as fathom_zod_ts } from "./fathom/zod";
@@ -50,6 +51,7 @@ import { appDataSchema as zoomvideo_zod_ts } from "./zoomvideo/zod";
export const appDataSchemas = {
alby: alby_zod_ts,
basecamp3: basecamp3_zod_ts,
campsite: campsite_zod_ts,
closecom: closecom_zod_ts,
dailyvideo: dailyvideo_zod_ts,
fathom: fathom_zod_ts,
@@ -13,6 +13,7 @@ export const apiHandlers = {
"cal-ai": import("./cal-ai/api"),
caldavcalendar: import("./caldavcalendar/api"),
campfire: import("./campfire/api"),
campsite: import("./campsite/api"),
clic: import("./clic/api"),
closecom: import("./closecom/api"),
cron: import("./cron/api"),
@@ -4,6 +4,7 @@
**/
import around_config_json from "./around/config.json";
import campfire_config_json from "./campfire/config.json";
import campsite_config_json from "./campsite/config.json";
import { metadata as dailyvideo__metadata_ts } from "./dailyvideo/_metadata";
import demodesk_config_json from "./demodesk/config.json";
import discord_config_json from "./discord/config.json";
@@ -45,6 +46,7 @@ import { metadata as zoomvideo__metadata_ts } from "./zoomvideo/_metadata";
export const appStoreMetadata = {
around: around_config_json,
campfire: campfire_config_json,
campsite: campsite_config_json,
dailyvideo: dailyvideo__metadata_ts,
demodesk: demodesk_config_json,
discord: discord_config_json,
@@ -0,0 +1,8 @@
---
items:
- 1.png
- 2.png
- 3.png
---
{DESCRIPTION}
+45
View File
@@ -0,0 +1,45 @@
import type { NextApiRequest } from "next";
import { stringify } from "querystring";
import { z } from "zod";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { defaultHandler, defaultResponder } from "@calcom/lib/server";
import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug";
import { encodeOAuthState } from "../../_utils/oauth/encodeOAuthState";
const campsiteAppKeysSchema = z.object({
client_id: z.string(),
client_secret: z.string(),
});
export const getCampsiteAppKeys = async () => {
const appKeys = await getAppKeysFromSlug("campsite");
return campsiteAppKeysSchema.parse(appKeys);
};
async function handler(req: NextApiRequest) {
// Get user
const user = req?.session?.user;
if (!user) {
return { status: 401, body: { error: "Unauthorized" } };
}
const { client_id } = await getCampsiteAppKeys();
const state = encodeOAuthState(req);
const params = {
response_type: "code",
client_id,
redirect_uri: `${WEBAPP_URL}/api/integrations/campsite/callback`,
state,
scope: "read_user write_call_room",
};
const query = stringify(params);
const url = `https://auth.campsite.co/oauth/authorize?${query}`;
return { url };
}
export default defaultHandler({
GET: Promise.resolve({ default: defaultResponder(handler) }),
});
@@ -0,0 +1,73 @@
import type { NextApiRequest, NextApiResponse } from "next";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl";
import prisma from "@calcom/prisma";
import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug";
import getInstalledAppPath from "../../_utils/getInstalledAppPath";
import createOAuthAppCredential from "../../_utils/oauth/createOAuthAppCredential";
import { decodeOAuthState } from "../../_utils/oauth/decodeOAuthState";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const state = decodeOAuthState(req);
const userId = req.session?.user.id;
if (!userId) {
return res.status(404).json({ message: "No user found" });
}
const { code } = req.query;
const { client_id, client_secret } = await getAppKeysFromSlug("campsite");
const result = await fetch(`https://auth.campsite.co/oauth/token`, {
method: "POST",
body: JSON.stringify({
grant_type: "authorization_code",
code,
client_id,
client_secret,
redirect_uri: `${WEBAPP_URL}/api/integrations/campsite/callback`,
}),
headers: {
"Content-Type": "application/json",
},
});
if (result.status !== 200) {
let errorMessage = "Something is wrong with the Campsite API";
try {
const responseBody = await result.json();
errorMessage = responseBody.error;
} catch (e) {}
res.status(400).json({ message: errorMessage });
return;
}
const responseBody = await result.json();
if (responseBody.error) {
res.status(400).json({ message: responseBody.error });
return;
}
/**
* With this we take care of no duplicate Campsite keys for a single user
* when creating a room using deleteMany if there is already a Campsite key
* */
await prisma.credential.deleteMany({
where: {
type: "campsite_conferencing",
userId,
appId: "campsite",
},
});
await createOAuthAppCredential(
{ appId: "campsite", type: "campsite_conferencing" },
{ access_token: responseBody.access_token },
req
);
res.redirect(
getSafeRedirectUrl(state?.returnTo) ?? getInstalledAppPath({ variant: "conferencing", slug: "campsite" })
);
}
+2
View File
@@ -0,0 +1,2 @@
export { default as add } from "./add";
export { default as callback } from "./callback";
+23
View File
@@ -0,0 +1,23 @@
{
"/*": "Don't modify slug - If required, do it using cli edit command",
"name": "Campsite",
"slug": "campsite",
"type": "campsite_conferencing",
"logo": "icon.svg",
"url": "https://campsite.co",
"variant": "conferencing",
"categories": ["conferencing"],
"publisher": "Campsite",
"email": "support@campsite.co",
"appData": {
"location": {
"type": "integrations:{SLUG}_conferencing",
"label": "{TITLE}",
"linkType": "dynamic"
}
},
"description": "Campsite is an all-in-one communication app that helps async teams move faster. Posts, docs, calls, and chat in one place — finally. Everything is searchable and easy to find.",
"isTemplate": false,
"__createdUsingCli": true,
"__template": "event-type-location-video-static"
}
+2
View File
@@ -0,0 +1,2 @@
export * as api from "./api";
export * as lib from "./lib";
@@ -0,0 +1,47 @@
import type { CalendarEvent, EventBusyDate } from "@calcom/types/Calendar";
import type { CredentialPayload } from "@calcom/types/Credential";
import type { PartialReference } from "@calcom/types/EventManager";
import type { VideoApiAdapter, VideoCallData } from "@calcom/types/VideoApiAdapter";
type CampsiteToken = {
access_token: string;
};
const CampsiteVideoApiAdapter = (credential: CredentialPayload): VideoApiAdapter => {
return {
createMeeting: async (_event: CalendarEvent): Promise<VideoCallData> => {
const keys = credential.key as CampsiteToken;
const { access_token } = keys;
const response = await fetch("https://api.campsite.co/v1/integrations/cal_dot_com/call_rooms", {
method: "POST",
headers: {
Authorization: `Bearer ${access_token}`,
"Content-Type": "application/json",
},
});
const callRoom = await response.json();
return {
type: "campsite_conferencing",
id: callRoom.id,
password: "",
url: callRoom.url,
};
},
updateMeeting: async (bookingRef: PartialReference, _event: CalendarEvent): Promise<VideoCallData> => {
return {
type: "campsite_conferencing",
id: bookingRef.externalCalendarId ? bookingRef.externalCalendarId : "",
password: "",
url: bookingRef.meetingUrl ? bookingRef.meetingUrl : "",
};
},
deleteMeeting: async (_uid: string): Promise<unknown> => {
return {};
},
getAvailability: async (_dateFrom?: string, _dateTo?: string): Promise<EventBusyDate[]> => {
return [];
},
};
};
export default CampsiteVideoApiAdapter;
+1
View File
@@ -0,0 +1 @@
export { default as VideoApiAdapter } from "./VideoApiAdapter";
+14
View File
@@ -0,0 +1,14 @@
{
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"name": "@calcom/campsite",
"version": "0.0.0",
"main": "./index.ts",
"dependencies": {
"@calcom/lib": "*"
},
"devDependencies": {
"@calcom/types": "*"
},
"description": "Campsite is an all-in-one communication app that helps async teams move faster. Posts, docs, calls, and chat in one place — finally. Everything is searchable and easy to find."
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

+157
View File
@@ -0,0 +1,157 @@
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_2575_4550)">
<g filter="url(#filter0_di_2575_4550)">
<path
d="M48 0H16C7.16344 0 0 7.16344 0 16V48C0 56.8366 7.16344 64 16 64H48C56.8366 64 64 56.8366 64 48V16C64 7.16344 56.8366 0 48 0Z"
fill="url(#paint0_radial_2575_4550)"
/>
</g>
<g filter="url(#filter1_dddii_2575_4550)">
<path
d="M9.52012 41.9532L22.0953 20.6545C22.3348 20.2489 22.7709 19.9999 23.2421 19.9999H43.7279C44.7587 19.9999 45.3987 21.1203 44.8747 22.0077L32.2996 43.3064C32.0601 43.7121 31.624 43.961 31.1528 43.961H10.667C9.63615 43.961 8.99619 42.8406 9.52012 41.9532Z"
fill="url(#paint1_linear_2575_4550)"
fill-opacity="0.96"
/>
<path
d="M38.9673 43.9609H53.2769C54.2811 43.9609 54.924 42.8921 54.4531 42.0055L47.6958 29.2815C47.2086 28.3639 45.9046 28.333 45.3743 29.2264L37.8221 41.9505C37.2954 42.8378 37.9352 43.9609 38.9673 43.9609Z"
fill="url(#paint2_linear_2575_4550)"
fill-opacity="0.96"
/>
</g>
</g>
<defs>
<filter
id="filter0_di_2575_4550"
x="-40"
y="0"
width="144"
height="144"
filterUnits="userSpaceOnUse"
color-interpolation-filters="sRGB"
>
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feColorMatrix
in="SourceAlpha"
type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
result="hardAlpha"
/>
<feOffset dy="40" />
<feGaussianBlur stdDeviation="20" />
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0" />
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_2575_4550" />
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_2575_4550" result="shape" />
<feColorMatrix
in="SourceAlpha"
type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
result="hardAlpha"
/>
<feOffset dy="16" />
<feGaussianBlur stdDeviation="8" />
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" />
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.25 0" />
<feBlend mode="normal" in2="shape" result="effect2_innerShadow_2575_4550" />
</filter>
<filter
id="filter1_dddii_2575_4550"
x="1.33325"
y="15.9999"
width="61.2772"
height="39.9611"
filterUnits="userSpaceOnUse"
color-interpolation-filters="sRGB"
>
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feColorMatrix
in="SourceAlpha"
type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
result="hardAlpha"
/>
<feOffset dy="1" />
<feGaussianBlur stdDeviation="1" />
<feColorMatrix type="matrix" values="0 0 0 0 0.733333 0 0 0 0 0.0745098 0 0 0 0 0.0745098 0 0 0 0.316416 0" />
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_2575_4550" />
<feColorMatrix
in="SourceAlpha"
type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
result="hardAlpha"
/>
<feOffset dy="2" />
<feGaussianBlur stdDeviation="2" />
<feColorMatrix type="matrix" values="0 0 0 0 0.733333 0 0 0 0 0.0745098 0 0 0 0 0.0745098 0 0 0 0.358691 0" />
<feBlend mode="normal" in2="effect1_dropShadow_2575_4550" result="effect2_dropShadow_2575_4550" />
<feColorMatrix
in="SourceAlpha"
type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
result="hardAlpha"
/>
<feOffset dy="4" />
<feGaussianBlur stdDeviation="4" />
<feColorMatrix type="matrix" values="0 0 0 0 0.733333 0 0 0 0 0.0745098 0 0 0 0 0.0745098 0 0 0 0.31 0" />
<feBlend mode="normal" in2="effect2_dropShadow_2575_4550" result="effect3_dropShadow_2575_4550" />
<feBlend mode="normal" in="SourceGraphic" in2="effect3_dropShadow_2575_4550" result="shape" />
<feColorMatrix
in="SourceAlpha"
type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
result="hardAlpha"
/>
<feOffset dy="-2" />
<feGaussianBlur stdDeviation="12" />
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" />
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.4 0" />
<feBlend mode="normal" in2="shape" result="effect4_innerShadow_2575_4550" />
<feColorMatrix
in="SourceAlpha"
type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
result="hardAlpha"
/>
<feOffset />
<feGaussianBlur stdDeviation="2" />
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" />
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" />
<feBlend mode="normal" in2="effect4_innerShadow_2575_4550" result="effect5_innerShadow_2575_4550" />
</filter>
<radialGradient
id="paint0_radial_2575_4550"
cx="0"
cy="0"
r="1"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(13.5534 65.4757) rotate(-74.2658) scale(68.0246 308.191)"
>
<stop stop-color="#FA4438" />
<stop offset="1" stop-color="#FF6D23" />
</radialGradient>
<linearGradient
id="paint1_linear_2575_4550"
x1="46.122"
y1="28.5745"
x2="46.122"
y2="43.9609"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="white" />
<stop offset="1" stop-color="white" stop-opacity="0.86" />
</linearGradient>
<linearGradient
id="paint2_linear_2575_4550"
x1="46.122"
y1="28.5745"
x2="46.122"
y2="43.9609"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="white" />
<stop offset="1" stop-color="white" stop-opacity="0.86" />
</linearGradient>
<clipPath id="clip0_2575_4550">
<rect width="64" height="64" rx="16" fill="white" />
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 6.0 KiB

+8
View File
@@ -0,0 +1,8 @@
import { z } from "zod";
export const appDataSchema = z.object({});
export const appKeysSchema = z.object({
client_id: z.string().min(1),
client_secret: z.string().min(1),
});
+1
View File
@@ -4,6 +4,7 @@ const appStore = {
applecalendar: () => import("./applecalendar"),
aroundvideo: () => import("./around"),
caldavcalendar: () => import("./caldavcalendar"),
campsiteconferencing: () => import("./campsite"),
closecom: () => import("./closecom"),
dailyvideo: () => import("./dailyvideo"),
googlecalendar: () => import("./googlecalendar"),
@@ -1,4 +1,4 @@
import { LRUCache } from "lru-cache";
import * as lruCache from "lru-cache";
import type { GetServerSidePropsContext, NextApiRequest, NextApiResponse } from "next";
import type { AuthOptions, Session } from "next-auth";
import { getToken } from "next-auth/jwt";
@@ -15,7 +15,7 @@ const log = logger.getSubLogger({ prefix: ["getServerSession"] });
* Stores the session in memory using the stringified token as the key.
*
*/
const CACHE = new LRUCache<string, Session>({ max: 1000 });
const CACHE = new lruCache.LRUCache<string, Session>({ max: 1000 });
/**
* This is a slimmed down version of the `getServerSession` function from
@@ -93,7 +93,7 @@ export const useBookings = ({ event, hashedLink, bookingForm, metadata, teamMemb
function refactorMeWithoutEffect() {
const data = _instantBooking.data;
if (!data) return;
if (!data || !data.booking) return;
try {
const locationVideoCallUrl: string | undefined = bookingMetadataSchema.parse(
data.booking?.metadata || {}
@@ -2,6 +2,7 @@ import { useState } from "react";
import type { UseFormReturn } from "react-hook-form";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { TimeUnit } from "@calcom/prisma/enums";
import {
Dropdown,
DropdownItem,
@@ -12,21 +13,50 @@ import {
TextField,
} from "@calcom/ui";
import { getWorkflowTimeUnitOptions } from "../lib/getOptions";
import type { FormValues } from "../pages/workflow";
const TIME_UNITS = [TimeUnit.DAY, TimeUnit.HOUR, TimeUnit.MINUTE] as const;
type Props = {
form: UseFormReturn<FormValues>;
disabled: boolean;
};
const TimeUnitAddonSuffix = ({
DropdownItems,
timeUnitOptions,
form,
}: {
form: UseFormReturn<FormValues>;
DropdownItems: JSX.Element;
timeUnitOptions: { [x: string]: string };
}) => {
// because isDropdownOpen already triggers a render cycle we can use getValues()
// instead of watch() function
const timeUnit = form.getValues("timeUnit");
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
return (
<Dropdown onOpenChange={setIsDropdownOpen}>
<DropdownMenuTrigger asChild>
<button className="flex items-center">
<div className="mr-1 w-3/5">{timeUnit ? timeUnitOptions[timeUnit] : "undefined"}</div>
<div className="w-1/4 pt-1">
{isDropdownOpen ? <Icon name="chevron-up" /> : <Icon name="chevron-down" />}
</div>
</button>
</DropdownMenuTrigger>
<DropdownMenuContent>{DropdownItems}</DropdownMenuContent>
</Dropdown>
);
};
export const TimeTimeUnitInput = (props: Props) => {
const { form } = props;
const { t } = useLocale();
const timeUnitOptions = getWorkflowTimeUnitOptions(t);
const [timeUnit, setTimeUnit] = useState(form.getValues("timeUnit"));
const timeUnitOptions = TIME_UNITS.reduce((acc, option) => {
acc[option] = t(`${option.toLowerCase()}_timeUnit`);
return acc;
}, {} as { [x: string]: string });
return (
<div className="flex">
<div className="grow">
@@ -39,33 +69,26 @@ export const TimeTimeUnitInput = (props: Props) => {
className="-mt-2 rounded-r-none text-sm focus:ring-0"
{...form.register("time", { valueAsNumber: true })}
addOnSuffix={
<Dropdown>
<DropdownMenuTrigger asChild>
<button className="flex items-center">
<div className="mr-1 w-3/4">
{timeUnit ? t(`${timeUnit.toLowerCase()}_timeUnit`) : "undefined"}{" "}
</div>
<div className="w-1/4 pt-1">
<Icon name="chevron-down" />
</div>
</button>
</DropdownMenuTrigger>
<DropdownMenuContent>
{timeUnitOptions.map((option, index) => (
<DropdownMenuItem key={index} className="outline-none">
<DropdownItem
key={index}
type="button"
onClick={() => {
setTimeUnit(option.value);
form.setValue("timeUnit", option.value);
}}>
{option.label}
</DropdownItem>
</DropdownMenuItem>
))}
</DropdownMenuContent>
</Dropdown>
<TimeUnitAddonSuffix
form={form}
timeUnitOptions={timeUnitOptions}
DropdownItems={
<>
{TIME_UNITS.map((timeUnit, index) => (
<DropdownMenuItem key={index} className="outline-none">
<DropdownItem
key={index}
type="button"
onClick={() => {
form.setValue("timeUnit", timeUnit);
}}>
{timeUnitOptions[timeUnit]}
</DropdownItem>
</DropdownMenuItem>
))}
</>
}
/>
}
/>
</div>
@@ -4,7 +4,6 @@ import type { WorkflowActions } from "@calcom/prisma/enums";
import { isSMSOrWhatsappAction, isWhatsappAction, isEmailToAttendeeAction } from "./actionHelperFunctions";
import {
TIME_UNIT,
WHATSAPP_WORKFLOW_TEMPLATES,
WORKFLOW_ACTIONS,
BASIC_WORKFLOW_TEMPLATES,
@@ -32,12 +31,6 @@ export function getWorkflowTriggerOptions(t: TFunction) {
});
}
export function getWorkflowTimeUnitOptions(t: TFunction) {
return TIME_UNIT.map((timeUnit) => {
return { label: t(`${timeUnit.toLowerCase()}_timeUnit`), value: timeUnit };
});
}
export function getWorkflowTemplateOptions(t: TFunction, action: WorkflowActions | undefined) {
const TEMPLATES =
action && isWhatsappAction(action)
+3
View File
@@ -0,0 +1,3 @@
VITE_BOOKER_EMBED_OAUTH_CLIENT_ID="clye3x2f6000133strp0tf85t"
VITE_BOOKER_EMBED_API_URL="http://localhost:5555/api/v2"
@@ -0,0 +1,15 @@
import type { BookerPlatformWrapperAtomProps } from "../booker/BookerPlatformWrapper";
import { BookerPlatformWrapper } from "../booker/BookerPlatformWrapper";
import { CalProvider } from "../cal-provider/CalProvider";
export const BookerEmbed = (props: BookerPlatformWrapperAtomProps) => {
return (
<CalProvider
clientId={import.meta.env.VITE_BOOKER_EMBED_OAUTH_CLIENT_ID}
options={{
apiUrl: import.meta.env.VITE_BOOKER_EMBED_API_URL,
}}>
<BookerPlatformWrapper {...props} />
</CalProvider>
);
};
@@ -0,0 +1 @@
export { BookerEmbed } from "./BookerEmbed";
@@ -41,7 +41,7 @@ import { useMe } from "../hooks/useMe";
import { useSlots } from "../hooks/useSlots";
import { AtomsWrapper } from "../src/components/atoms-wrapper";
type BookerPlatformWrapperAtomProps = Omit<BookerProps, "username" | "entity"> & {
export type BookerPlatformWrapperAtomProps = Omit<BookerProps, "username" | "entity"> & {
rescheduleUid?: string;
bookingUid?: string;
username: string | string[];
+1
View File
@@ -14,3 +14,4 @@ export { useGetBookings } from "./hooks/useGetBookings";
export { useMe } from "./hooks/useMe";
export { OutlookConnect } from "./connect/outlook/OutlookConnect";
export * as Connect from "./connect";
export { BookerEmbed } from "./booker-embed";
@@ -28,6 +28,9 @@ export function Navbar({ username }: { username?: string }) {
<li>
<Link href="/bookings">My Bookings</Link>
</li>
<li>
<Link href="/embed">Embed</Link>
</li>
</ul>
</div>
</nav>
@@ -1,9 +1,11 @@
import "@/styles/globals.css";
import type { AppProps } from "next/app";
import { Poppins } from "next/font/google";
import { usePathname } from "next/navigation";
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import { CalProvider } from "@calcom/atoms";
import { CalProvider, BookerEmbed } from "@calcom/atoms";
import "@calcom/atoms/globals.min.css";
const poppins = Poppins({ subsets: ["latin"], weight: ["400", "800"] });
@@ -25,6 +27,8 @@ export default function App({ Component, pageProps }: AppProps) {
const [accessToken, setAccessToken] = useState("");
const [email, setUserEmail] = useState("");
const [username, setUsername] = useState("");
const router = useRouter();
const pathname = usePathname();
useEffect(() => {
const randomEmail = generateRandomEmail();
@@ -59,6 +63,30 @@ export default function App({ Component, pageProps }: AppProps) {
</>
)}
</CalProvider>{" "}
{pathname === "/embed" && (
<div>
<BookerEmbed
customClassNames={{
bookerContainer: "!bg-[#F5F2FE] [&_button:!rounded-full] border-subtle border",
datePickerCustomClassNames: {
datePickerDatesActive: "!bg-[#D7CEF5]",
},
eventMetaCustomClassNames: {
eventMetaTitle: "text-[#7151DC]",
},
availableTimeSlotsCustomClassNames: {
availableTimeSlotsHeaderContainer: "!bg-[#F5F2FE]",
availableTimes: "!bg-[#D7CEF5]",
},
}}
username={username}
eventSlug="thirty-minutes"
onCreateBookingSuccess={(data) => {
router.push(`/${data.data.uid}`);
}}
/>
</div>
)}
</div>
);
}
@@ -0,0 +1,15 @@
import { Navbar } from "@/components/Navbar";
import { Inter } from "next/font/google";
const inter = Inter({ subsets: ["latin"] });
export default function Embed(props: { calUsername: string; calEmail: string }) {
return (
<main className={`flex ${inter.className} text-default flex flex-col`}>
<Navbar username={props.calUsername} />
<div>
<h1 className="mx-8 my-4 text-2xl font-bold">This is the booker embed</h1>
</div>
</main>
);
}
+42
View File
@@ -0,0 +1,42 @@
# How to work with platform libraries in Dev
whenever you want to change anything in platform libraries, or if you modify the code of one of the functions imported in libraries you will need to build the code again.
first thing to know is that we version this package using NPM
https://www.npmjs.com/package/@calcom/platform-libraries?activeTab=code
In order to work using the locally built platform libraries you need to:
- in packages/platform/libraries/package.json set the version to for example 1.2.3
- in apps/api/v2/package.json add to dependencies:
"@calcom/platform-libraries-1.2.3": "npm:@calcom/platform-libraries@1.2.3"
- in api v2 code simply import using the new alias:
import {
getAllUserBookings as getAllUserBookings1.2.3,
} from "@calcom/platform-libraries-1.2.3";
since the versions are matching in both package.json yarn will try to use the locally built code
now go to packages/platform/libraries and do
- yarn build
then go back to /apps/api/v2 and run
- yarn
- yarn dev
# Before Merging to main
- Publish Your Version of Libraries on NPM:
- To publish, ensure you are a contributor to the platform libraries' NPM package.
- Authenticate yourself via the CLI using npm auth.
- Increment the version number accordingly.
- Run yarn publish to publish your version.
- Once it's published, change back the version in packages/platform/libraries/package.json back to 0.0.0
- Run yarn
- You should now be using the npm package instead of the locally built version
+5
View File
@@ -1,3 +1,4 @@
import { ApiProperty } from "@nestjs/swagger";
import { Transform } from "class-transformer";
import { IsNumber, Min, Max, IsOptional } from "class-validator";
import type { Response as BaseResponse } from "express";
@@ -41,6 +42,7 @@ export type ApiResponse<T = undefined> = T extends undefined
export type ApiResponseMaybeRedirect<T = undefined> = ApiResponse<T> | ApiRedirectResponseType;
export class Pagination {
@ApiProperty({ required: false, description: "The number of items to return", example: 10 })
@Transform(({ value }: { value: string }) => value && parseInt(value))
@IsNumber()
@Min(1)
@@ -49,6 +51,7 @@ export class Pagination {
limit?: number;
@Transform(({ value }: { value: string }) => value && parseInt(value))
@ApiProperty({ required: false, description: "The number of items to skip", example: 0 })
@IsNumber()
@Min(0)
@Max(100)
@@ -57,6 +60,7 @@ export class Pagination {
}
export class SkipTakePagination {
@ApiProperty({ required: false, description: "The number of items to return", example: 10 })
@Transform(({ value }: { value: string }) => value && parseInt(value))
@IsNumber()
@Min(1)
@@ -64,6 +68,7 @@ export class SkipTakePagination {
@IsOptional()
take?: number;
@ApiProperty({ required: false, description: "The number of items to skip", example: 0 })
@Transform(({ value }: { value: string }) => value && parseInt(value))
@IsNumber()
@Min(0)
@@ -0,0 +1,8 @@
/*
Warnings:
- A unique constraint covering the columns `[username,organizationId]` on the table `Profile` will be added. If there are existing duplicate values, this will fail.
*/
-- CreateIndex
CREATE UNIQUE INDEX "Profile_username_organizationId_key" ON "Profile"("username", "organizationId");
+4
View File
@@ -349,7 +349,11 @@ model Profile {
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// A user can have multiple profiles in different organizations
@@unique([userId, organizationId])
// Allow username reuse only across different organizations
@@unique([username, organizationId])
@@index([uid])
@@index([userId])
@@index([organizationId])
@@ -7,9 +7,9 @@ type NoShowOptions = {
};
export const noShowHandler = async ({ input }: NoShowOptions) => {
const { bookingUid, attendees } = input;
const { bookingUid, attendees, noShowHost } = input;
return handleMarkNoShow({ bookingUid, attendees });
return handleMarkNoShow({ bookingUid, attendees, noShowHost });
};
export default noShowHandler;