geekelo / dsa_practice

This repo is maintained just for the purpose of daily data structures and algorithm practice and proficiency
1 stars 0 forks source link

ESCROW #59

Open geekelo opened 1 month ago

geekelo commented 1 month ago

[P2P-API] Add ecrowAmount and tradeEscrow amount to profiles

RUN npx typeorm migration:create src/migrations/AddEscrowAmountsToProfiles npx typeorm migration:create src/migrations/AddTradeEscrowAmountsToProfiles

THEN MODIFY MIGRATION

import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm';

export class AddEscrowAmountsToProfiles1632839270312 implements MigrationInterface {
  name = 'AddEscrowAmountsToProfiles1632839270312';

  public async up(queryRunner: QueryRunner): Promise<void> {
    const checkEscrowAmountColumn = await queryRunner.hasColumn('profile', 'escrowAmount');
    if (!checkEscrowAmountColumn) {
      await queryRunner.addColumn(
        'profiles',
        new TableColumn({
          name: 'escrowAmount',
          type: 'decimal',
          precision: 10,
          scale: 2,
          default: 0,
          isNullable: true,
        }),
      );
    }

    const checkTradeEscrowAmountColumn = await queryRunner.hasColumn('profiles', 'tradeEscrowAmount');
    if (!checkTradeEscrowAmountColumn) {
      await queryRunner.addColumn(
        'profiles',
        new TableColumn({
          name: 'tradeEscrowAmount',
          type: 'decimal',
          precision: 10,
          scale: 2,
          default: 0,
          isNullable: true,
        }),
      );
    }
  }

  public async down(queryRunner: QueryRunner): Promise<void> {
    const checkEscrowAmountColumn = await queryRunner.hasColumn('profiles', 'escrowAmount');
    if (checkEscrowAmountColumn) {
      await queryRunner.dropColumn('profiles', 'escrowAmount');
    }

    const checkTradeEscrowAmountColumn = await queryRunner.hasColumn('profiles', 'tradeEscrowAmount');
    if (checkTradeEscrowAmountColumn) {
      await queryRunner.dropColumn('profiles', 'tradeEscrowAmount');
    }
  }
}
geekelo commented 1 month ago

[P2P-API] Update the get escrow action in the profiles controller to return the amount from the p2p profiles. Remove the fees as well

- In profiles controller


  @Get(':id/escrow_amount')
  @UsePipes(ValidationPipe)
  async getUserEscrowAmount(@Param('id') id: string) {
    try {
      const profile = await this.service.findOne({ where: { id: id } });
      if (!profile) {
        throw new NotFoundException('Profile ID Not Found.');
      }
      return Generic.response(1, {
        escrowAmount: profile.escrowAmount,
        tradeEscrowAmount: profile.tradeEscrowAmount,
      });
    } catch (error) {
      throw new HttpException(error.message, error.status);
    }
  }

- In Entities


  @Column({ type: 'decimal', default: 0 })
  escrowAmount: number;

  @Column({ type: 'decimal', default: 0 })
  tradeEscrowAmount: number;
geekelo commented 1 month ago

[P2P-API] When creating an offer, add the offer amount to the ecrowAmount in the profiles table. Confirm the amount you need to add from product. I.e for offers with range and fixedAmount

