Bläddra i källkod

Implement browsing of ringtones, update stories

The browsing of ringtones are now in a pagination-based
format. The response from the backend has now been changed
in order to account the total ringtones count, although
there can be some optimizations regarding the repetitive
queries in the future (such as pagination tokens, caching).

The ringtones lack a way to get the composer's user details
such as usernames; this will be implemented in the future.
master
TheoryOfNekomata 3 år sedan
förälder
incheckning
439cba3a62
14 ändrade filer med 581 tillägg och 140 borttagningar
  1. +6
    -1
      REQUIREMENTS.md
  2. +40
    -0
      packages/app-web/src/components/molecules/presentation/Card/index.tsx
  3. +68
    -0
      packages/app-web/src/components/organisms/presentation/RingtoneCardDisplay/index.tsx
  4. +237
    -0
      packages/app-web/src/components/templates/BrowseRingtones/index.tsx
  5. +4
    -27
      packages/app-web/src/components/templates/CreateRingtone/index.tsx
  6. +32
    -0
      packages/app-web/src/data/layout.ts
  7. +7
    -0
      packages/app-web/src/modules/ringtone/client.ts
  8. +90
    -7
      packages/app-web/src/pages/index.tsx
  9. +0
    -5
      packages/app-web/src/pages/my/create/ringtones/[id].tsx
  10. +0
    -5
      packages/app-web/src/pages/my/create/ringtones/index.tsx
  11. +1
    -3
      packages/app-web/src/utils/api/fetch.ts
  12. +7
    -0
      packages/app-web/src/utils/presentation/date.ts
  13. +4
    -2
      packages/service-core/src/modules/ringtone/controller.ts
  14. +85
    -90
      packages/service-core/src/modules/ringtone/service.ts

+ 6
- 1
REQUIREMENTS.md Visa fil

@@ -27,8 +27,13 @@

- As a client, I want to search for ringtones.
- [ ] In the front-end, the client requests provides a search keyword to request for ringtones from the back-end.
- [X] In the back-end, the server retrieves the ringtones whose name matches the search keyword provided by the
- [ ] In the back-end, the server retrieves the ringtones whose name matches the search keyword provided by the
front-end.
- [X] The server must retrieve ringtones containing the search keywords in the name
- Query `foo bar` must match names containing `foo` _or_ `bar`.
- [ ] The server must retrieve ringtones with note data similar to the search keyword as melody
- The server should match data `8c4 8d#5` and `4c5 4d#5` for the query `c d#`.
- The search should ignore rests.
- As a client, I want to search for composers.
- [ ] In the front-end, the client requests provides a search keyword to request for composers from the back-end.
- [X] In the back-end, the server retrieves the composers whose name matches the search keyword provided by the


+ 40
- 0
packages/app-web/src/components/molecules/presentation/Card/index.tsx Visa fil

@@ -0,0 +1,40 @@
import {FC} from 'react';
import styled from 'styled-components';

const Base = styled('div')({
borderRadius: '0.25rem',
position: 'relative',
padding: '1rem',
boxSizing: 'border-box',
'::after': {
content: "''",
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
boxSizing: 'border-box',
borderRadius: 'inherit',
pointerEvents: 'none',
border: '1px solid',
},
})

type Props = {
className?: string,
}

const Card: FC<Props> = ({
children,
...etcProps
}) => {
return (
<Base
{...etcProps}
>
{children}
</Base>
)
}

export default Card

+ 68
- 0
packages/app-web/src/components/organisms/presentation/RingtoneCardDisplay/index.tsx Visa fil

@@ -0,0 +1,68 @@
import {FC} from 'react';
import styled from 'styled-components';
import {formatCreatedAt} from '../../../../utils/presentation/date';

const Primary = styled('div')({
display: 'grid',
gridTemplateColumns: 'repeat(2, 1fr)',
justifyContent: 'space-between',
alignItems: 'center',
gap: '0.5rem',
'@media (min-width: 720px)': {
gridTemplateColumns: '1fr',
},
})