@Post()
  @UsePipes(ValidationPipe)
  async create(@Body() createDto: CreateOfferDto) {
    try {
      const pmTableData = await this.pmService.findOne({
        where: { id: createDto.paymentMethodId },
        relations: { paymentMethodGroup: true },
      });

      if (!pmTableData) {
        throw new NotFoundException('Payment Method ID Not Found.');
      }

      const pmgTableData = await this.pmgService.findOne({
        where: { id: pmTableData.paymentMethodGroup.id },
      });

      if (!pmgTableData) {
        throw new NotFoundException('Payment Method Group ID Not Found.');
      }

      const pTableData = await this.pService.findOne({
        where: { id: createDto.profileId },
        relations: { country: true },
      });

      if (!pTableData) {
        throw new NotFoundException('User ID Not Found.');
      }

      const ccTableData = await this.ccService.findOne({
        where: { code: createDto.cryptoCurrencyCode },
      });

      if (!ccTableData) {
        throw new NotFoundException('Crypto Currency Code Not Found.');
      }

      if (createDto.country) {
        const cTableData = await this.cService.findOne({
          where: { code: createDto.country },
        });

        if (!cTableData) {
          throw new NotFoundException('Country Code Not Found.');
        }
        createDto.country = cTableData;
      } else if (pTableData && pTableData.country) {
        createDto.country = pTableData.country;
      }

      const fcTableData = await this.fcService.findOne({
        where: { code: createDto.fiatCurrencyCode },
      });

      if (!fcTableData) {
        throw new NotFoundException('Fiat Curreny Code Not Found.');
      }

      createDto.paymentMethod = pmTableData;
      createDto.paymentMethodGroup = pmgTableData;
      createDto.cryptoCurrency = ccTableData;
      createDto.fiatCurrency = fcTableData;
      createDto.profile = pTableData;
      createDto.userCountry = pTableData.country;
      createDto.paymentWindow = (
        createDto.fiatCurrency.code === 'NGN' ? 0.5 : 8
      ).toString();

      const dutyHours = createDto.dutyHours
        ? JSON.parse(createDto.dutyHours.toString())
        : null;
      delete createDto.dutyHours;

      const offer = await this.service.create(createDto);

      const offerAmount = createDto.fixedPrice || createDto.rangeMax || 0;
      pTableData.escrowAmount += offerAmount;
      await this.pService.update(pTableData.id, pTableData);

      const self = this;
      if (dutyHours) {
        dutyHours.forEach(function (row: any) {
          row.offer = offer;
          self.service.ceateDutyHours(row);
        });
      }
      return Generic.response(1, await this.service.findOne(offer.id));
    } catch (error) {
      throw new HttpException(error.message, error.status);
    }
  }

  @Get()
  async findAll(@Query() query: GetOfferDto) {
    try {
      return Generic.response(
        1,
        await this.service.paginate(
          { page: query.query, limit: query.limit },
          query,
        ),
      );
    } catch (error) {
      throw new HttpException(error.message, error.status);
    }
  }
geekelo commented 1 month ago

[P2P-API] Refactor the current escrow amount from trades to subtract the value from the escrow amount and add it to the trade escrow amount

To refactor the current escrow amount from trades, subtracting the value of the trade amount from the escrow amount, and adding it to the trade escrow amount when a trade is completed, the relevant section of the code is in the tradeCompleted method of the TradeService class.

Here's the existing tradeCompleted method with comments indicating where you need to modify the code to achieve this:

async tradeCompleted(tradeId: string, body: any) {
    const trade = await this.repository.findOne({ where: { id: tradeId } });
    if (!trade) {
        throw new NotFoundException('Trade has been deleted or invalid!');
    }

    if (
        ![TradeStates.PAYMENT_ACCEPTED, TradeStates.COMPLETED].includes(
            trade.state,
        )
    ) {
        throw new HttpException(
            'You are not allowed to perform this action!',
            HttpStatus.BAD_REQUEST,
        );
    }
    const previousState = trade.state;

    trade.state = TradeStates.COMPLETED;
    trade.completedAt = new Date();
    trade.isInEscrow = false;

    // Subtracting the value of the trade amount from the escrow amount
    const sellerProfile = await this.profileRepository.findOne({
        where: { id: trade.sellerProfileId },
    });
    if (!sellerProfile) {
        throw new NotFoundException('Seller profile not found!');
    }

    sellerProfile.escrowBalance -= trade.cryptoAmount;

    // Adding the trade amount to the trade escrow amount
    trade.tradeEscrowAmount = trade.cryptoAmount;

    await this.profileRepository.save(sellerProfile);
    const updatedTrade = await this.repository.save(trade);

    if (previousState === TradeStates.PAYMENT_ACCEPTED) {
        /**
         * Update buyer rating and stats
         */
        const buyerProfile = await this.profileRepository.findOne({
            where: { id: trade.buyerProfileId },
        });
        buyerProfile.tradeVolume =
            parseFloat(buyerProfile.tradeVolume.toString()) +
            parseFloat(trade.cryptoAmount);
        buyerProfile.totalTrades += 1;
        const buyerRating = await this.calculateProfileRating(
            trade.buyerProfileId,
        );
        buyerProfile.rating = buyerRating;

        if (trade.tradeType === 'sell') {
            buyerProfile.tradeOfferVolume =
                parseFloat(buyerProfile.tradeOfferVolume.toString()) +
                parseFloat(trade.cryptoAmount);
            buyerProfile.totalOffersCompleted += 1;
        }

        await this.profileRepository.update(trade.buyerProfileId, buyerProfile);

        /**
         * Update seller rating and stats
         */
        sellerProfile.tradeVolume =
            parseFloat(sellerProfile.tradeVolume.toString()) +
            parseFloat(trade.cryptoAmount);
        sellerProfile.totalTrades += 1;
        const sellerRating = await this.calculateProfileRating(
            trade.sellerProfileId,
        );
        sellerProfile.rating = sellerRating;

        if (trade.tradeType === 'buy') {
            sellerProfile.tradeOfferVolume =
                parseFloat(sellerProfile.tradeOfferVolume.toString()) +
                parseFloat(trade.cryptoAmount);
            sellerProfile.totalOffersCompleted += 1;
        }

        await this.profileRepository.update(trade.sellerProfileId, sellerProfile);
    }

    return {
        message: 'Trade payment has been completed successfully!',
        trade: {
            state: updatedTrade.state,
            completedAt: updatedTrade.completedAt,
            id: updatedTrade.id,
        },
    };
}

Explanation of Changes:

  1. Subtracting the Trade Amount from the Escrow Balance:

    • Find the seller profile.
    • Subtract the trade amount from the seller's escrowBalance.
    • Save the updated seller profile.
  2. Adding the Trade Amount to the Trade Escrow Amount:

    • Update the trade.tradeEscrowAmount with the trade amount.
    • Save the updated trade.
  3. Save the Updated Seller Profile and Trade:

    • Ensure the seller profile is saved with the updated escrowBalance.
    • Save the trade with the updated tradeEscrowAmount.

These changes ensure that when a trade is completed, the value of the trade amount is subtracted from the escrow balance of the seller and added to the trade escrow amount.

geekelo commented 1 month ago

[P2P-API] When a user deactivates an offer, update the escrowAmount to release the remaining funds. We need to keep track of all the trades that have taken place for the offer and subtract the total amount from the amount held in escrow for that offer.

Yes, the two blocks of code you provided should be integrated into your OfferService class. Here’s how you can include them:

  1. Add the deactivateOffer Method

You should add the deactivateOffer method to the OfferService class. This method will handle the logic for deactivating an offer and updating the escrow amounts.

  1. Add the calculateTotalAmountFromTrades Method

You should also add the calculateTotalAmountFromTrades method to the OfferService class. This method will be used by deactivateOffer to calculate the total amount from trades associated with an offer.

Here’s how you can incorporate both methods into your OfferService:

@Injectable()
export class OfferService {
  private readonly logger = new Logger(OfferService.name);
  private cache: NodeCache;

  constructor(
    @InjectRepository(Offer) private readonly repository: Repository<Offer>,
    @InjectRepository(DutyHour) private readonly repositoryDH: Repository<DutyHour>,
    @InjectRepository(ExternalRate) private readonly repositoryExternalRate: Repository<ExternalRate>,
    @InjectRepository(Profile) private readonly profileRepository: Repository<Profile>, // Add Profile repository
    @InjectRepository(Trade) private readonly tradeRepository: Repository<Trade>, // Add Trade repository
    private algoliaService: AlgoliaService,
  ) {
    this.cache = new NodeCache({ stdTTL: 100, checkperiod: 120 });
  }

  // Add this method
  async deactivateOffer(id: string): Promise<void> {
    const offer = await this.repository.findOne({
      where: { id },
      relations: ['profile', 'trades'], // Adjust the relations based on your actual entities
    });

    if (!offer) {
      throw new Error('Offer not found');
    }

    // Calculate the total amount from trades associated with this offer
    const totalAmountFromTrades = await this.calculateTotalAmountFromTrades(id);

    // Update the profile’s escrow balance
    if (offer.profile) {
      offer.profile.escrowBalance += totalAmountFromTrades;
      await this.profileRepository.save(offer.profile);
    }

    // Update the offer's escrow amount
    offer.escrowAmount -= totalAmountFromTrades;
    await this.repository.save(offer);

    // Optionally, handle any additional cleanup or notifications
  }