const Name = styled('div')({
fontWeight: 'bolder',
})

const CreatedAt = styled('time')({
fontSize: '0.75rem',
textAlign: 'right',
'@media (min-width: 720px)': {
textAlign: 'left',
},
})

const Data = styled('div')({
marginTop: '0.5rem',
fontFamily: 'monospace',
fontSize: '0.75rem',
height: '3.75em',
lineHeight: 1.25,
overflow: 'hidden',
})

type Props = {
name: string,
data: string,
createdAt: Date,
}

const RingtoneCardDisplay: FC<Props> = ({
name,
data,
createdAt: rawCreatedAt,
}) => {
const createdAt = new Date(rawCreatedAt)
return (
<>
<Primary>
<Name>
{name}
</Name>
<CreatedAt
dateTime={createdAt.toISOString()}
>
{formatCreatedAt(createdAt)}
</CreatedAt>
</Primary>
<Data>
{data}
</Data>
</>
)
}

export default RingtoneCardDisplay

+ 237
- 0
packages/app-web/src/components/templates/BrowseRingtones/index.tsx Visa fil

@@ -0,0 +1,237 @@
import {FC, FormEventHandler} from 'react';
import styled from 'styled-components';
import { LeftSidebarWithMenu } from '@theoryofnekomata/viewfinder'
import Brand from '../../molecules/brand/Brand';
import Link from '../../molecules/navigation/Link';
import OmnisearchForm from '../../organisms/forms/Omnisearch';
import {Session} from '@auth0/nextjs-auth0';
import {models} from '@tonality/library-common';
import RingtoneCardDisplay from '../../organisms/presentation/RingtoneCardDisplay';
import Card from '../../molecules/presentation/Card';
import {moreLinkMenuItem, sidebarMenuItems} from '../../../data/layout';
import ActionButton from '../../molecules/forms/ActionButton';
import NumericInput from '../../molecules/forms/NumericInput';

const TopBarComponent = styled('div')({
backgroundColor: 'var(--color-bg, white)',
})

const SidebarMenuComponent = styled('div')({
backgroundColor: 'var(--color-bg, white)',
})

const Avatar = styled('img')({
objectFit: 'cover',
objectPosition: 'center',
width: '3rem',
height: '3rem',
borderRadius: '50%',
})

const CardList = styled('div')({
display: 'grid',
gap: '1rem',
margin: '2rem 0',
'@media (min-width: 720px)': {
gridTemplateColumns: 'repeat(2, 1fr)',
},
})

const CardLink = styled(Link)({
display: 'block',
textDecoration: 'none',
})

const StyledCard = styled(Card)({
height: '100%',
})

const Footer = styled('div')({
display: 'grid',
gap: '1rem',
gridTemplateColumns: '1fr',
margin: '2rem 0',
'@media (min-width: 720px)': {
gridTemplateColumns: 'auto 1fr auto',
},
})

const TakeForm = styled('form')({
display: 'grid',
gap: '1rem',
gridTemplateColumns: '1fr auto',
})

const BrowseArea = styled('div')({
'@media (min-width: 720px)': {
gridColumn: 3,
},
})

const TakeInput = styled(NumericInput)({
'@media (min-width: 720px)': {
width: '6rem',
},
})

type Props = {
onSearch?: FormEventHandler,
onNextPage?: FormEventHandler,
session?: Partial<Session>,
ringtones: models.Ringtone[],
skip?: number,
take?: number,
total?: number,
loading?: boolean,
}