  // Add this method
  private async calculateTotalAmountFromTrades(offerId: string): Promise<number> {
    // Adjust this query based on your actual trade model and relations
    const trades = await this.tradeRepository.find({
      where: { offerId },
    });

    // Sum up the amounts from the trades
    return trades.reduce((total, trade) => total + trade.amount, 0);
  }

  // Other methods...
}

Changes Made:

  1. Profile Repository Injection: Added @InjectRepository(Profile) to inject the Profile repository since it's used in the deactivateOffer method.
  2. Trade Repository Injection: Added @InjectRepository(Trade) to inject the Trade repository since it's used in the calculateTotalAmountFromTrades method.
  3. Methods: Added the deactivateOffer and calculateTotalAmountFromTrades methods to the OfferService class.

Additional Considerations:

Feel free to adjust the code based on your specific entity definitions and relations. Let me know if you need further assistance!

Other changes

To address this requirement, you need to make several modifications to your service logic. Here’s a step-by-step guide on where and how to implement these changes:

1. Update Offer Deactivation Logic

When an offer is deactivated, you need to:

Here's a general approach:

1.1 Modify the OfferService

Add a new method to handle the deactivation of an offer. This method should:

async deactivateOffer(id: string): Promise<void> {
  const offer = await this.repository.findOne({
    where: { id },
    relations: ['profile', 'trades'], // Assuming 'trades' is the relation for trades related to the offer
  });

  if (!offer) {
    throw new Error('Offer not found');
  }

  // Calculate the total amount from trades associated with this offer
  const totalAmountFromTrades = await this.calculateTotalAmountFromTrades(id);

  // Update the profile’s escrow balance
  if (offer.profile) {
    offer.profile.escrowBalance += totalAmountFromTrades;
    await this.profileRepository.save(offer.profile);
  }

  // Update the offer's escrow amount
  offer.escrowAmount -= totalAmountFromTrades;
  await this.repository.save(offer);

  // Optionally, handle any additional cleanup or notifications
}

1.2 Calculate Total Amount from Trades

Add a method to calculate the total amount from trades related to the offer:

private async calculateTotalAmountFromTrades(offerId: string): Promise<number> {
  // Adjust this query based on your actual trade model and relations
  const trades = await this.tradeRepository.find({
    where: { offerId },
  });

  // Sum up the amounts from the trades
  return trades.reduce((total, trade) => total + trade.amount, 0);
}

2. Update Offer Entity

Ensure your Offer entity has an escrowAmount field and that it’s properly updated when an offer is deactivated.

@Entity()
export class Offer {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  // Other fields...

  @Column({ type: 'decimal', default: 0 })
  escrowAmount: number;

  @ManyToOne(() => Profile, profile => profile.offers)
  profile: Profile;

  @OneToMany(() => Trade, trade => trade.offer)
  trades: Trade[];
}

3. Update Profile Entity

Ensure your Profile entity has an escrowBalance field.

@Entity()
export class Profile {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  // Other fields...

  @Column({ type: 'decimal', default: 0 })
  escrowBalance: number;

  @OneToMany(() => Offer, offer => offer.profile)
  offers: Offer[];
}

4. Implement the Deactivation Logic

Ensure that the deactivation of an offer triggers the deactivateOffer method. This could be part of an existing method or a new endpoint in your controller:

@Controller('offers')
export class OfferController {
  constructor(private readonly offerService: OfferService) {}

  @Patch(':id/deactivate')
  async deactivateOffer(@Param('id') id: string) {
    await this.offerService.deactivateOffer(id);
    return { message: 'Offer deactivated and escrow updated' };
  }
}

5. Testing

Test the new functionality thoroughly:

Implement these changes carefully and test thoroughly to ensure that the escrow amount is accurately updated when an offer is deactivated. If you have any questions or need further assistance with specific parts of the implementation, feel free to ask!

geekelo commented 1 month ago

[DASHBOARD] Add migration to save the current amount in escrow to the tradeEscrow column in profiles

To add a migration in Rails to save the current amount in escrow to a new column (trade_escrow) in the profiles table, you'll need to follow these steps:

  1. Generate a Migration: Create a new migration to add the trade_escrow column to the profiles table.

  2. Update the Column with Current Escrow Amount: Modify the migration to update the new column with the current escrow amount.

  3. Run the Migration: Apply the migration to your database.

Here’s a step-by-step guide:

1. Generate the Migration

Use the Rails generator to create a new migration:

rails generate migration AddTradeEscrowToProfiles trade_escrow:decimal

This command creates a new migration file that adds a trade_escrow column of type decimal to the profiles table.

2. Update the Migration

Open the newly generated migration file in db/migrate/. It should look something like this:

class AddTradeEscrowToProfiles < ActiveRecord::Migration[6.0]
  def change
    add_column :profiles, :trade_escrow, :decimal, precision: 10, scale: 2
  end
end

To also update the trade_escrow column with the current escrow amount, you can add a def up method to handle this. Here’s an example of how to modify the migration:

class AddTradeEscrowToProfiles < ActiveRecord::Migration[6.0]
  def up
    add_column :profiles, :trade_escrow, :decimal, precision: 10, scale: 2

    # Assuming you have a method to calculate the current escrow amount
    # You may need to adjust this based on your application logic
    Profile.reset_column_information # Ensure that the new column is loaded
    Profile.find_each do |profile|
      profile.update_column(:trade_escrow, profile.escrow_amount) # Adjust as needed
    end
  end

  def down
    remove_column :profiles, :trade_escrow
  end
end

3. Run the Migration

Apply the migration to your database:

rails db:migrate

4. Update the Model (Optional)

If you need to update your Profile model to include any logic for trade_escrow, add it to app/models/profile.rb:

class Profile < ApplicationRecord
  # Ensure `trade_escrow` is accessible and managed as needed
  # Example: validating presence or numericality
  validates :trade_escrow, numericality: { greater_than_or_equal_to: 0 }
end

5. Verify the Changes

After running the migration, check your profiles table to ensure the trade_escrow column has been added and populated correctly.

Summary

Feel free to adjust the migration logic based on your specific application needs and business rules!

geekelo commented 1 month ago

[DASHBOARD] Add migration to move the current active offers to escrow

To move the current active offers' amounts to the escrow_amount column in the profiles table via a Rails migration, follow these steps:

  1. Generate a Migration: Create a new migration to perform the necessary updates.

  2. Update the Migration: Add logic to move the amounts from active offers to the escrow_amount column in the profiles table.

  3. Run the Migration: Apply the migration to your database.

Here’s how you can implement it:

1. Generate the Migration

Create a new migration file using the Rails generator:

rails generate migration MoveActiveOffersToEscrow

2. Update the Migration

Open the newly generated migration file in db/migrate/. It should look like this:

class MoveActiveOffersToEscrow < ActiveRecord::Migration[6.0]
  def up
    # Update profiles to move amounts from active offers to escrow_amount

    # Assuming you have an Offer model and Profile model with appropriate associations

    # Retrieve all active offers
    active_offers = Offer.where(active: true)

    # Group by profile to accumulate the total escrow amount
    offer_amounts_by_profile = active_offers.group(:profile_id).sum(:amount)

    # Update each profile with the accumulated escrow amount
    offer_amounts_by_profile.each do |profile_id, amount|
      profile = Profile.find(profile_id)
      profile.update_column(:escrow_amount, profile.escrow_amount + amount)
    end
  end

  def down
    # Optional: Define how to revert the changes if needed
    # For instance, if you need to set escrow_amount to the original values, you would implement that here
  end
end

Explanation

3. Run the Migration

Apply the migration to your database with:

rails db:migrate

4. Verify the Changes

Check the profiles table to ensure that the escrow_amount column has been updated correctly based on the amounts from active offers.

Optional: Rollback (if needed)

If you need to provide a way to revert the migration, you could implement logic in the down method. For example:

def down
  # Reset escrow_amount for each profile to its original state
  # You would need to have a way to track the original amounts if you want to support rollback
end

Summary

Adjust the migration logic according to your specific schema and business logic.