const BrowseRingtonesTemplate: FC<Props> = ({
onSearch,
session,
ringtones,
skip = 0,
take = 10,
total = 0,
onNextPage,
loading,
}) => {
return (
<LeftSidebarWithMenu.Layout
brand={
<Brand />
}
userLink={
<Link
href={{
query: {
popup: 'user',
},
}}
>
{
session
&& (
<Avatar
src={session.user.picture}
/>
)
}
</Link>
}
topBarComponent={TopBarComponent}
sidebarMenuComponent={SidebarMenuComponent}
topBarCenter={
<OmnisearchForm
labels={{
form: 'Search',
query: 'Query',
}}
onSubmit={onSearch}
action="/api/a/search"
/>
}
linkComponent={({ url, icon, label, }) => (
<Link
href={url}
>
<LeftSidebarWithMenu.SidebarMenuContainer>
<LeftSidebarWithMenu.SidebarMenuItemIcon>
{icon}
</LeftSidebarWithMenu.SidebarMenuItemIcon>
{label}
</LeftSidebarWithMenu.SidebarMenuContainer>
</Link>
)}
moreLinkMenuItem={moreLinkMenuItem}
moreLinkComponent={({ url, icon, label, }) => (
<Link
href={url}
>
<LeftSidebarWithMenu.MoreSidebarMenuContainer>
<LeftSidebarWithMenu.MoreSidebarMenuItemIcon>
{icon}
</LeftSidebarWithMenu.MoreSidebarMenuItemIcon>
{label}
</LeftSidebarWithMenu.MoreSidebarMenuContainer>
</Link>
)}
sidebarMenuItems={sidebarMenuItems}
>
<LeftSidebarWithMenu.ContentContainer>
{
Array.isArray(ringtones)
&& (
<>
<CardList>
{ringtones.map(r => (
<CardLink
key={r.id.toString()}
href={{
pathname: '/ringtones/[id]',
query: {
id: r.id.toString(),
},
}}
>
<StyledCard>
<RingtoneCardDisplay
name={r.name}
data={r.data}
createdAt={r.createdAt}
/>
</StyledCard>
</CardLink>
))}
</CardList>
<Footer>
<TakeForm>
<TakeInput
label="Visible Items"
name="take"
defaultValue={take}
/>
<ActionButton
type="submit"
disabled={loading}
>
Set
</ActionButton>
</TakeForm>
<BrowseArea>
{
skip + take < total
&& (
<form
onSubmit={onNextPage}
>
<input
type="hidden"
name="skip"
value={skip + take}
/>
<input
type="hidden"
name="take"
value={take}
/>
<ActionButton
type="submit"
disabled={loading}
block
>
Browse More
</ActionButton>
</form>
)
}
</BrowseArea>
</Footer>
</>
)
}
</LeftSidebarWithMenu.ContentContainer>
</LeftSidebarWithMenu.Layout>
)
}

export default BrowseRingtonesTemplate

+ 4
- 27
packages/app-web/src/components/templates/CreateRingtone/index.tsx Visa fil

@@ -7,6 +7,7 @@ import CreateRingtoneForm from '../../organisms/forms/CreateRingtone'
import Link from '../../molecules/navigation/Link'
import OmnisearchForm from '../../organisms/forms/Omnisearch'
import Brand from '../../molecules/brand/Brand'
import {moreLinkMenuItem, sidebarMenuItems} from '../../../data/layout';

const TopBarComponent = styled('div')({
backgroundColor: 'var(--color-bg, white)',
@@ -17,7 +18,7 @@ const SidebarMenuComponent = styled('div')({
})

const Padding = styled('div')({
padding: '2rem 0',
margin: '2rem 0',
})

const Avatar = styled('img')({
@@ -33,8 +34,6 @@ type Props = {
onSubmit?: FormEventHandler,
session: Partial<Session>,
currentRingtone?: models.Ringtone,
composerRingtones: models.Ringtone[],

updateTempo: ({ songRef, dataRef, setTempo, }) => (...args: unknown[]) => void,
updateView: ({ setNoteGlyph, setRestGlyph, noteGlyphs, restGlyphs, }) => (...args: unknown[]) => void,
addRest: ({ formRef, dataRef, songRef, }) => (...args: unknown[]) => void,
@@ -49,7 +48,6 @@ const CreateRingtoneTemplate: FC<Props> = ({
onSubmit,
session,
currentRingtone = {},
composerRingtones = [],
updateTempo,
updateView,
addRest,
@@ -100,11 +98,7 @@ const CreateRingtoneTemplate: FC<Props> = ({
</LeftSidebarWithMenu.SidebarMenuContainer>
</Link>
)}
moreLinkMenuItem={{
label: 'More',
icon: 'M',
url: {},
}}
moreLinkMenuItem={moreLinkMenuItem}
moreLinkComponent={({ url, icon, label, }) => (
<Link
href={url}
@@ -117,24 +111,7 @@ const CreateRingtoneTemplate: FC<Props> = ({
</LeftSidebarWithMenu.MoreSidebarMenuContainer>
</Link>
)}
sidebarMenuItems={[
{
id: 'browse',
label: 'Browse',
icon: 'B',
url: {
pathname: '/ringtones',
},
},
{
id: 'compose',
label: 'Compose',
icon: 'C',
url: {
pathname: '/my/create/ringtone',
},
},
]}
sidebarMenuItems={sidebarMenuItems}
>
<Padding>
<CreateRingtoneForm


+ 32
- 0
packages/app-web/src/data/layout.ts Visa fil

@@ -0,0 +1,32 @@
export const sidebarMenuItems = [
{
id: 'browse',
label: 'Browse',
icon: 'B',
url: {
pathname: '/',
},
},
{
id: 'compose',
label: 'Compose',
icon: 'C',
url: {
pathname: '/my/create/ringtones',
},
},
{
id: 'bin',
label: 'Bin',
icon: 'B',
url: {
pathname: '/my/bin',
},
},
]

export const moreLinkMenuItem = {
label: 'More',
icon: 'M',
url: {},
}

+ 7
- 0
packages/app-web/src/modules/ringtone/client.ts Visa fil

@@ -52,4 +52,11 @@ export default class RingtoneClient {
return data
}
}

browse = async ({ skip, take }) => {
const response = await this.fetchClient(endpoints.browse({ skip, take, }))
if (response.ok) {
return response.json()
}
}
}

+ 90
- 7
packages/app-web/src/pages/index.tsx Visa fil

@@ -1,17 +1,100 @@
import {GetServerSideProps, NextPage} from 'next';
type Props = {}
import BrowseRingtonesTemplate from '../components/templates/BrowseRingtones';
import {getSession, Session} from '@auth0/nextjs-auth0';
import {models} from '@tonality/library-common';
import RingtoneClient from '../modules/ringtone/client';
import {useEffect, useState} from 'react';
import getFormValues from '@theoryofnekomata/formxtra';
import {useRouter} from 'next/router';

type Props = {
session: Partial<Session>,
ringtones: models.Ringtone[],
skip: number,
take: number,
total: number,
}

const IndexPage: NextPage<Props> = ({
session,
ringtones: ringtonesProp,
skip: skipProp,
take: takeProp,
total: totalProp,
}) => {
const [ringtoneClient, setRingtoneClient] = useState<RingtoneClient>(null)
const [ringtones, setRingtones] = useState(ringtonesProp)
const [skip, setSkip] = useState(skipProp)
const [take, setTake] = useState(takeProp)
const [total, setTotal] = useState(totalProp)
const [loading, setLoading] = useState(false)
const router = useRouter()

const getNextPage = async (e) => {
// e.preventDefault()
// const values = getFormValues(e.target)
// setLoading(true)
// try {
// const {data: ringtones, skip, take, total} = await ringtoneClient.browse({
// skip: Number(values.skip),
// take: Number(values.take)
// })
// setRingtones(ringtones)
// setSkip(skip)
// setTake(take)
// setTotal(total)
// router.push({
// query: {
// skip: Number(values.skip),
// take: Number(values.take),
// },
// })
// } catch (err) {
// console.log(err)
// }
// setLoading(false)
}

useEffect(() => {
setRingtoneClient(new RingtoneClient(process.env.NEXT_PUBLIC_API_BASE_URL, session))
}, [])

const IndexPage: NextPage<Props> = () => {
return (
<>
Landing Page Here
</>
<BrowseRingtonesTemplate
session={session}
ringtones={ringtones}
skip={skip}
take={take}
total={total}
loading={loading}
onNextPage={getNextPage}
/>
);
};

export const getServerSideProps: GetServerSideProps = async (ctx) => {
export const getServerSideProps: GetServerSideProps = async ({ req, res, query }) => {
const authSession = getSession(req, res)
const session = authSession
? {
idToken: authSession.idToken,
token_type: authSession.token_type,
user: authSession.user,
}
: null
const client = new RingtoneClient(process.env.NEXT_PUBLIC_API_BASE_URL, session)
const { data: ringtones, skip, take, total } = await client.browse({
skip: query.skip ? Number(query.skip) : undefined,
take: query.take ? Number(query.take) : undefined,
})

return {
props: {}
props: {
session,
ringtones,
skip,
take,
total,
}
}
}



+ 0
- 5
packages/app-web/src/pages/my/create/ringtones/[id].tsx Visa fil

@@ -12,13 +12,11 @@ import {useRouter} from 'next/router';
type Props = {
session: Partial<Session>,
currentRingtone: models.Ringtone,
composerRingtones: models.Ringtone[],
}

const Page: NextPage<Props> = ({
session,
currentRingtone,
composerRingtones,
}) => {
const [hydrated, setHydrated] = useState(false)
const [ringtoneClient, setRingtoneClient] = useState<RingtoneClient>(null)
@@ -47,7 +45,6 @@ const Page: NextPage<Props> = ({
<CreateRingtoneTemplate
session={session}
currentRingtone={currentRingtone}
composerRingtones={composerRingtones}
addNote={composerClient ? composerClient.addNote : undefined}
addRest={composerClient ? composerClient.addRest : undefined}
togglePlayback={composerClient ? composerClient.togglePlayback : undefined}
@@ -64,7 +61,6 @@ export const getServerSideProps: GetServerSideProps = withPageAuthRequired({
getServerSideProps: async ({ req, res, params }) => {
const { id } = params
const { idToken, token_type, user } = getSession(req, res)
const composerRingtones = []
const session = {
idToken,
token_type,
@@ -83,7 +79,6 @@ export const getServerSideProps: GetServerSideProps = withPageAuthRequired({
props: {
session,
currentRingtone,
composerRingtones,
},
}
},


+ 0
- 5
packages/app-web/src/pages/my/create/ringtones/index.tsx Visa fil

@@ -11,12 +11,10 @@ import {useRouter} from 'next/router';

type Props = {
session: Partial<Session>,
composerRingtones: models.Ringtone[],
}

const Page: NextPage<Props> = ({
session,
composerRingtones,
}) => {
const [hydrated, setHydrated] = useState(false)
const [ringtoneClient, setRingtoneClient] = useState<RingtoneClient>(null)
@@ -44,7 +42,6 @@ const Page: NextPage<Props> = ({
return (
<CreateRingtoneTemplate
session={session}
composerRingtones={composerRingtones}
addNote={composerClient ? composerClient.addNote : undefined}
addRest={composerClient ? composerClient.addRest : undefined}
togglePlayback={composerClient ? composerClient.togglePlayback : undefined}
@@ -60,7 +57,6 @@ const Page: NextPage<Props> = ({
export const getServerSideProps: GetServerSideProps = withPageAuthRequired({
getServerSideProps: async (ctx) => {
const { idToken, token_type, user } = getSession(ctx.req, ctx.res)
const composerRingtones = []
const session = {
idToken,
token_type,
@@ -70,7 +66,6 @@ export const getServerSideProps: GetServerSideProps = withPageAuthRequired({
return {
props: {
session,
composerRingtones,
},
}
},


+ 1
- 3
packages/app-web/src/utils/api/fetch.ts Visa fil

@@ -1,5 +1,3 @@
import {URLSearchParams} from 'url';

export enum Method {
HEAD = 'HEAD',
GET = 'GET',
@@ -45,7 +43,7 @@ export const createFetchClient: CreateFetchClient = ({
}) => {
const theUrl = new URL(url, baseUrl)
if (Boolean(query as unknown)) {
theUrl.search = new URLSearchParams(query).toString()
theUrl.search = new URLSearchParams(query as any).toString()
}
return ff(theUrl.toString(), {
method,


+ 7
- 0
packages/app-web/src/utils/presentation/date.ts Visa fil

@@ -0,0 +1,7 @@
export const formatCreatedAt = (d: Date) => {
const month = d.getMonth().toString().padStart(2, '0')
const date = d.getDate().toString().padStart(2, '0')
const year = d.getFullYear().toString().padStart(4, '0')

return `${year}-${month}-${date}`
}

+ 4
- 2
packages/service-core/src/modules/ringtone/controller.ts Visa fil

@@ -70,10 +70,11 @@ export class RingtoneControllerImpl implements RingtoneController {
const skip = !isNaN(skipNumber) ? skipNumber : 0
const take = !isNaN(takeNumber) ? takeNumber : 10
const rawData = await this.ringtoneService.browse(skip, take, request.user?.sub)
const data = rawData.map(serializeRingtone)
const data = rawData.data.map(serializeRingtone)

reply.raw.statusMessage = 'Multiple Ringtones Retrieved'
return {
total: rawData.total,
data,
skip,
take,
@@ -88,10 +89,11 @@ export class RingtoneControllerImpl implements RingtoneController {
try {
const { 'q': query } = request.query
const rawData = await this.ringtoneService.search(query, request.user?.sub)
const data = rawData.map(serializeRingtone)
const data = rawData.data.map(serializeRingtone)

reply.raw.statusMessage = 'Search Results Retrieved'
return {
total: rawData.total,
data,
}
} catch (err) {


+ 85
- 90
packages/service-core/src/modules/ringtone/service.ts Visa fil

@@ -9,11 +9,16 @@ import {
UpdateDeletedRingtoneError,
} from './response';

type Counted<T> = {
total: number,
data: T[],
}

export default interface RingtoneService {
get(id: Uuid, requesterUserSub?: string): Promise<models.Ringtone>
browseByComposer(composerUserSub: string, skip?: number, take?: number, requesterUserSub?: string): Promise<models.Ringtone[]>
browse(skip?: number, take?: number, requesterUserSub?: string): Promise<models.Ringtone[]>
search(q?: string, requesterUserSub?: string): Promise<models.Ringtone[]>
browseByComposer(composerUserSub: string, skip?: number, take?: number, requesterUserSub?: string): Promise<Counted<models.Ringtone>>
browse(skip?: number, take?: number, requesterUserSub?: string): Promise<Counted<models.Ringtone>>
search(q?: string, requesterUserSub?: string): Promise<Counted<models.Ringtone>>
create(data: Partial<models.Ringtone>, requesterUserSub: string): Promise<models.Ringtone>
update(data: Partial<models.Ringtone>, requesterUserSub: string): Promise<models.Ringtone>
softDelete(id: Uuid, requesterUserSub: string): Promise<models.Ringtone>
@@ -35,35 +40,30 @@ export class RingtoneServiceImpl implements RingtoneService {
private readonly prismaClient: PrismaClient,
) {}

async browseByComposer(composerUserSub: string, skip: number = 0, take: number = 10, requesterUserSub?: string): Promise<models.Ringtone[]> {
let rawData

if (typeof requesterUserSub === 'string' && composerUserSub === requesterUserSub) {
rawData = await this.prismaClient.ringtone.findMany({
where: {
composerUserSub: {
equals: composerUserSub,
},
},
skip,
take,
})
} else {
rawData = await this.prismaClient.ringtone.findMany({
where: {
composerUserSub: {
equals: composerUserSub,
},
deletedAt: {
equals: null,
},
async browseByComposer(composerUserSub: string, skip: number = 0, take: number = 10, requesterUserSub?: string): Promise<Counted<models.Ringtone>> {
const baseArgs = {
where: {
composerUserSub: {
equals: composerUserSub,
},
skip,
take,
})
},
}
if (!(typeof requesterUserSub === 'string' && composerUserSub === requesterUserSub)) {
baseArgs.where['deletedAt'] = {
equals: null,
}
}
const args = {
...baseArgs,
skip,
take,
}
const rawData = await this.prismaClient.ringtone.findMany(baseArgs)
const total = await this.prismaClient.ringtone.count(args)
return {
data: rawData.map(serializeRingtone),
total,
}

return rawData.map(serializeRingtone)
}

async get(id: Uuid, requesterUserSub?: string): Promise<models.Ringtone> {
@@ -89,77 +89,72 @@ export class RingtoneServiceImpl implements RingtoneService {
return serializeRingtone(ringtone)
}

async browse(skip: number = 0, take: number = 10, requesterUserSub?: string): Promise<models.Ringtone[]> {
let rawData
if (typeof requesterUserSub === 'string') {
rawData = await this.prismaClient.ringtone.findMany({
where: {
OR: [
{
deletedAt: {
equals: null,
},
async browse(skip: number = 0, take: number = 10, requesterUserSub?: string): Promise<Counted<models.Ringtone>> {
const baseArgs = {
where: {
OR: [
{
deletedAt: {
equals: null,
},
{
composerUserSub: {
equals: requesterUserSub,
},
}
],
},
skip,
take,
})
} else {
rawData = await this.prismaClient.ringtone.findMany({
where: {
deletedAt: {
equals: null,
},
} as Record<string, unknown>,,
]
},
}

if (typeof requesterUserSub === 'string') {
baseArgs.where.OR.push({
composerUserSub: {
equals: requesterUserSub,
},
skip,
take,
})
}

return rawData.map(serializeRingtone)
const args = {
...baseArgs,
skip,
take,
}

const rawData = await this.prismaClient.ringtone.findMany(args)
const total = await this.prismaClient.ringtone.count(baseArgs)

return {
total,
data: rawData.map(serializeRingtone)
}
}

async search(q: string = '', requesterUserSub?: string): Promise<models.Ringtone[]> {
let rawData
if (typeof requesterUserSub === 'string') {
rawData = this.prismaClient.ringtone.findMany({
where: {
OR: [
{
deletedAt: {
equals: null,
},
},
{
composerUserSub: {
equals: requesterUserSub,
},
async search(q: string = '', requesterUserSub?: string): Promise<Counted<models.Ringtone>> {
const baseArgs = {
where: {
OR: [
{
deletedAt: {
equals: null,
},
],
name: {
contains: q,
},
} as Record<string, unknown>,
],
name: {
contains: q,
},
})
} else {
rawData = this.prismaClient.ringtone.findMany({
where: {
name: {
contains: q,
},
deletedAt: {
equals: null,
},
},
}

if (typeof requesterUserSub === 'string') {
baseArgs.where.OR.push({
composerUserSub: {
equals: requesterUserSub,
},
})
},)
}

const rawData = await this.prismaClient.ringtone.findMany(baseArgs)
const total = await this.prismaClient.ringtone.count(baseArgs)
return {
data: rawData.map(serializeRingtone),
total,
}
return rawData.map(serializeRingtone)
}

async create(data: Partial<models.Ringtone>, requesterUserSub: string): Promise<models.Ringtone> {


Laddar…
Avbryt
Spara